feat(preconditioner): major work on preconditioner system
first preconditioner MVP
This commit is contained in:
392
tests/preconditioning/backends.cpp
Normal file
392
tests/preconditioning/backends.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
namespace backend = mean_field::preconditioning::backend;
|
||||
|
||||
using DiagonalMass = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::mass_like,
|
||||
preconditioning::OperatorValueStructure::scalar,
|
||||
preconditioning::OperatorSymmetry::symmetric,
|
||||
preconditioning::OperatorDefiniteness::positive_definite,
|
||||
preconditioning::OperatorRepresentation::diagonal,
|
||||
preconditioning::OperatorDistribution::local,
|
||||
preconditioning::OperatorFESpace::h1>;
|
||||
using MatrixFreeHdivMass = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::mass_like,
|
||||
preconditioning::OperatorValueStructure::vector,
|
||||
preconditioning::OperatorSymmetry::symmetric,
|
||||
preconditioning::OperatorDefiniteness::positive_definite,
|
||||
preconditioning::OperatorRepresentation::matrix_free,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof,
|
||||
preconditioning::OperatorFESpace::h_div>;
|
||||
using DenseBorder = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::dense_border,
|
||||
preconditioning::OperatorValueStructure::block,
|
||||
preconditioning::OperatorSymmetry::nonsymmetric,
|
||||
preconditioning::OperatorDefiniteness::indefinite,
|
||||
preconditioning::OperatorRepresentation::assembled_dense,
|
||||
preconditioning::OperatorDistribution::local>;
|
||||
using ScalarH1Elliptic = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::elliptic_like,
|
||||
preconditioning::OperatorValueStructure::scalar,
|
||||
preconditioning::OperatorSymmetry::symmetric,
|
||||
preconditioning::OperatorDefiniteness::positive_definite,
|
||||
preconditioning::OperatorRepresentation::assembled_sparse,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof,
|
||||
preconditioning::OperatorFESpace::h1>;
|
||||
using ConstantNullspaceH1Elliptic = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::elliptic_like,
|
||||
preconditioning::OperatorValueStructure::scalar,
|
||||
preconditioning::OperatorSymmetry::symmetric,
|
||||
preconditioning::OperatorDefiniteness::positive_semidefinite,
|
||||
preconditioning::OperatorRepresentation::assembled_sparse,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof,
|
||||
preconditioning::OperatorFESpace::h1,
|
||||
preconditioning::OperatorNullspace::constant_mode>;
|
||||
using HdivElliptic = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::elliptic_like,
|
||||
preconditioning::OperatorValueStructure::vector,
|
||||
preconditioning::OperatorSymmetry::symmetric,
|
||||
preconditioning::OperatorDefiniteness::positive_definite,
|
||||
preconditioning::OperatorRepresentation::assembled_sparse,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof,
|
||||
preconditioning::OperatorFESpace::h_div>;
|
||||
using NonsymmetricH1Elliptic = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::elliptic_like,
|
||||
preconditioning::OperatorValueStructure::scalar,
|
||||
preconditioning::OperatorSymmetry::nonsymmetric,
|
||||
preconditioning::OperatorDefiniteness::indefinite,
|
||||
preconditioning::OperatorRepresentation::assembled_sparse,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof,
|
||||
preconditioning::OperatorFESpace::h1>;
|
||||
using SuppliedNullspaceH1Elliptic = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::elliptic_like,
|
||||
preconditioning::OperatorValueStructure::scalar,
|
||||
preconditioning::OperatorSymmetry::symmetric,
|
||||
preconditioning::OperatorDefiniteness::positive_semidefinite,
|
||||
preconditioning::OperatorRepresentation::assembled_sparse,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof,
|
||||
preconditioning::OperatorFESpace::h1,
|
||||
preconditioning::OperatorNullspace::supplied_basis>;
|
||||
|
||||
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
||||
using AdaptiveAMG = backend::HypreBoomerAMG<backend::SolveToTolerance>;
|
||||
|
||||
using DiagonalComponent = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<blocks::density::mass::value>,
|
||||
blocks::type_list<blocks::density::mass::residual>,
|
||||
blocks::type_list<>,
|
||||
DiagonalMass,
|
||||
backend::Diagonal>;
|
||||
using UnderdeclaredDiagonalComponent = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<blocks::density::mass::value>,
|
||||
blocks::type_list<blocks::density::mass::residual>,
|
||||
blocks::type_list<>,
|
||||
DiagonalMass,
|
||||
backend::Diagonal,
|
||||
preconditioning::NoPreparationDependencies>;
|
||||
|
||||
class TinyParallelH1Operator final {
|
||||
public:
|
||||
TinyParallelH1Operator()
|
||||
: m_serialMesh(mfem::Mesh::MakeCartesian1D(4)),
|
||||
m_parallelMesh(
|
||||
MPI_COMM_WORLD,
|
||||
m_serialMesh
|
||||
),
|
||||
m_collection(
|
||||
1,
|
||||
1
|
||||
),
|
||||
m_space(
|
||||
&m_parallelMesh,
|
||||
&m_collection
|
||||
),
|
||||
m_form(&m_space) {
|
||||
m_form.AddDomainIntegrator(new mfem::DiffusionIntegrator());
|
||||
m_form.AddDomainIntegrator(new mfem::MassIntegrator());
|
||||
m_form.Assemble();
|
||||
m_form.Finalize();
|
||||
m_matrix.reset(m_form.ParallelAssemble());
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::HypreParMatrix &matrix() const {
|
||||
return *m_matrix;
|
||||
}
|
||||
|
||||
private:
|
||||
mfem::Mesh m_serialMesh;
|
||||
mfem::ParMesh m_parallelMesh;
|
||||
mfem::H1_FECollection m_collection;
|
||||
mfem::ParFiniteElementSpace m_space;
|
||||
mfem::ParBilinearForm m_form;
|
||||
std::unique_ptr<mfem::HypreParMatrix> m_matrix;
|
||||
};
|
||||
|
||||
class KnownMatrixFreeSPDOperator final : public mfem::Operator {
|
||||
public:
|
||||
KnownMatrixFreeSPDOperator()
|
||||
: mfem::Operator(3),
|
||||
m_matrix(3) {
|
||||
m_matrix = 0.0;
|
||||
m_matrix(0, 0) = 4.0;
|
||||
m_matrix(0, 1) = 1.0;
|
||||
m_matrix(1, 0) = 1.0;
|
||||
m_matrix(1, 1) = 3.0;
|
||||
m_matrix(1, 2) = 0.5;
|
||||
m_matrix(2, 1) = 0.5;
|
||||
m_matrix(2, 2) = 2.0;
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &input,
|
||||
mfem::Vector &output
|
||||
) const override {
|
||||
m_matrix.Mult(input, output);
|
||||
}
|
||||
|
||||
void AssembleDiagonal(mfem::Vector &diagonal) const override {
|
||||
diagonal.SetSize(Height());
|
||||
for (int index = 0; index < Height(); ++index) {
|
||||
diagonal(index) = m_matrix(index, index);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
mfem::DenseMatrix m_matrix;
|
||||
};
|
||||
|
||||
[[nodiscard]] double relativeResidual(
|
||||
const mfem::HypreParMatrix &matrix,
|
||||
const mfem::Vector &rightHandSide,
|
||||
const mfem::Vector &action
|
||||
) {
|
||||
mfem::Vector residual(rightHandSide.Size());
|
||||
matrix.Mult(action, residual);
|
||||
residual -= rightHandSide;
|
||||
return gravity_prepared_test_utils::global_norm(residual, matrix.GetComm()) /
|
||||
gravity_prepared_test_utils::global_norm(rightHandSide, matrix.GetComm());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Preconditioning Backends Expose Complete Compile-Time Capabilities",
|
||||
tags::preconditioning_backend_unit
|
||||
) {
|
||||
STATIC_CHECK(backend::Compatible<backend::Diagonal, DiagonalMass>);
|
||||
STATIC_CHECK(backend::Compatible<backend::Diagonal, MatrixFreeHdivMass>);
|
||||
STATIC_CHECK(backend::Compatible<backend::MatrixFreeChebyshev, MatrixFreeHdivMass>);
|
||||
STATIC_CHECK(backend::Compatible<backend::DenseDirect, DenseBorder>);
|
||||
STATIC_CHECK(backend::Compatible<FixedAMG, ScalarH1Elliptic>);
|
||||
STATIC_CHECK(backend::Compatible<FixedAMG, ConstantNullspaceH1Elliptic>);
|
||||
STATIC_CHECK_FALSE(backend::Compatible<FixedAMG, HdivElliptic>);
|
||||
STATIC_CHECK_FALSE(backend::Compatible<FixedAMG, NonsymmetricH1Elliptic>);
|
||||
STATIC_CHECK_FALSE(backend::Compatible<FixedAMG, SuppliedNullspaceH1Elliptic>);
|
||||
|
||||
STATIC_CHECK(backend::ArnoldiAdmissible<FixedAMG>);
|
||||
STATIC_CHECK(backend::ArnoldiAdmissible<backend::MatrixFreeChebyshev>);
|
||||
STATIC_CHECK_FALSE(backend::ArnoldiAdmissible<AdaptiveAMG>);
|
||||
STATIC_CHECK(backend::requiresAssembledSparseSurrogate<FixedAMG>);
|
||||
STATIC_CHECK_FALSE(backend::requiresAssembledSparseSurrogate<backend::Diagonal>);
|
||||
STATIC_CHECK(backend::Traits<backend::Diagonal>::supportsSerialExecution);
|
||||
STATIC_CHECK(backend::Traits<backend::Diagonal>::supportsDistributedExecution);
|
||||
STATIC_CHECK(backend::Traits<backend::DenseDirect>::supportsSerialExecution);
|
||||
STATIC_CHECK_FALSE(backend::Traits<backend::DenseDirect>::supportsDistributedExecution);
|
||||
STATIC_CHECK_FALSE(backend::Traits<FixedAMG>::supportsSerialExecution);
|
||||
STATIC_CHECK(backend::Traits<FixedAMG>::supportsDistributedExecution);
|
||||
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<DiagonalComponent>);
|
||||
STATIC_CHECK(
|
||||
DiagonalComponent::PreparationDependencies::contains(preconditioning::PreparationDependency::linearization)
|
||||
);
|
||||
STATIC_CHECK_FALSE(preconditioning::PreconditionerComponent<UnderdeclaredDiagonalComponent>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Diagonal Backend Exactly Inverts A Known Diagonal Operator",
|
||||
tags::preconditioning_backend_unit
|
||||
) {
|
||||
mfem::Vector diagonal(3);
|
||||
diagonal(0) = 2.0;
|
||||
diagonal(1) = -4.0;
|
||||
diagonal(2) = 0.5;
|
||||
|
||||
auto prepared = backend::prepare(backend::Diagonal{}, diagonal);
|
||||
mfem::Vector rightHandSide(3);
|
||||
rightHandSide(0) = 4.0;
|
||||
rightHandSide(1) = 8.0;
|
||||
rightHandSide(2) = -1.0;
|
||||
mfem::Vector action(3);
|
||||
prepared.Mult(rightHandSide, action);
|
||||
|
||||
CHECK(action(0) == Catch::Approx(2.0));
|
||||
CHECK(action(1) == Catch::Approx(-2.0));
|
||||
CHECK(action(2) == Catch::Approx(-2.0));
|
||||
CHECK(prepared.GetStatistics().setups == 1);
|
||||
CHECK(prepared.GetStatistics().applications == 1);
|
||||
CHECK(prepared.GetStatistics().innerIterations == 0);
|
||||
|
||||
diagonal(1) = 0.0;
|
||||
CHECK_THROWS_AS(prepared.Refresh(diagonal), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Dense Direct Backend Exactly Solves And Refreshes A Known Border",
|
||||
tags::preconditioning_backend_unit
|
||||
) {
|
||||
mfem::DenseMatrix matrix(2);
|
||||
matrix(0, 0) = 4.0;
|
||||
matrix(0, 1) = 1.0;
|
||||
matrix(1, 0) = 2.0;
|
||||
matrix(1, 1) = 3.0;
|
||||
|
||||
auto prepared = backend::prepare(backend::DenseDirect{}, matrix);
|
||||
mfem::Vector rightHandSide(2);
|
||||
rightHandSide(0) = 7.0;
|
||||
rightHandSide(1) = 1.0;
|
||||
mfem::Vector action(2);
|
||||
prepared.Mult(rightHandSide, action);
|
||||
|
||||
CHECK(action(0) == Catch::Approx(2.0).margin(1.0e-14));
|
||||
CHECK(action(1) == Catch::Approx(-1.0).margin(1.0e-14));
|
||||
|
||||
matrix = 0.0;
|
||||
matrix(0, 0) = 2.0;
|
||||
matrix(1, 1) = 4.0;
|
||||
prepared.Refresh(matrix);
|
||||
prepared.Mult(rightHandSide, action);
|
||||
|
||||
CHECK(action(0) == Catch::Approx(3.5).margin(1.0e-14));
|
||||
CHECK(action(1) == Catch::Approx(0.25).margin(1.0e-14));
|
||||
CHECK(prepared.GetStatistics().setups == 2);
|
||||
CHECK(prepared.GetStatistics().applications == 2);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Matrix-Free Chebyshev Backend Is A Fixed Linear Positive Approximate Inverse",
|
||||
tags::preconditioning_backend_unit
|
||||
) {
|
||||
const KnownMatrixFreeSPDOperator operation;
|
||||
auto prepared =
|
||||
backend::prepare(backend::MatrixFreeChebyshev{.order = 3, .powerIterations = 20}, operation, MPI_COMM_WORLD);
|
||||
|
||||
mfem::Vector first(3);
|
||||
first(0) = 1.0;
|
||||
first(1) = -2.0;
|
||||
first(2) = 0.25;
|
||||
mfem::Vector second(3);
|
||||
second(0) = -0.5;
|
||||
second(1) = 0.75;
|
||||
second(2) = 3.0;
|
||||
mfem::Vector combination(first);
|
||||
combination *= 1.7;
|
||||
combination.Add(-0.4, second);
|
||||
|
||||
mfem::Vector firstAction(3);
|
||||
mfem::Vector secondAction(3);
|
||||
mfem::Vector combinationAction(3);
|
||||
prepared.Mult(first, firstAction);
|
||||
prepared.Mult(second, secondAction);
|
||||
prepared.Mult(combination, combinationAction);
|
||||
|
||||
mfem::Vector expected(firstAction);
|
||||
expected *= 1.7;
|
||||
expected.Add(-0.4, secondAction);
|
||||
mfem::Vector linearityError(combinationAction);
|
||||
linearityError -= expected;
|
||||
CHECK(linearityError.Norml2() <= 2.0e-12 * std::max(1.0, expected.Norml2()));
|
||||
CHECK((first * firstAction) > 0.0);
|
||||
CHECK(prepared.GetStatistics().setups == 1);
|
||||
CHECK(prepared.GetStatistics().applications == 3);
|
||||
CHECK(prepared.GetStatistics().innerIterations == 9);
|
||||
CHECK(prepared.GetStatistics().lastInnerIterations == 3);
|
||||
CHECK_THROWS_AS(
|
||||
backend::prepare(backend::MatrixFreeChebyshev{.order = 0}, operation, MPI_COMM_WORLD), std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
backend::prepare(backend::MatrixFreeChebyshev{.order = 6}, operation, MPI_COMM_WORLD), std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
backend::prepare(backend::MatrixFreeChebyshev{.powerTolerance = 1.0}, operation, MPI_COMM_WORLD),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
backend::prepare(backend::MatrixFreeChebyshev{.powerSeed = 0}, operation, MPI_COMM_WORLD), std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"BoomerAMG Fixed Cycles Are Linear While Adaptive Application Meets Its Tolerance",
|
||||
tags::preconditioning_backend_unit
|
||||
) {
|
||||
TinyParallelH1Operator problem;
|
||||
const mfem::HypreParMatrix &matrix = problem.matrix();
|
||||
|
||||
mfem::Vector exact(matrix.Width());
|
||||
exact = 1.0;
|
||||
mfem::Vector rightHandSide(matrix.Height());
|
||||
matrix.Mult(exact, rightHandSide);
|
||||
|
||||
auto fixed = backend::prepare(FixedAMG{backend::FixedCycles{.cycles = 2}}, matrix);
|
||||
mfem::Vector fixedAction(matrix.Width());
|
||||
fixedAction = 0.0;
|
||||
fixed.Mult(rightHandSide, fixedAction);
|
||||
|
||||
CHECK(relativeResidual(matrix, rightHandSide, fixedAction) < 1.0);
|
||||
|
||||
mfem::Vector secondExact(matrix.Width());
|
||||
for (int index = 0; index < secondExact.Size(); ++index) {
|
||||
secondExact(index) = 0.25 + static_cast<double>(index);
|
||||
}
|
||||
mfem::Vector secondRightHandSide(matrix.Height());
|
||||
matrix.Mult(secondExact, secondRightHandSide);
|
||||
mfem::Vector secondAction(matrix.Width());
|
||||
secondAction = 0.0;
|
||||
fixed.Mult(secondRightHandSide, secondAction);
|
||||
|
||||
mfem::Vector combinedRightHandSide(rightHandSide);
|
||||
combinedRightHandSide *= 0.7;
|
||||
combinedRightHandSide.Add(-0.2, secondRightHandSide);
|
||||
mfem::Vector combinedAction(matrix.Width());
|
||||
combinedAction = 0.0;
|
||||
fixed.Mult(combinedRightHandSide, combinedAction);
|
||||
|
||||
mfem::Vector expectedCombinedAction(fixedAction);
|
||||
expectedCombinedAction *= 0.7;
|
||||
expectedCombinedAction.Add(-0.2, secondAction);
|
||||
combinedAction -= expectedCombinedAction;
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::global_norm(combinedAction, matrix.GetComm()) <
|
||||
1.0e-11 * gravity_prepared_test_utils::global_norm(expectedCombinedAction, matrix.GetComm())
|
||||
);
|
||||
|
||||
CHECK(fixed.GetStatistics().setups == 1);
|
||||
CHECK(fixed.GetStatistics().applications == 3);
|
||||
CHECK(fixed.GetStatistics().lastInnerIterations >= 1);
|
||||
CHECK(fixed.GetStatistics().lastInnerIterations <= 2);
|
||||
|
||||
auto adaptive = backend::prepare(
|
||||
AdaptiveAMG{backend::SolveToTolerance{.relativeTolerance = 1.0e-10, .maximumCycles = 50}}, matrix
|
||||
);
|
||||
mfem::Vector adaptiveAction(matrix.Width());
|
||||
adaptiveAction = 0.0;
|
||||
adaptive.Mult(rightHandSide, adaptiveAction);
|
||||
|
||||
CHECK(relativeResidual(matrix, rightHandSide, adaptiveAction) < 1.0e-8);
|
||||
CHECK(adaptive.GetStatistics().setups == 1);
|
||||
CHECK(adaptive.GetStatistics().applications == 1);
|
||||
CHECK(adaptive.GetStatistics().lastInnerIterations >= 1);
|
||||
CHECK(adaptive.GetStatistics().lastInnerIterations <= 50);
|
||||
}
|
||||
226
tests/preconditioning/equilibrium_coordinates.cpp
Normal file
226
tests/preconditioning/equilibrium_coordinates.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
|
||||
using Form = blocks::central_density_bordered_stellar_equilibrium_form;
|
||||
using GroupedComponent = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<
|
||||
blocks::density::mass::value,
|
||||
blocks::surface_deformation::parameters::value,
|
||||
blocks::enthalpy::specific::value,
|
||||
blocks::gravity::gradient::value,
|
||||
blocks::gravity::poisson::value,
|
||||
blocks::fixed_total_mass::mass_normalization::value,
|
||||
blocks::fixed_central_density::central_value::value>,
|
||||
blocks::type_list<
|
||||
blocks::density::mass::residual,
|
||||
blocks::surface_deformation::shape_equilibrium::residual,
|
||||
blocks::enthalpy::specific::residual,
|
||||
blocks::gravity::gradient::residual,
|
||||
blocks::gravity::poisson::residual,
|
||||
blocks::fixed_total_mass::mass_normalization::residual,
|
||||
blocks::fixed_central_density::central_value::residual>,
|
||||
blocks::type_list<>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
preconditioning::backend::Identity>;
|
||||
using IncompleteComponent = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<
|
||||
blocks::density::mass::value,
|
||||
blocks::surface_deformation::parameters::value,
|
||||
blocks::enthalpy::specific::value,
|
||||
blocks::gravity::gradient::value,
|
||||
blocks::gravity::poisson::value,
|
||||
blocks::fixed_total_mass::mass_normalization::value>,
|
||||
blocks::type_list<
|
||||
blocks::density::mass::residual,
|
||||
blocks::surface_deformation::shape_equilibrium::residual,
|
||||
blocks::enthalpy::specific::residual,
|
||||
blocks::gravity::gradient::residual,
|
||||
blocks::gravity::poisson::residual,
|
||||
blocks::fixed_total_mass::mass_normalization::residual>,
|
||||
blocks::type_list<>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
preconditioning::backend::Identity>;
|
||||
|
||||
[[nodiscard]] blocks::form_layout<Form> makeUnevenLayout() {
|
||||
return {
|
||||
std::array<int, Form::value_block_count>{2, 3, 4, 5, 6, 1, 1},
|
||||
std::array<int, Form::residual_block_count>{4, 5, 2, 3, 6, 1, 1}
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] double relativeError(
|
||||
const mfem::Vector &left,
|
||||
const mfem::Vector &right
|
||||
) {
|
||||
mfem::Vector difference(left);
|
||||
difference -= right;
|
||||
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies makeDependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 9301, .revision = 1},
|
||||
.density = {.identity = 9303, .revision = 1},
|
||||
.surfaceDeformation = {.identity = 9307, .revision = 1},
|
||||
.gravityGradient = {.identity = 9311, .revision = 1},
|
||||
.gravityPotential = {.identity = 9317, .revision = 1},
|
||||
.enthalpy = {.identity = 9323, .revision = 1},
|
||||
.bernoulliConstant = {.identity = 9329, .revision = 1},
|
||||
.rotation = {.identity = 9331, .revision = 1},
|
||||
.targetMass = {.identity = 9337, .revision = 1}
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::physics::RigidRotation zeroRotation() {
|
||||
mfem::Vector angularVelocity(3);
|
||||
mfem::Vector center(3);
|
||||
angularVelocity = 0.0;
|
||||
center = 0.0;
|
||||
return {angularVelocity, center};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Typed Equilibrium Coordinate Maps Preserve Every Uneven Block Without Scaling",
|
||||
"[preconditioning][equilibrium_coordinates][unit]"
|
||||
) {
|
||||
STATIC_CHECK(preconditioning::EquilibriumCoordinateComponentFor<GroupedComponent, Form>);
|
||||
STATIC_CHECK_FALSE(preconditioning::EquilibriumCoordinateComponentFor<IncompleteComponent, Form>);
|
||||
|
||||
const auto layout = makeUnevenLayout();
|
||||
preconditioning::EquilibriumPreconditionerCoordinateMap<Form, GroupedComponent> coordinates(layout);
|
||||
|
||||
REQUIRE(coordinates.EquilibriumStateSize() == 22);
|
||||
REQUIRE(coordinates.EquilibriumResidualSize() == 22);
|
||||
REQUIRE(coordinates.PreconditionerCorrectionSize() == 22);
|
||||
REQUIRE(coordinates.PreconditionerResidualSize() == 22);
|
||||
|
||||
const auto &correctionRanges = coordinates.GetCorrectionRanges();
|
||||
CHECK(correctionRanges[0] == (preconditioning::EquilibriumCoordinateRange{0, 0, 2}));
|
||||
CHECK(correctionRanges[1] == (preconditioning::EquilibriumCoordinateRange{2, 2, 3}));
|
||||
CHECK(correctionRanges[2] == (preconditioning::EquilibriumCoordinateRange{14, 5, 6}));
|
||||
CHECK(correctionRanges[3] == (preconditioning::EquilibriumCoordinateRange{5, 11, 4}));
|
||||
CHECK(correctionRanges[4] == (preconditioning::EquilibriumCoordinateRange{9, 15, 5}));
|
||||
CHECK(correctionRanges[5] == (preconditioning::EquilibriumCoordinateRange{20, 20, 1}));
|
||||
CHECK(correctionRanges[6] == (preconditioning::EquilibriumCoordinateRange{21, 21, 1}));
|
||||
|
||||
const auto &residualRanges = coordinates.GetResidualRanges();
|
||||
CHECK(residualRanges[0] == (preconditioning::EquilibriumCoordinateRange{9, 0, 2}));
|
||||
CHECK(residualRanges[1] == (preconditioning::EquilibriumCoordinateRange{11, 2, 3}));
|
||||
CHECK(residualRanges[2] == (preconditioning::EquilibriumCoordinateRange{14, 5, 6}));
|
||||
CHECK(residualRanges[3] == (preconditioning::EquilibriumCoordinateRange{0, 11, 4}));
|
||||
CHECK(residualRanges[4] == (preconditioning::EquilibriumCoordinateRange{4, 15, 5}));
|
||||
CHECK(residualRanges[5] == (preconditioning::EquilibriumCoordinateRange{20, 20, 1}));
|
||||
CHECK(residualRanges[6] == (preconditioning::EquilibriumCoordinateRange{21, 21, 1}));
|
||||
|
||||
mfem::Vector equilibriumCorrection(22);
|
||||
mfem::Vector equilibriumResidual(22);
|
||||
for (int index = 0; index < 22; ++index) {
|
||||
equilibriumCorrection(index) = 100.0 + static_cast<double>(index);
|
||||
equilibriumResidual(index) = -200.0 - static_cast<double>(index);
|
||||
}
|
||||
|
||||
mfem::Vector groupedCorrection(22);
|
||||
mfem::Vector groupedResidual(22);
|
||||
const double *const groupedCorrectionStorage = groupedCorrection.GetData();
|
||||
const double *const groupedResidualStorage = groupedResidual.GetData();
|
||||
coordinates.PackCorrection(equilibriumCorrection, groupedCorrection);
|
||||
coordinates.PackResidual(equilibriumResidual, groupedResidual);
|
||||
CHECK(groupedCorrection.GetData() == groupedCorrectionStorage);
|
||||
CHECK(groupedResidual.GetData() == groupedResidualStorage);
|
||||
CHECK(groupedCorrection(5) == equilibriumCorrection(14));
|
||||
CHECK(groupedCorrection(11) == equilibriumCorrection(5));
|
||||
CHECK(groupedResidual(0) == equilibriumResidual(9));
|
||||
CHECK(groupedResidual(11) == equilibriumResidual(0));
|
||||
|
||||
mfem::Vector recoveredCorrection(22);
|
||||
mfem::Vector recoveredResidual(22);
|
||||
coordinates.UnpackCorrection(groupedCorrection, recoveredCorrection);
|
||||
coordinates.UnpackResidual(groupedResidual, recoveredResidual);
|
||||
CHECK(relativeError(recoveredCorrection, equilibriumCorrection) == 0.0);
|
||||
CHECK(relativeError(recoveredResidual, equilibriumResidual) == 0.0);
|
||||
|
||||
const auto &statistics = coordinates.GetStatistics();
|
||||
CHECK(statistics.correctionPacks == 1);
|
||||
CHECK(statistics.correctionUnpacks == 1);
|
||||
CHECK(statistics.residualPacks == 1);
|
||||
CHECK(statistics.residualUnpacks == 1);
|
||||
|
||||
mfem::Vector wrongSize(21);
|
||||
CHECK_THROWS_AS(coordinates.PackResidual(wrongSize, groupedResidual), std::invalid_argument);
|
||||
CHECK_THROWS_AS(coordinates.UnpackCorrection(wrongSize, recoveredCorrection), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Stellar Preconditioning Matches An Explicit Canonical Coordinate Transformation",
|
||||
"[preconditioning][equilibrium_coordinates][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
const utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
constexpr double radius = utils::RADIUS;
|
||||
constexpr double mass = utils::MASS;
|
||||
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||
auto model = model::StellarModel(
|
||||
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(model, finiteElements);
|
||||
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||
|
||||
auto component = preconditioning::makePreconditioner(problem);
|
||||
auto prepared = preconditioning::prepare(problem, component);
|
||||
prepared.SetOperator(problem.GetLinearizationOperator());
|
||||
|
||||
mfem::Vector equilibriumResidual(problem.EquationSize());
|
||||
for (int index = 0; index < equilibriumResidual.Size(); ++index) {
|
||||
equilibriumResidual(index) = 0.25 * std::cos(0.19 * static_cast<double>(index + 1));
|
||||
}
|
||||
|
||||
mfem::Vector groupedResidual(problem.EquationSize());
|
||||
mfem::Vector groupedCorrection(problem.StateSize());
|
||||
mfem::Vector expected(problem.StateSize());
|
||||
prepared.GetCoordinateMap().PackResidual(equilibriumResidual, groupedResidual);
|
||||
prepared.GetGroupedPreconditioner().Mult(groupedResidual, groupedCorrection);
|
||||
prepared.GetCoordinateMap().UnpackCorrection(groupedCorrection, expected);
|
||||
|
||||
mfem::Vector actual(problem.StateSize());
|
||||
const double *const actionStorage = actual.GetData();
|
||||
prepared.Mult(equilibriumResidual, actual);
|
||||
CHECK(actual.GetData() == actionStorage);
|
||||
CHECK(relativeError(actual, expected) <= 2.0e-12);
|
||||
|
||||
const auto &statistics = prepared.GetStatistics();
|
||||
CHECK(statistics.applications == 1);
|
||||
CHECK(statistics.residualCoordinateMappings == 1);
|
||||
CHECK(statistics.correctionCoordinateMappings == 1);
|
||||
CHECK(prepared.GetCoordinateMap().GetStatistics().residualPacks == 2);
|
||||
CHECK(prepared.GetCoordinateMap().GetStatistics().correctionUnpacks == 2);
|
||||
|
||||
const auto unchanged = prepared.Refresh();
|
||||
CHECK_FALSE(unchanged.DidAnyWork());
|
||||
CHECK(prepared.IsCurrent());
|
||||
}
|
||||
393
tests/preconditioning/gravity_field.cpp
Normal file
393
tests/preconditioning/gravity_field.cpp
Normal file
@@ -0,0 +1,393 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace backend = mean_field::preconditioning::backend;
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace gravity_context = mean_field::operators::context::gravity_field;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
|
||||
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
||||
using AdaptiveAMG = backend::HypreBoomerAMG<backend::SolveToTolerance>;
|
||||
using FixedGravityLDU =
|
||||
preconditioning::GravityFieldBlock<backend::Diagonal, FixedAMG, preconditioning::GravityApproximateLDU>;
|
||||
using ChebyshevGravityLDU = preconditioning::
|
||||
GravityFieldBlock<backend::MatrixFreeChebyshev, FixedAMG, preconditioning::GravityApproximateLDU>;
|
||||
using AdaptiveGravityLDU =
|
||||
preconditioning::GravityFieldBlock<backend::Diagonal, AdaptiveAMG, preconditioning::GravityApproximateLDU>;
|
||||
|
||||
using DensityIdentity =
|
||||
preconditioning::IdentityBlock<blocks::density::mass::value, blocks::density::mass::residual>;
|
||||
using SurfaceIdentity = preconditioning::IdentityBlock<
|
||||
blocks::surface_deformation::parameters::value,
|
||||
blocks::surface_deformation::shape_equilibrium::residual>;
|
||||
using EnthalpyIdentity =
|
||||
preconditioning::IdentityBlock<blocks::enthalpy::specific::value, blocks::enthalpy::specific::residual>;
|
||||
using MassIdentity = preconditioning::IdentityBlock<
|
||||
blocks::fixed_total_mass::mass_normalization::value,
|
||||
blocks::fixed_total_mass::mass_normalization::residual>;
|
||||
using FixedGravityPlan = preconditioning::
|
||||
PreconditionerPlan<DensityIdentity, SurfaceIdentity, FixedGravityLDU, EnthalpyIdentity, MassIdentity>;
|
||||
using AdaptiveGravityPlan = preconditioning::
|
||||
PreconditionerPlan<DensityIdentity, SurfaceIdentity, AdaptiveGravityLDU, EnthalpyIdentity, MassIdentity>;
|
||||
|
||||
template <typename Policy>
|
||||
mfem::Vector applyKnownFactorization(
|
||||
Policy policy,
|
||||
const mfem::Vector &rightHandSide,
|
||||
preconditioning::GravityFactorizationStatistics *statistics = nullptr
|
||||
) {
|
||||
mfem::Vector massDiagonal(2);
|
||||
massDiagonal = 1.0;
|
||||
auto massInverse = backend::prepare(backend::Diagonal{}, massDiagonal);
|
||||
|
||||
mfem::DenseMatrix schurMatrix(1);
|
||||
schurMatrix(0, 0) = 5.0;
|
||||
auto schurInverse = backend::prepare(backend::DenseDirect{}, schurMatrix);
|
||||
|
||||
mfem::DenseMatrix divergence(1, 2);
|
||||
divergence(0, 0) = 2.0;
|
||||
divergence(0, 1) = -1.0;
|
||||
|
||||
preconditioning::GravityFactorizationOperator<Policy> factorization(
|
||||
policy, massInverse, schurInverse, divergence
|
||||
);
|
||||
mfem::Vector action(factorization.Height());
|
||||
action = std::numeric_limits<double>::quiet_NaN();
|
||||
factorization.Mult(rightHandSide, action);
|
||||
if (statistics != nullptr) {
|
||||
*statistics = factorization.GetStatistics();
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
void checkVector(
|
||||
const mfem::Vector &computed,
|
||||
const std::array<
|
||||
double,
|
||||
3> &expected
|
||||
) {
|
||||
REQUIRE(computed.Size() == static_cast<int>(expected.size()));
|
||||
for (int index = 0; index < computed.Size(); ++index) {
|
||||
CHECK(computed(index) == Catch::Approx(expected[static_cast<std::size_t>(index)]).margin(2.0e-14));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Policy> void checkExactDenseRecovery(Policy policy) {
|
||||
mfem::DenseMatrix mass(2);
|
||||
mass(0, 0) = 2.0;
|
||||
mass(0, 1) = 0.5;
|
||||
mass(1, 0) = 0.5;
|
||||
mass(1, 1) = 1.5;
|
||||
auto massInverse = backend::prepare(backend::DenseDirect{}, mass);
|
||||
|
||||
mfem::DenseMatrix divergence(1, 2);
|
||||
divergence(0, 0) = 1.0;
|
||||
divergence(0, 1) = -2.0;
|
||||
|
||||
mfem::Vector divergenceTranspose(2);
|
||||
divergenceTranspose(0) = 1.0;
|
||||
divergenceTranspose(1) = -2.0;
|
||||
mfem::Vector massInverseDivergenceTranspose(2);
|
||||
massInverse.Mult(divergenceTranspose, massInverseDivergenceTranspose);
|
||||
|
||||
mfem::DenseMatrix schur(1);
|
||||
schur(0, 0) = divergenceTranspose * massInverseDivergenceTranspose;
|
||||
auto schurInverse = backend::prepare(backend::DenseDirect{}, schur);
|
||||
|
||||
preconditioning::GravityFactorizationOperator<Policy> factorization(
|
||||
policy, massInverse, schurInverse, divergence
|
||||
);
|
||||
|
||||
mfem::Vector exact(3);
|
||||
exact(0) = 0.7;
|
||||
exact(1) = -1.2;
|
||||
exact(2) = 0.4;
|
||||
|
||||
mfem::Vector rightHandSide(3);
|
||||
mfem::Vector exactGradient(exact.GetData(), 2);
|
||||
mfem::Vector gradientRightHandSide(rightHandSide.GetData(), 2);
|
||||
mass.Mult(exactGradient, gradientRightHandSide);
|
||||
gradientRightHandSide(0) += divergence(0, 0) * exact(2);
|
||||
gradientRightHandSide(1) += divergence(0, 1) * exact(2);
|
||||
rightHandSide(2) = divergence(0, 0) * exact(0) + divergence(0, 1) * exact(1);
|
||||
|
||||
mfem::Vector action(3);
|
||||
action = 0.0;
|
||||
const double *const actionStorage = action.GetData();
|
||||
factorization.Mult(rightHandSide, action);
|
||||
|
||||
CHECK(action.GetData() == actionStorage);
|
||||
for (int index = 0; index < action.Size(); ++index) {
|
||||
CHECK(action(index) == Catch::Approx(exact(index)).margin(2.0e-13));
|
||||
}
|
||||
|
||||
mfem::Vector repeated(3);
|
||||
repeated = 0.0;
|
||||
factorization.Mult(rightHandSide, repeated);
|
||||
for (int index = 0; index < repeated.Size(); ++index) {
|
||||
CHECK(repeated(index) == action(index));
|
||||
}
|
||||
}
|
||||
|
||||
struct PreparedGeometry final {
|
||||
mean_field::fem::FEM finiteElements;
|
||||
gravity_context::GravityFieldGeometryContext context;
|
||||
|
||||
explicit PreparedGeometry(const mean_field::utils::Args &arguments)
|
||||
: finiteElements(
|
||||
mean_field::fem::setup_fem(
|
||||
arguments.mesh_file,
|
||||
arguments,
|
||||
0
|
||||
)
|
||||
),
|
||||
context(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
) {
|
||||
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
|
||||
displacementTrue = 0.0;
|
||||
const mfem::Vector displacement = context.GetDisplacementMap().gather(displacementTrue);
|
||||
context.PreparePrimal(displacement, {.value = 1}, {.value = 1});
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Field Blocks Expose Complete Compile-Time Ownership And Backend Contracts",
|
||||
tags::preconditioning_gravity_unit
|
||||
) {
|
||||
using Form = blocks::surface_deformed_stellar_equilibrium_form;
|
||||
using JacobianForm = blocks::surface_deformed_stellar_equilibrium_jacobian_form;
|
||||
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<FixedGravityLDU>);
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<ChebyshevGravityLDU>);
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<AdaptiveGravityLDU>);
|
||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename FixedGravityLDU::BackendType>);
|
||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename ChebyshevGravityLDU::BackendType>);
|
||||
STATIC_CHECK_FALSE(preconditioning::backend::ArnoldiAdmissible<typename AdaptiveGravityLDU::BackendType>);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<FixedGravityPlan, Form>);
|
||||
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<FixedGravityPlan, Form, JacobianForm>);
|
||||
STATIC_CHECK(preconditioning::StationaryLinearPreconditionerPlan<FixedGravityPlan>);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<AdaptiveGravityPlan, Form>);
|
||||
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<AdaptiveGravityPlan, Form, JacobianForm>);
|
||||
STATIC_CHECK_FALSE(preconditioning::StationaryLinearPreconditionerPlan<AdaptiveGravityPlan>);
|
||||
STATIC_CHECK(FixedGravityLDU::RequiredCouplings::size == 3);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Factorization Policies Preserve Their Signed Block Algebra",
|
||||
tags::preconditioning_gravity_unit
|
||||
) {
|
||||
mfem::Vector rightHandSide(3);
|
||||
rightHandSide(0) = 3.0;
|
||||
rightHandSide(1) = 4.0;
|
||||
rightHandSide(2) = 7.0;
|
||||
|
||||
checkVector(applyKnownFactorization(preconditioning::GravityBlockDiagonal{}, rightHandSide), {3.0, 4.0, 1.4});
|
||||
checkVector(applyKnownFactorization(preconditioning::GravityLowerTriangular{}, rightHandSide), {3.0, 4.0, -1.0});
|
||||
checkVector(applyKnownFactorization(preconditioning::GravityUpperTriangular{}, rightHandSide), {5.8, 2.6, -1.4});
|
||||
|
||||
preconditioning::GravityFactorizationStatistics statistics;
|
||||
checkVector(
|
||||
applyKnownFactorization(preconditioning::GravityApproximateLDU{}, rightHandSide, &statistics), {5.0, 3.0, -1.0}
|
||||
);
|
||||
CHECK(statistics.applications == 1);
|
||||
CHECK(statistics.massInverseApplications == 2);
|
||||
CHECK(statistics.potentialSchurApplications == 1);
|
||||
CHECK(statistics.divergenceApplications == 1);
|
||||
CHECK(statistics.transposeDivergenceApplications == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Exact Gravity LDU Recovers A Dense Coupled Saddle-Point System",
|
||||
tags::preconditioning_gravity_unit
|
||||
) {
|
||||
checkExactDenseRecovery(preconditioning::GravityApproximateLDU{});
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Assembled Gravity Divergence Matches The Prepared Matrix-Free Couplings",
|
||||
tags::preconditioning_gravity_integration
|
||||
) {
|
||||
const auto arguments = test_utils::setup_args();
|
||||
PreparedGeometry geometry(arguments);
|
||||
|
||||
const auto assembledDivergence = preconditioning::assembleGravityDivergenceSurrogate(geometry.finiteElements);
|
||||
const mfem::Operator &preparedDivergence = geometry.context.GetDivergenceOperator();
|
||||
|
||||
const mfem::Vector flux = gravity_prepared_test_utils::make_deterministic_vector(
|
||||
geometry.finiteElements.gravityFluxFes->GetTrueVSize(), 0.31
|
||||
);
|
||||
mfem::Vector assembledForward(assembledDivergence->Height());
|
||||
mfem::Vector preparedForward(preparedDivergence.Height());
|
||||
assembledDivergence->Mult(flux, assembledForward);
|
||||
preparedDivergence.Mult(flux, preparedForward);
|
||||
|
||||
const mfem::Vector potential = gravity_prepared_test_utils::make_deterministic_vector(
|
||||
geometry.finiteElements.gravityPotentialFes->GetTrueVSize(), 0.73
|
||||
);
|
||||
mfem::Vector assembledTranspose(assembledDivergence->Width());
|
||||
mfem::Vector preparedTranspose(preparedDivergence.Width());
|
||||
assembledDivergence->MultTranspose(potential, assembledTranspose);
|
||||
preparedDivergence.MultTranspose(potential, preparedTranspose);
|
||||
|
||||
const MPI_Comm communicator = geometry.finiteElements.mesh->GetComm();
|
||||
CHECK(gravity_prepared_test_utils::relative_error(assembledForward, preparedForward, communicator) <= 2.0e-12);
|
||||
CHECK(gravity_prepared_test_utils::relative_error(assembledTranspose, preparedTranspose, communicator) <= 2.0e-12);
|
||||
|
||||
const auto &gradientMap = geometry.context.GetMassOperator().GetFluxMap();
|
||||
const auto &potentialMap = geometry.context.GetSourceOperator().GetPotentialMap();
|
||||
preconditioning::ReducedGravityDivergenceOperator reducedDivergence(preparedDivergence, gradientMap, potentialMap);
|
||||
const mfem::Vector reducedFlux =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(gradientMap.reduced_size(), 0.47);
|
||||
mfem::Vector reducedAction(reducedDivergence.Height());
|
||||
reducedDivergence.Mult(reducedFlux, reducedAction);
|
||||
|
||||
const mfem::Vector trueFlux = gradientMap.scatter(reducedFlux);
|
||||
mfem::Vector trueAction(potentialMap.full_size());
|
||||
preparedDivergence.Mult(trueFlux, trueAction);
|
||||
const mfem::Vector expectedReducedAction = potentialMap.gather(trueAction);
|
||||
CHECK(gravity_prepared_test_utils::relative_error(reducedAction, expectedReducedAction, communicator) <= 2.0e-14);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Gravity Block Diagonal Is Legacy Equivalent And Allocation Stable",
|
||||
tags::preconditioning_gravity_integration
|
||||
) {
|
||||
const auto arguments = test_utils::setup_args();
|
||||
PreparedGeometry geometry(arguments);
|
||||
|
||||
mean_field::operators::ReducedGravityFieldPreconditioner legacy(geometry.finiteElements, geometry.context);
|
||||
const auto block = preconditioning::GravityFieldBlock(
|
||||
backend::Diagonal{}, FixedAMG{backend::FixedCycles{.cycles = 1}}, preconditioning::GravityBlockDiagonal{}
|
||||
);
|
||||
auto prepared = preconditioning::prepare(geometry.finiteElements, geometry.context, block);
|
||||
|
||||
const mfem::Vector rightHandSide = gravity_prepared_test_utils::make_deterministic_vector(prepared.Width(), 0.59);
|
||||
mfem::Vector legacyAction(prepared.Height());
|
||||
mfem::Vector preparedAction(prepared.Height());
|
||||
legacyAction = 0.0;
|
||||
preparedAction = 0.0;
|
||||
double *const preparedStorage = preparedAction.GetData();
|
||||
|
||||
const std::uint64_t massPreparations = geometry.context.GetMassOperator().GetPreparationCount();
|
||||
const std::uint64_t sourcePreparations = geometry.context.GetSourceOperator().GetPreparationCount();
|
||||
legacy.Mult(rightHandSide, legacyAction);
|
||||
prepared.Mult(rightHandSide, preparedAction);
|
||||
|
||||
CHECK(preparedAction.GetData() == preparedStorage);
|
||||
CHECK(geometry.context.GetMassOperator().GetPreparationCount() == massPreparations);
|
||||
CHECK(geometry.context.GetSourceOperator().GetPreparationCount() == sourcePreparations);
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, legacyAction, geometry.finiteElements.mesh->GetComm()
|
||||
) <= 2.0e-12
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Gravity Blocks Refresh Explicitly Without Repreparing Geometry",
|
||||
tags::preconditioning_gravity_integration
|
||||
) {
|
||||
const auto arguments = test_utils::setup_args();
|
||||
PreparedGeometry geometry(arguments);
|
||||
|
||||
const auto block = preconditioning::GravityFieldBlock(
|
||||
backend::Diagonal{}, FixedAMG{backend::FixedCycles{.cycles = 1}}, preconditioning::GravityApproximateLDU{}
|
||||
);
|
||||
auto prepared = preconditioning::prepare(geometry.finiteElements, geometry.context, block);
|
||||
const auto chebyshevBlock = preconditioning::GravityFieldBlock(
|
||||
backend::MatrixFreeChebyshev{.order = 2, .powerIterations = 10}, FixedAMG{backend::FixedCycles{.cycles = 1}},
|
||||
preconditioning::GravityApproximateLDU{}
|
||||
);
|
||||
auto chebyshevPrepared = preconditioning::prepare(geometry.finiteElements, geometry.context, chebyshevBlock);
|
||||
|
||||
const auto unchanged = prepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
const auto unchangedChebyshev = chebyshevPrepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
CHECK_FALSE(unchanged.DidAnyWork());
|
||||
CHECK_FALSE(unchangedChebyshev.DidAnyWork());
|
||||
CHECK(prepared.GetStatistics().refreshChecks == 1);
|
||||
CHECK(prepared.GetStatistics().noOpRefreshes == 1);
|
||||
|
||||
const mfem::Vector displacementTrue = gravity_prepared_test_utils::make_displacement(geometry.finiteElements, 0.4);
|
||||
const mfem::Vector displacement = geometry.context.GetDisplacementMap().gather(displacementTrue);
|
||||
geometry.context.PreparePrimal(displacement, {.value = 1}, {.value = 2});
|
||||
CHECK_FALSE(prepared.IsCurrent());
|
||||
CHECK_FALSE(chebyshevPrepared.IsCurrent());
|
||||
|
||||
mfem::Vector rightHandSide(prepared.Width());
|
||||
mfem::Vector action(prepared.Height());
|
||||
rightHandSide = 1.0;
|
||||
action = 0.0;
|
||||
CHECK_THROWS_AS(prepared.Mult(rightHandSide, action), std::logic_error);
|
||||
CHECK_THROWS_AS(chebyshevPrepared.Mult(rightHandSide, action), std::logic_error);
|
||||
|
||||
const std::uint64_t massPreparations = geometry.context.GetMassOperator().GetPreparationCount();
|
||||
const std::uint64_t sourcePreparations = geometry.context.GetSourceOperator().GetPreparationCount();
|
||||
const auto changed = prepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
const auto changedChebyshev = chebyshevPrepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
|
||||
CHECK(changed.geometryChanged);
|
||||
CHECK_FALSE(changed.discretizationChanged);
|
||||
CHECK(changed.rebuiltMassInverse);
|
||||
CHECK_FALSE(changed.rebuiltDivergenceBinding);
|
||||
CHECK(changed.rebuiltPotentialSchur);
|
||||
CHECK(prepared.IsCurrent());
|
||||
CHECK(changedChebyshev.geometryChanged);
|
||||
CHECK(changedChebyshev.rebuiltMassInverse);
|
||||
CHECK(changedChebyshev.rebuiltPotentialSchur);
|
||||
CHECK(chebyshevPrepared.IsCurrent());
|
||||
CHECK(chebyshevPrepared.GetMassInverse().GetStatistics().setups == 2);
|
||||
CHECK(geometry.context.GetMassOperator().GetPreparationCount() == massPreparations);
|
||||
CHECK(geometry.context.GetSourceOperator().GetPreparationCount() == sourcePreparations);
|
||||
CHECK(prepared.GetStatistics().refreshes == 1);
|
||||
|
||||
mfem::Vector refreshedAction(chebyshevPrepared.Height());
|
||||
refreshedAction = 0.0;
|
||||
chebyshevPrepared.Mult(rightHandSide, refreshedAction);
|
||||
for (int index = 0; index < refreshedAction.Size(); ++index) {
|
||||
CHECK(std::isfinite(refreshedAction(index)));
|
||||
}
|
||||
|
||||
// A discretization revision reconstructs the matrix-free mass operator. The
|
||||
// owning gravity block must reject every route to its now-stale inverse until
|
||||
// refresh has rebound and rebuilt the Chebyshev smoother.
|
||||
geometry.context.PreparePrimal(displacement, {.value = 2}, {.value = 2});
|
||||
CHECK_FALSE(chebyshevPrepared.IsCurrent());
|
||||
CHECK_THROWS_AS(chebyshevPrepared.Mult(rightHandSide, action), std::logic_error);
|
||||
CHECK_THROWS_AS(chebyshevPrepared.GetMassInverse(), std::logic_error);
|
||||
|
||||
const auto reconstructed = chebyshevPrepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
CHECK(reconstructed.discretizationChanged);
|
||||
CHECK_FALSE(reconstructed.geometryChanged);
|
||||
CHECK(reconstructed.rebuiltMassInverse);
|
||||
CHECK(reconstructed.rebuiltDivergenceBinding);
|
||||
CHECK(reconstructed.rebuiltPotentialSchur);
|
||||
CHECK(chebyshevPrepared.IsCurrent());
|
||||
CHECK(chebyshevPrepared.GetMassInverse().GetStatistics().setups == 3);
|
||||
|
||||
mfem::Vector firstReconstructedAction(chebyshevPrepared.Height());
|
||||
mfem::Vector secondReconstructedAction(chebyshevPrepared.Height());
|
||||
firstReconstructedAction = 0.0;
|
||||
secondReconstructedAction = 0.0;
|
||||
chebyshevPrepared.Mult(rightHandSide, firstReconstructedAction);
|
||||
chebyshevPrepared.Mult(rightHandSide, secondReconstructedAction);
|
||||
mfem::Vector repeatabilityError(firstReconstructedAction);
|
||||
repeatabilityError -= secondReconstructedAction;
|
||||
CHECK(repeatabilityError.Norml2() <= 2.0e-14 * std::max(1.0, firstReconstructedAction.Norml2()));
|
||||
for (int index = 0; index < firstReconstructedAction.Size(); ++index) {
|
||||
CHECK(std::isfinite(firstReconstructedAction(index)));
|
||||
}
|
||||
}
|
||||
614
tests/preconditioning/material_surface.cpp
Normal file
614
tests/preconditioning/material_surface.cpp
Normal file
@@ -0,0 +1,614 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace backend = mean_field::preconditioning::backend;
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
|
||||
using PolytropicModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::integral::FixedTotalMass,
|
||||
mean_field::constraint::FixedCentralDensity>>;
|
||||
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
||||
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
||||
using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock<
|
||||
PolytropicMaterialSurfaceDescriptor,
|
||||
backend::Diagonal,
|
||||
backend::Diagonal,
|
||||
preconditioning::SurfaceThenMaterialTriangular>;
|
||||
using FixedCycleAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
||||
using MaterialSurfaceH1 = preconditioning::MaterialSurfaceBlock<
|
||||
PolytropicMaterialSurfaceDescriptor,
|
||||
backend::Diagonal,
|
||||
FixedCycleAMG,
|
||||
preconditioning::ApproximateMaterialSurfaceLDU,
|
||||
preconditioning::SurfaceH1MassStiffness>;
|
||||
using PreparedMaterialSurfaceDiagonal = preconditioning::PreparedMaterialSurfaceBlock<
|
||||
PolytropicMaterialSurfaceDescriptor,
|
||||
preconditioning::SurfaceThenMaterialTriangular>;
|
||||
using PreparedMaterialSurfaceH1 = preconditioning::PreparedH1MaterialSurfaceBlock<
|
||||
PolytropicMaterialSurfaceDescriptor,
|
||||
preconditioning::ApproximateMaterialSurfaceLDU,
|
||||
backend::FixedCycles>;
|
||||
|
||||
class KnownCouplings final {
|
||||
public:
|
||||
KnownCouplings() : m_offsets(4) {
|
||||
m_offsets[0] = 0;
|
||||
m_offsets[1] = 1;
|
||||
m_offsets[2] = 2;
|
||||
m_offsets[3] = 3;
|
||||
}
|
||||
|
||||
[[nodiscard]] int Height() const noexcept {
|
||||
return 3;
|
||||
}
|
||||
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept {
|
||||
return m_offsets;
|
||||
}
|
||||
|
||||
void ApplyEnthalpyToDensity(
|
||||
const mfem::Vector &enthalpy,
|
||||
mfem::Vector &density
|
||||
) const {
|
||||
density(0) = 4.0 * enthalpy(0);
|
||||
}
|
||||
void ApplySurfaceToMaterial(
|
||||
const mfem::Vector &surface,
|
||||
mfem::Vector &density,
|
||||
mfem::Vector &enthalpy
|
||||
) const {
|
||||
density(0) = 3.0 * surface(0);
|
||||
enthalpy(0) = 8.0 * surface(0);
|
||||
}
|
||||
void ApplyMaterialToSurface(
|
||||
const mfem::Vector &density,
|
||||
const mfem::Vector &enthalpy,
|
||||
mfem::Vector &surface
|
||||
) const {
|
||||
surface(0) = 5.0 * density(0) + 7.0 * enthalpy(0);
|
||||
}
|
||||
|
||||
private:
|
||||
mfem::Array<int> m_offsets;
|
||||
};
|
||||
|
||||
template <typename Policy>
|
||||
[[nodiscard]] mfem::Vector applyKnownFactorization(
|
||||
Policy policy,
|
||||
const mfem::Vector &rightHandSide,
|
||||
const double surfaceEntry = 6.0
|
||||
) {
|
||||
mfem::Vector densityDiagonal(1);
|
||||
mfem::Vector surfaceDiagonal(1);
|
||||
mfem::Vector enthalpyDiagonal(1);
|
||||
densityDiagonal(0) = 2.0;
|
||||
surfaceDiagonal(0) = surfaceEntry;
|
||||
enthalpyDiagonal(0) = 9.0;
|
||||
const auto densityInverse = backend::prepare(backend::Diagonal{}, densityDiagonal);
|
||||
const auto surfaceInverse = backend::prepare(backend::Diagonal{}, surfaceDiagonal);
|
||||
const auto enthalpyInverse = backend::prepare(backend::Diagonal{}, enthalpyDiagonal);
|
||||
const KnownCouplings couplings;
|
||||
preconditioning::MaterialSurfaceFactorizationOperator<Policy, KnownCouplings> factorization(
|
||||
policy, densityInverse, surfaceInverse, enthalpyInverse, couplings
|
||||
);
|
||||
mfem::Vector action(3);
|
||||
factorization.Mult(rightHandSide, action);
|
||||
return action;
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies makeDependencies(std::uint64_t revision = 1) {
|
||||
return {
|
||||
.discretization = {.identity = 8101, .revision = 1},
|
||||
.density = {.identity = 8103, .revision = revision},
|
||||
.surfaceDeformation = {.identity = 8107, .revision = revision},
|
||||
.gravityGradient = {.identity = 8111, .revision = revision},
|
||||
.gravityPotential = {.identity = 8117, .revision = revision},
|
||||
.enthalpy = {.identity = 8123, .revision = revision},
|
||||
.bernoulliConstant = {.identity = 8129, .revision = revision},
|
||||
.rotation = {.identity = 8131, .revision = revision},
|
||||
.targetMass = {.identity = 8137, .revision = 1}
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::physics::RigidRotation zeroRotation() {
|
||||
mfem::Vector angularVelocity(3);
|
||||
mfem::Vector center(3);
|
||||
angularVelocity = 0.0;
|
||||
center = 0.0;
|
||||
return {angularVelocity, center};
|
||||
}
|
||||
|
||||
[[nodiscard]] double relativeError(
|
||||
const mfem::Vector &left,
|
||||
const mfem::Vector &right
|
||||
) {
|
||||
mfem::Vector difference(left);
|
||||
difference -= right;
|
||||
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Compiled Material Surface Blocks Derive Their Physical Ownership And Backend Requirements",
|
||||
"[preconditioning][material_surface][unit][type_contract]"
|
||||
) {
|
||||
using Form = blocks::surface_deformed_stellar_equilibrium_form;
|
||||
using JacobianForm = blocks::surface_deformed_stellar_equilibrium_jacobian_form;
|
||||
using GravityIdentity =
|
||||
preconditioning::IdentityBlock<blocks::gravity::gradient::value, blocks::gravity::gradient::residual>;
|
||||
using PotentialIdentity =
|
||||
preconditioning::IdentityBlock<blocks::gravity::poisson::value, blocks::gravity::poisson::residual>;
|
||||
using MassIdentity = preconditioning::IdentityBlock<
|
||||
blocks::fixed_total_mass::mass_normalization::value, blocks::fixed_total_mass::mass_normalization::residual>;
|
||||
using Plan =
|
||||
preconditioning::PreconditionerPlan<MaterialSurfaceDiagonal, GravityIdentity, PotentialIdentity, MassIdentity>;
|
||||
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceDiagonal>);
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceH1>);
|
||||
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
|
||||
STATIC_CHECK(
|
||||
mean_field::material::CompiledThermodynamicEquations<typename PolytropicProblem::ThermodynamicEquationsType>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename PolytropicMaterialSurfaceDescriptor::SurfaceStateFields,
|
||||
mean_field::field::TypeList<mean_field::field::Enthalpy>>
|
||||
);
|
||||
STATIC_CHECK(MaterialSurfaceDiagonal::CorrectionBlocks::size == 3);
|
||||
STATIC_CHECK(MaterialSurfaceDiagonal::ResidualBlocks::size == 3);
|
||||
STATIC_CHECK(MaterialSurfaceDiagonal::RequiredCouplings::size == 8);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<Plan, Form>);
|
||||
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<Plan, Form, JacobianForm>);
|
||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename MaterialSurfaceDiagonal::BackendType>);
|
||||
STATIC_CHECK_FALSE(std::is_copy_constructible_v<PreparedMaterialSurfaceDiagonal>);
|
||||
STATIC_CHECK_FALSE(std::is_copy_assignable_v<PreparedMaterialSurfaceDiagonal>);
|
||||
STATIC_CHECK_FALSE(std::is_move_constructible_v<PreparedMaterialSurfaceDiagonal>);
|
||||
STATIC_CHECK_FALSE(std::is_move_assignable_v<PreparedMaterialSurfaceDiagonal>);
|
||||
STATIC_CHECK_FALSE(std::is_copy_constructible_v<PreparedMaterialSurfaceH1>);
|
||||
STATIC_CHECK_FALSE(std::is_copy_assignable_v<PreparedMaterialSurfaceH1>);
|
||||
STATIC_CHECK_FALSE(std::is_move_constructible_v<PreparedMaterialSurfaceH1>);
|
||||
STATIC_CHECK_FALSE(std::is_move_assignable_v<PreparedMaterialSurfaceH1>);
|
||||
STATIC_CHECK(
|
||||
preconditioning::backend::Compatible<backend::Diagonal, preconditioning::SurfaceDiagonalCharacteristics>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
preconditioning::backend::Compatible<backend::DenseDirect, preconditioning::SurfaceDiagonalCharacteristics>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
preconditioning::backend::Compatible<FixedCycleAMG, preconditioning::SurfaceH1MassStiffnessCharacteristics>
|
||||
);
|
||||
STATIC_CHECK_FALSE(std::same_as<MaterialSurfaceDiagonal, MaterialSurfaceH1>);
|
||||
STATIC_CHECK(std::same_as<typename MaterialSurfaceH1::SurfaceSurrogate, preconditioning::SurfaceH1MassStiffness>);
|
||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename MaterialSurfaceH1::BackendType>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Surface H1 Calibration Recovers Signed Nonnegative Mass And Stiffness Fits",
|
||||
"[preconditioning][material_surface][surface_h1][unit]"
|
||||
) {
|
||||
const preconditioning::SurfaceH1MassStiffness configuration{
|
||||
.calibration =
|
||||
{.target = preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur, .probeCount = 4},
|
||||
.relativeMassCoefficientFloor = 1.0e-12,
|
||||
.gramRelativeTolerance = 1.0e-12
|
||||
};
|
||||
const preconditioning::SurfaceH1NormalEquations exactPositive{
|
||||
.massMass = 2.0,
|
||||
.massStiffness = 2.0,
|
||||
.stiffnessStiffness = 5.0,
|
||||
.massTarget = 10.0,
|
||||
.stiffnessTarget = 19.0,
|
||||
.targetTarget = 77.0
|
||||
};
|
||||
|
||||
const auto positive = preconditioning::detail::fitSurfaceH1Coefficients(exactPositive, configuration);
|
||||
CHECK(positive.WasCalibrated());
|
||||
CHECK(positive.sign == 1.0);
|
||||
CHECK(positive.massCoefficient == Catch::Approx(2.0).margin(2.0e-13));
|
||||
CHECK(positive.stiffnessCoefficient == Catch::Approx(3.0).margin(2.0e-13));
|
||||
CHECK(positive.relativeResidual == Catch::Approx(0.0).margin(2.0e-13));
|
||||
CHECK(positive.relativeGramDeterminant > configuration.gramRelativeTolerance);
|
||||
CHECK(positive.normalEquations.targetTarget == Catch::Approx(77.0));
|
||||
|
||||
auto exactNegative = exactPositive;
|
||||
exactNegative.massTarget = -exactNegative.massTarget;
|
||||
exactNegative.stiffnessTarget = -exactNegative.stiffnessTarget;
|
||||
const auto negative = preconditioning::detail::fitSurfaceH1Coefficients(exactNegative, configuration);
|
||||
CHECK(negative.sign == -1.0);
|
||||
CHECK(negative.massCoefficient == Catch::Approx(2.0).margin(2.0e-13));
|
||||
CHECK(negative.stiffnessCoefficient == Catch::Approx(3.0).margin(2.0e-13));
|
||||
CHECK(negative.relativeResidual == Catch::Approx(0.0).margin(2.0e-13));
|
||||
|
||||
const preconditioning::SurfaceH1NormalEquations massDominated{
|
||||
.massMass = 1.0,
|
||||
.massStiffness = 0.0,
|
||||
.stiffnessStiffness = 1.0,
|
||||
.massTarget = 4.0,
|
||||
.stiffnessTarget = -2.0,
|
||||
.targetTarget = 20.0
|
||||
};
|
||||
const auto constrained = preconditioning::detail::fitSurfaceH1Coefficients(massDominated, configuration);
|
||||
CHECK(constrained.sign == 1.0);
|
||||
CHECK(constrained.massCoefficient == Catch::Approx(4.0).margin(2.0e-13));
|
||||
CHECK(constrained.stiffnessCoefficient == Catch::Approx(0.0).margin(2.0e-13));
|
||||
CHECK(constrained.relativeResidual == Catch::Approx(std::sqrt(0.2)).margin(2.0e-13));
|
||||
|
||||
auto rankDeficient = exactPositive;
|
||||
rankDeficient.massMass = 1.0;
|
||||
rankDeficient.massStiffness = 2.0;
|
||||
rankDeficient.stiffnessStiffness = 4.0;
|
||||
CHECK_THROWS_AS(
|
||||
preconditioning::detail::fitSurfaceH1Coefficients(rankDeficient, configuration), std::runtime_error
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Surface Riesz Scalar Calibration Distinguishes Operator And Right-Preconditioned Objectives",
|
||||
"[preconditioning][material_surface][surface_riesz][unit]"
|
||||
) {
|
||||
using Objective = preconditioning::SurfaceRieszCalibrationObjective;
|
||||
|
||||
const auto operatorFit = preconditioning::detail::fitSurfaceRieszScalar(6.0, 2.0, Objective::operator_action);
|
||||
CHECK(operatorFit.surrogateScale == Catch::Approx(3.0));
|
||||
CHECK(operatorFit.inverseMultiplier == Catch::Approx(1.0 / 3.0));
|
||||
|
||||
const auto inverseFit =
|
||||
preconditioning::detail::fitSurfaceRieszScalar(6.0, 2.0, Objective::right_preconditioned_action);
|
||||
CHECK(inverseFit.surrogateScale == Catch::Approx(1.0 / 3.0));
|
||||
CHECK(inverseFit.inverseMultiplier == Catch::Approx(3.0));
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
preconditioning::detail::fitSurfaceRieszScalar(1.0, 0.0, Objective::operator_action), std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
preconditioning::detail::fitSurfaceRieszScalar(0.0, 1.0, Objective::right_preconditioned_action),
|
||||
std::runtime_error
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Signed Surface Solver Adapts Boundary Coordinates Without Exposing An Indefinite Backend",
|
||||
"[preconditioning][material_surface][surface_h1][unit]"
|
||||
) {
|
||||
mfem::DenseMatrix ambientMatrix(3);
|
||||
ambientMatrix = 0.0;
|
||||
ambientMatrix(0, 0) = 2.0;
|
||||
ambientMatrix(1, 1) = 7.0;
|
||||
ambientMatrix(2, 2) = 4.0;
|
||||
const auto ambientInverse = backend::prepare(backend::DenseDirect{}, ambientMatrix);
|
||||
|
||||
mfem::Array<int> boundaryTrueDofs(2);
|
||||
boundaryTrueDofs[0] = 0;
|
||||
boundaryTrueDofs[1] = 2;
|
||||
mean_field::field::ScalarBoundaryDofMap surfaceMap(3, boundaryTrueDofs, 0, 2);
|
||||
preconditioning::SignedScalarBoundarySolverAdapter surfaceInverse(ambientInverse, surfaceMap, -1.0);
|
||||
|
||||
mfem::Vector rightHandSide(2);
|
||||
mfem::Vector action(2);
|
||||
rightHandSide(0) = 2.0;
|
||||
rightHandSide(1) = 4.0;
|
||||
surfaceInverse.Mult(rightHandSide, action);
|
||||
CHECK(action(0) == Catch::Approx(-1.0));
|
||||
CHECK(action(1) == Catch::Approx(-1.0));
|
||||
CHECK(surfaceInverse.GetSign() == -1.0);
|
||||
CHECK_THROWS_AS(surfaceInverse.SetSign(0.0), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Material Surface Factorization Policies Preserve Their Signed Triangular Algebra",
|
||||
"[preconditioning][material_surface][unit][factorization]"
|
||||
) {
|
||||
mfem::Vector rightHandSide(3);
|
||||
rightHandSide(0) = 29.0;
|
||||
rightHandSide(1) = 44.0;
|
||||
rightHandSide(2) = 43.0;
|
||||
|
||||
const auto check = [](const mfem::Vector &value, std::array<double, 3> expected) {
|
||||
for (int index = 0; index < value.Size(); ++index) {
|
||||
CHECK(value(index) == Catch::Approx(expected[static_cast<std::size_t>(index)]).margin(2.0e-13));
|
||||
}
|
||||
};
|
||||
|
||||
check(
|
||||
applyKnownFactorization(preconditioning::MaterialSurfaceBlockDiagonal{}, rightHandSide),
|
||||
{14.5, 44.0 / 6.0, 43.0 / 9.0}
|
||||
);
|
||||
check(
|
||||
applyKnownFactorization(preconditioning::CoupledMaterialIndependentSurface{}, rightHandSide),
|
||||
{(29.0 - 4.0 * (43.0 / 9.0)) / 2.0, 44.0 / 6.0, 43.0 / 9.0}
|
||||
);
|
||||
const double materialEnthalpy = 43.0 / 9.0;
|
||||
const double materialDensity = (29.0 - 4.0 * materialEnthalpy) / 2.0;
|
||||
check(
|
||||
applyKnownFactorization(preconditioning::MaterialThenSurfaceTriangular{}, rightHandSide),
|
||||
{materialDensity, (44.0 - 5.0 * materialDensity - 7.0 * materialEnthalpy) / 6.0, materialEnthalpy}
|
||||
);
|
||||
const double surfaceFirst = 44.0 / 6.0;
|
||||
const double surfaceCorrectedEnthalpy = (43.0 - 8.0 * surfaceFirst) / 9.0;
|
||||
check(
|
||||
applyKnownFactorization(preconditioning::SurfaceThenMaterialTriangular{}, rightHandSide),
|
||||
{(29.0 - 3.0 * surfaceFirst - 4.0 * surfaceCorrectedEnthalpy) / 2.0, surfaceFirst, surfaceCorrectedEnthalpy}
|
||||
);
|
||||
|
||||
const double firstMaterialEnthalpy = 43.0 / 9.0;
|
||||
const double firstMaterialDensity = (29.0 - 4.0 * firstMaterialEnthalpy) / 2.0;
|
||||
const double lduSurface = (44.0 - 5.0 * firstMaterialDensity - 7.0 * firstMaterialEnthalpy) / 6.0;
|
||||
const double lduEnthalpy = (43.0 - 8.0 * lduSurface) / 9.0;
|
||||
check(
|
||||
applyKnownFactorization(preconditioning::ApproximateMaterialSurfaceLDU{}, rightHandSide),
|
||||
{(29.0 - 3.0 * lduSurface - 4.0 * lduEnthalpy) / 2.0, lduSurface, lduEnthalpy}
|
||||
);
|
||||
|
||||
// M = [[2,4],[0,9]], B = [3,8]^T, C = [5,7], so the exact
|
||||
// scalar surface Schur complement is 6 - C M^{-1} B = 7/6.
|
||||
const mfem::Vector exact =
|
||||
applyKnownFactorization(preconditioning::ApproximateMaterialSurfaceLDU{}, rightHandSide, 7.0 / 6.0);
|
||||
CHECK(2.0 * exact(0) + 3.0 * exact(1) + 4.0 * exact(2) == Catch::Approx(29.0).margin(2.0e-12));
|
||||
CHECK(5.0 * exact(0) + 6.0 * exact(1) + 7.0 * exact(2) == Catch::Approx(44.0).margin(2.0e-12));
|
||||
CHECK(8.0 * exact(1) + 9.0 * exact(2) == Catch::Approx(43.0).margin(2.0e-12));
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Generated Material Surface Action Is The Exact Restricted Stellar Jacobian And Uses A Bounded Surrogate",
|
||||
"[preconditioning][material_surface][surface_h1][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
const utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
constexpr double radius = utils::RADIUS;
|
||||
constexpr double mass = utils::MASS;
|
||||
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||
const auto stellarModel = model::StellarModel(
|
||||
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(stellarModel, finiteElements);
|
||||
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
||||
const auto rotation = zeroRotation();
|
||||
problem.Prepare(projected.values, makeDependencies(), rotation);
|
||||
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
|
||||
|
||||
const auto block = preconditioning::materialSurfaceBlock(
|
||||
problem, backend::Diagonal{}, backend::Diagonal{}, preconditioning::SurfaceThenMaterialTriangular{}
|
||||
);
|
||||
const auto defaultBlock = preconditioning::materialSurfaceBlock(problem);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename std::remove_cvref_t<decltype(block)>::Descriptor,
|
||||
preconditioning::MaterialSurfaceDescriptorFor<decltype(problem)>>
|
||||
);
|
||||
STATIC_CHECK(std::same_as<std::remove_cvref_t<decltype(defaultBlock)>, std::remove_cvref_t<decltype(block)>>);
|
||||
auto prepared = preconditioning::prepare(problem, block);
|
||||
const auto &restricted = prepared.GetCoupledOperator();
|
||||
mfem::Vector restrictedDirection(restricted.Width());
|
||||
for (int index = 0; index < restrictedDirection.Size(); ++index) {
|
||||
restrictedDirection(index) = 0.01 * std::sin(0.37 * static_cast<double>(index + 1));
|
||||
}
|
||||
mfem::Vector restrictedAction(restricted.Height());
|
||||
restricted.Mult(restrictedDirection, restrictedAction);
|
||||
|
||||
mfem::Vector fullDirection(physical.Width());
|
||||
fullDirection = 0.0;
|
||||
const auto fullDirectionView = physical.GetRootManifest().directionView(fullDirection);
|
||||
const auto &offsets = restricted.GetOffsets();
|
||||
const mfem::Vector densityDirection(restrictedDirection.GetData(), offsets[1]);
|
||||
const mfem::Vector surfaceDirection(restrictedDirection.GetData() + offsets[1], offsets[2] - offsets[1]);
|
||||
const mfem::Vector enthalpyDirection(restrictedDirection.GetData() + offsets[2], offsets[3] - offsets[2]);
|
||||
mfem::Vector fullDensityDirection = fullDirectionView.block(blocks::density_field.mass_term);
|
||||
mfem::Vector fullSurfaceDirection = fullDirectionView.block(blocks::surface_deformation_field.parameters_term);
|
||||
mfem::Vector fullEnthalpyDirection = fullDirectionView.block(blocks::enthalpy_field.specific_term);
|
||||
fullDensityDirection = densityDirection;
|
||||
fullSurfaceDirection = surfaceDirection;
|
||||
fullEnthalpyDirection = enthalpyDirection;
|
||||
|
||||
mfem::Vector fullAction;
|
||||
physical.Mult(fullDirection, fullAction);
|
||||
const auto fullActionView = physical.GetRootManifest().residualView(fullAction);
|
||||
mfem::Vector expected(restricted.Height());
|
||||
mfem::Vector expectedDensity(expected.GetData(), offsets[1]);
|
||||
mfem::Vector expectedSurface(expected.GetData() + offsets[1], offsets[2] - offsets[1]);
|
||||
mfem::Vector expectedEnthalpy(expected.GetData() + offsets[2], offsets[3] - offsets[2]);
|
||||
const mfem::Vector fullDensityAction = fullActionView.block(blocks::density_field.mass_term);
|
||||
const mfem::Vector fullSurfaceAction =
|
||||
fullActionView.block(blocks::surface_deformation_field.shape_equilibrium_term);
|
||||
const mfem::Vector fullEnthalpyAction = fullActionView.block(blocks::enthalpy_field.specific_term);
|
||||
expectedDensity = fullDensityAction;
|
||||
expectedSurface = fullSurfaceAction;
|
||||
expectedEnthalpy = fullEnthalpyAction;
|
||||
const mfem::Vector restrictedDensity(restrictedAction.GetData(), offsets[1]);
|
||||
const mfem::Vector restrictedSurface(restrictedAction.GetData() + offsets[1], offsets[2] - offsets[1]);
|
||||
const mfem::Vector restrictedEnthalpy(restrictedAction.GetData() + offsets[2], offsets[3] - offsets[2]);
|
||||
INFO("Restricted density-row error = " << relativeError(restrictedDensity, expectedDensity));
|
||||
INFO("Restricted surface-row error = " << relativeError(restrictedSurface, expectedSurface));
|
||||
INFO("Restricted enthalpy-row error = " << relativeError(restrictedEnthalpy, expectedEnthalpy));
|
||||
CHECK(relativeError(restrictedDensity, expectedDensity) <= 2.0e-12);
|
||||
CHECK(relativeError(restrictedSurface, expectedSurface) <= 2.0e-12);
|
||||
CHECK(relativeError(restrictedEnthalpy, expectedEnthalpy) <= 2.0e-12);
|
||||
CHECK(relativeError(restrictedAction, expected) <= 2.0e-12);
|
||||
|
||||
CHECK(prepared.GetDensityDiagonalQuality().maximumAbsoluteEntryBeforeRegularization > 0.0);
|
||||
CHECK(prepared.GetSurfaceDiagonalQuality().maximumAbsoluteEntryBeforeRegularization > 0.0);
|
||||
CHECK(prepared.GetSurfaceDiagonalQuality().minimumAbsoluteEntryBeforeRegularization > 0.0);
|
||||
CHECK(prepared.GetSurfaceDiagonalQuality().regularizedEntries == 0);
|
||||
CHECK(prepared.GetEnthalpyDiagonalQuality().maximumAbsoluteEntryBeforeRegularization > 0.0);
|
||||
CHECK(prepared.GetStatistics().surfaceJacobianProbes == 0);
|
||||
CHECK(prepared.GetStatistics().surfaceRieszAssemblies == 1);
|
||||
|
||||
const auto calibratedBlock = preconditioning::materialSurfaceBlock(
|
||||
problem, backend::Diagonal{}, backend::Diagonal{}, preconditioning::ApproximateMaterialSurfaceLDU{},
|
||||
{.surfaceCalibration = {
|
||||
.target = preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur,
|
||||
.probeCount = 3,
|
||||
.objective = preconditioning::SurfaceRieszCalibrationObjective::right_preconditioned_action
|
||||
}}
|
||||
);
|
||||
auto calibrated = preconditioning::prepare(problem, calibratedBlock);
|
||||
CHECK(calibrated.GetSurfaceCalibration().WasCalibrated());
|
||||
CHECK(
|
||||
calibrated.GetSurfaceCalibration().target ==
|
||||
preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur
|
||||
);
|
||||
CHECK(calibrated.GetSurfaceCalibration().probeCount == 3);
|
||||
CHECK(
|
||||
calibrated.GetSurfaceCalibration().objective ==
|
||||
preconditioning::SurfaceRieszCalibrationObjective::right_preconditioned_action
|
||||
);
|
||||
CHECK(std::isfinite(calibrated.GetSurfaceCalibration().scale));
|
||||
CHECK(calibrated.GetSurfaceCalibration().scale != 0.0);
|
||||
CHECK(calibrated.GetStatistics().surfaceJacobianProbes == 3);
|
||||
CHECK(calibrated.GetSurfaceDiagonalQuality().maximumAbsoluteEntryBeforeRegularization > 0.0);
|
||||
|
||||
const auto frequencyAwareBlock = preconditioning::materialSurfaceBlock(
|
||||
problem, backend::Diagonal{}, FixedCycleAMG{backend::FixedCycles{.cycles = 1}},
|
||||
preconditioning::ApproximateMaterialSurfaceLDU{},
|
||||
preconditioning::SurfaceH1MassStiffness{
|
||||
.calibration =
|
||||
{.target = preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur, .probeCount = 4},
|
||||
.relativeMassCoefficientFloor = 1.0e-10,
|
||||
.gramRelativeTolerance = 1.0e-12
|
||||
}
|
||||
);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename std::remove_cvref_t<decltype(frequencyAwareBlock)>::SurfaceSurrogate,
|
||||
preconditioning::SurfaceH1MassStiffness>
|
||||
);
|
||||
auto frequencyAware = preconditioning::prepare(problem, frequencyAwareBlock);
|
||||
using PreparedFrequencyAware = std::remove_cvref_t<decltype(frequencyAware)>;
|
||||
STATIC_CHECK_FALSE(std::copy_constructible<PreparedFrequencyAware>);
|
||||
STATIC_CHECK_FALSE(std::move_constructible<PreparedFrequencyAware>);
|
||||
const auto &surfaceFit = frequencyAware.GetSurfaceFit();
|
||||
CHECK(surfaceFit.WasCalibrated());
|
||||
CHECK(surfaceFit.target == preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur);
|
||||
CHECK(surfaceFit.probeCount == 4);
|
||||
CHECK((surfaceFit.sign == -1.0 || surfaceFit.sign == 1.0));
|
||||
CHECK(std::isfinite(surfaceFit.massCoefficient));
|
||||
CHECK(surfaceFit.massCoefficient > 0.0);
|
||||
CHECK(std::isfinite(surfaceFit.stiffnessCoefficient));
|
||||
CHECK(surfaceFit.stiffnessCoefficient >= 0.0);
|
||||
CHECK(std::isfinite(surfaceFit.relativeResidual));
|
||||
CHECK(surfaceFit.relativeGramDeterminant > 1.0e-12);
|
||||
CHECK(surfaceFit.normalEquations.targetTarget > 0.0);
|
||||
CHECK(frequencyAware.GetSurfaceInverse().Height() == physical.GetDomainDeformation().parameterCount());
|
||||
CHECK(frequencyAware.GetSurfaceSurrogateMatrix().Height() == finiteElements.surfaceDeformationFes->GetTrueVSize());
|
||||
CHECK(frequencyAware.GetSurfaceBackend().GetStatistics().setups == 1);
|
||||
CHECK(frequencyAware.GetStatistics().surfaceJacobianProbes == 4);
|
||||
CHECK(frequencyAware.GetStatistics().surfaceH1Assemblies == 3);
|
||||
const auto frequencyAwareNoChange = frequencyAware.Refresh(physical);
|
||||
CHECK_FALSE(frequencyAwareNoChange.DidAnyWork());
|
||||
CHECK(frequencyAware.GetStatistics().noOpRefreshes == 1);
|
||||
|
||||
mfem::Vector rightHandSide(prepared.Width());
|
||||
mfem::Vector correction(prepared.Height());
|
||||
mfem::Vector repeatedCorrection(prepared.Height());
|
||||
for (int index = 0; index < rightHandSide.Size(); ++index) {
|
||||
rightHandSide(index) = std::cos(0.19 * static_cast<double>(index + 1));
|
||||
}
|
||||
correction = 0.0;
|
||||
repeatedCorrection = 0.0;
|
||||
double *const correctionStorage = correction.GetData();
|
||||
prepared.Mult(rightHandSide, correction);
|
||||
prepared.Mult(rightHandSide, repeatedCorrection);
|
||||
CHECK(correction.GetData() == correctionStorage);
|
||||
CHECK(relativeError(correction, repeatedCorrection) <= 2.0e-15);
|
||||
for (int index = 0; index < correction.Size(); ++index) {
|
||||
REQUIRE(std::isfinite(correction(index)));
|
||||
}
|
||||
|
||||
mfem::Vector frequencyAwareCorrection(frequencyAware.Height());
|
||||
mfem::Vector repeatedFrequencyAwareCorrection(frequencyAware.Height());
|
||||
frequencyAwareCorrection = 0.0;
|
||||
repeatedFrequencyAwareCorrection = 0.0;
|
||||
frequencyAware.Mult(rightHandSide, frequencyAwareCorrection);
|
||||
frequencyAware.Mult(rightHandSide, repeatedFrequencyAwareCorrection);
|
||||
CHECK(relativeError(frequencyAwareCorrection, repeatedFrequencyAwareCorrection) <= 2.0e-13);
|
||||
for (int index = 0; index < frequencyAwareCorrection.Size(); ++index) {
|
||||
REQUIRE(std::isfinite(frequencyAwareCorrection(index)));
|
||||
}
|
||||
|
||||
const auto noChange = prepared.Refresh(physical);
|
||||
CHECK_FALSE(noChange.DidAnyWork());
|
||||
|
||||
// Full stellar-Jacobian finite-difference accuracy is covered by the
|
||||
// prepared-stellar-equilibrium tests. Here we change the state only to
|
||||
// exercise the material-surface refresh contract without repeating two
|
||||
// expensive nonlinear residual assemblies.
|
||||
mfem::Vector changedState(projected.values);
|
||||
mfem::Vector borderedDirection(problem.StateSize());
|
||||
borderedDirection = 0.0;
|
||||
mfem::Vector physicalDirection(borderedDirection.GetData(), physical.Width());
|
||||
physicalDirection = fullDirection;
|
||||
changedState.Add(1.0e-5, borderedDirection);
|
||||
problem.Prepare(changedState, makeDependencies(2), rotation);
|
||||
|
||||
CHECK_FALSE(prepared.IsCurrent());
|
||||
rightHandSide = 1.0;
|
||||
correction = 0.0;
|
||||
CHECK_THROWS_AS(prepared.Mult(rightHandSide, correction), std::logic_error);
|
||||
const auto refreshed = prepared.Refresh(problem.GetPreparedOperator().GetPhysicalOperator());
|
||||
CHECK(refreshed.linearizationChanged);
|
||||
CHECK(refreshed.rebuiltDensityInverse);
|
||||
CHECK(refreshed.rebuiltSurfaceInverse);
|
||||
CHECK(refreshed.rebuiltEnthalpyInverse);
|
||||
CHECK(prepared.IsCurrent());
|
||||
CHECK(prepared.GetStatistics().surfaceJacobianProbes == 0);
|
||||
CHECK(prepared.GetStatistics().surfaceRieszAssemblies == 2);
|
||||
|
||||
auto densityOnlyDependencies = makeDependencies(2);
|
||||
densityOnlyDependencies.density.revision = 3;
|
||||
problem.Prepare(changedState, densityOnlyDependencies, rotation);
|
||||
CHECK_FALSE(prepared.IsCurrent());
|
||||
const auto stateOnlyRefresh = prepared.Refresh(problem.GetPreparedOperator().GetPhysicalOperator());
|
||||
CHECK(stateOnlyRefresh.linearizationChanged);
|
||||
CHECK_FALSE(stateOnlyRefresh.DidAnyWork());
|
||||
CHECK_FALSE(stateOnlyRefresh.rebuiltDensityInverse);
|
||||
CHECK_FALSE(stateOnlyRefresh.rebuiltSurfaceInverse);
|
||||
CHECK_FALSE(stateOnlyRefresh.rebuiltEnthalpyInverse);
|
||||
CHECK(prepared.IsCurrent());
|
||||
CHECK(prepared.GetStatistics().surfaceRieszAssemblies == 2);
|
||||
|
||||
CHECK_FALSE(calibrated.IsCurrent());
|
||||
CHECK_FALSE(frequencyAware.IsCurrent());
|
||||
const auto calibratedRefresh = calibrated.Refresh(problem.GetPreparedOperator().GetPhysicalOperator());
|
||||
CHECK(calibratedRefresh.DidAnyWork());
|
||||
CHECK(calibratedRefresh.rebuiltDensityInverse);
|
||||
CHECK(calibratedRefresh.rebuiltSurfaceInverse);
|
||||
CHECK(calibratedRefresh.rebuiltEnthalpyInverse);
|
||||
CHECK(calibrated.IsCurrent());
|
||||
CHECK(calibrated.GetStatistics().surfaceJacobianProbes == 6);
|
||||
CHECK(calibrated.GetStatistics().surfaceRieszAssemblies == 2);
|
||||
|
||||
const auto frequencyAwareRefresh = frequencyAware.Refresh(problem.GetPreparedOperator().GetPhysicalOperator());
|
||||
CHECK(frequencyAwareRefresh.DidAnyWork());
|
||||
CHECK(frequencyAwareRefresh.rebuiltDensityInverse);
|
||||
CHECK(frequencyAwareRefresh.rebuiltSurfaceInverse);
|
||||
CHECK(frequencyAwareRefresh.rebuiltEnthalpyInverse);
|
||||
CHECK(frequencyAware.IsCurrent());
|
||||
CHECK(frequencyAware.GetSurfaceBackend().GetStatistics().setups == 2);
|
||||
CHECK(frequencyAware.GetStatistics().surfaceJacobianProbes == 8);
|
||||
CHECK(frequencyAware.GetStatistics().surfaceH1Assemblies == 6);
|
||||
frequencyAware.Mult(rightHandSide, frequencyAwareCorrection);
|
||||
for (int index = 0; index < frequencyAwareCorrection.Size(); ++index) {
|
||||
REQUIRE(std::isfinite(frequencyAwareCorrection(index)));
|
||||
}
|
||||
}
|
||||
236
tests/preconditioning/plan.cpp
Normal file
236
tests/preconditioning/plan.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
|
||||
using Form = blocks::surface_deformed_stellar_equilibrium_form;
|
||||
using JacobianForm = blocks::surface_deformed_stellar_equilibrium_jacobian_form;
|
||||
using CentralForm = blocks::central_density_bordered_stellar_equilibrium_form;
|
||||
using CentralJacobianForm = blocks::central_density_bordered_stellar_equilibrium_jacobian_form;
|
||||
|
||||
using DensityIdentity =
|
||||
preconditioning::IdentityBlock<blocks::density::mass::value, blocks::density::mass::residual>;
|
||||
using SurfaceIdentity = preconditioning::IdentityBlock<
|
||||
blocks::surface_deformation::parameters::value,
|
||||
blocks::surface_deformation::shape_equilibrium::residual>;
|
||||
using GravityGradientIdentity =
|
||||
preconditioning::IdentityBlock<blocks::gravity::gradient::value, blocks::gravity::gradient::residual>;
|
||||
using GravityPotentialIdentity =
|
||||
preconditioning::IdentityBlock<blocks::gravity::poisson::value, blocks::gravity::poisson::residual>;
|
||||
using EnthalpyIdentity =
|
||||
preconditioning::IdentityBlock<blocks::enthalpy::specific::value, blocks::enthalpy::specific::residual>;
|
||||
using MassIdentity = preconditioning::IdentityBlock<
|
||||
blocks::fixed_total_mass::mass_normalization::value,
|
||||
blocks::fixed_total_mass::mass_normalization::residual>;
|
||||
using CentralDensityIdentity = preconditioning::IdentityBlock<
|
||||
blocks::fixed_central_density::central_value::value,
|
||||
blocks::fixed_central_density::central_value::residual>;
|
||||
|
||||
using IdentityPlan = preconditioning::PreconditionerPlan<
|
||||
DensityIdentity,
|
||||
SurfaceIdentity,
|
||||
GravityGradientIdentity,
|
||||
GravityPotentialIdentity,
|
||||
EnthalpyIdentity,
|
||||
MassIdentity>;
|
||||
|
||||
using CentralIdentityPlan = preconditioning::PreconditionerPlan<
|
||||
DensityIdentity,
|
||||
SurfaceIdentity,
|
||||
GravityGradientIdentity,
|
||||
GravityPotentialIdentity,
|
||||
EnthalpyIdentity,
|
||||
MassIdentity,
|
||||
CentralDensityIdentity>;
|
||||
|
||||
using IncompleteCentralPlan = IdentityPlan;
|
||||
|
||||
struct AlternateDensityIdentity final : preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<blocks::density::mass::value>,
|
||||
blocks::type_list<blocks::density::mass::residual>,
|
||||
blocks::type_list<>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
preconditioning::backend::Identity> { };
|
||||
|
||||
using DuplicateOwnershipPlan = preconditioning::PreconditionerPlan<
|
||||
DensityIdentity,
|
||||
SurfaceIdentity,
|
||||
GravityGradientIdentity,
|
||||
GravityPotentialIdentity,
|
||||
EnthalpyIdentity,
|
||||
MassIdentity,
|
||||
CentralDensityIdentity,
|
||||
AlternateDensityIdentity>;
|
||||
|
||||
using ExplicitOverlapPlan = preconditioning::OverlappingPreconditionerPlan<
|
||||
DensityIdentity,
|
||||
SurfaceIdentity,
|
||||
GravityGradientIdentity,
|
||||
GravityPotentialIdentity,
|
||||
EnthalpyIdentity,
|
||||
MassIdentity,
|
||||
CentralDensityIdentity,
|
||||
AlternateDensityIdentity>;
|
||||
|
||||
struct ExtraCorrection final : blocks::value_block_base { };
|
||||
struct ExtraResidual final : blocks::residual_block_base { };
|
||||
using ExtraIdentity = preconditioning::IdentityBlock<ExtraCorrection, ExtraResidual>;
|
||||
using UnexpectedOwnershipPlan = preconditioning::PreconditionerPlan<
|
||||
DensityIdentity,
|
||||
SurfaceIdentity,
|
||||
GravityGradientIdentity,
|
||||
GravityPotentialIdentity,
|
||||
EnthalpyIdentity,
|
||||
MassIdentity,
|
||||
ExtraIdentity>;
|
||||
|
||||
using CoupledGravity = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<blocks::gravity::gradient::value, blocks::gravity::poisson::value>,
|
||||
blocks::type_list<blocks::gravity::gradient::residual, blocks::gravity::poisson::residual>,
|
||||
blocks::type_list<
|
||||
preconditioning::Coupling<blocks::gravity::gradient::residual, blocks::gravity::gradient::value>,
|
||||
preconditioning::Coupling<blocks::gravity::gradient::residual, blocks::gravity::poisson::value>,
|
||||
preconditioning::Coupling<blocks::gravity::poisson::residual, blocks::gravity::gradient::value>>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
preconditioning::backend::Identity>;
|
||||
|
||||
using ValidCoupledPlan = preconditioning::
|
||||
PreconditionerPlan<DensityIdentity, SurfaceIdentity, CoupledGravity, EnthalpyIdentity, MassIdentity>;
|
||||
|
||||
using InvalidGravityCoupling = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<blocks::gravity::gradient::value, blocks::gravity::poisson::value>,
|
||||
blocks::type_list<blocks::gravity::gradient::residual, blocks::gravity::poisson::residual>,
|
||||
blocks::type_list<preconditioning::Coupling<blocks::gravity::gradient::residual, blocks::density::mass::value>>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
preconditioning::backend::Identity>;
|
||||
|
||||
using InvalidCoupledPlan = preconditioning::
|
||||
PreconditionerPlan<DensityIdentity, SurfaceIdentity, InvalidGravityCoupling, EnthalpyIdentity, MassIdentity>;
|
||||
|
||||
using IncompatibleBackendComponent = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<blocks::gravity::gradient::value>,
|
||||
blocks::type_list<blocks::gravity::gradient::residual>,
|
||||
blocks::type_list<>,
|
||||
preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::elliptic_like,
|
||||
preconditioning::OperatorValueStructure::vector,
|
||||
preconditioning::OperatorSymmetry::symmetric,
|
||||
preconditioning::OperatorDefiniteness::positive_definite,
|
||||
preconditioning::OperatorRepresentation::assembled_sparse,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof,
|
||||
preconditioning::OperatorFESpace::h_div>,
|
||||
preconditioning::backend::HypreBoomerAMG<>>;
|
||||
|
||||
struct IncoherentPlanDeclaration final {
|
||||
using ComponentTypes = blocks::type_list<DensityIdentity>;
|
||||
using CorrectionBlocks = blocks::type_list<blocks::gravity::gradient::value>;
|
||||
using ResidualBlocks = blocks::type_list<blocks::density::mass::residual>;
|
||||
using RequiredCouplings = blocks::type_list<>;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Preconditioning Backends Advertise Compile-Time Operator Compatibility",
|
||||
tags::preconditioning_type_contract
|
||||
) {
|
||||
using ScalarElliptic = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::elliptic_like, preconditioning::OperatorValueStructure::scalar,
|
||||
preconditioning::OperatorSymmetry::symmetric, preconditioning::OperatorDefiniteness::positive_definite,
|
||||
preconditioning::OperatorRepresentation::assembled_sparse,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof, preconditioning::OperatorFESpace::h1>;
|
||||
|
||||
using VectorElliptic = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::elliptic_like, preconditioning::OperatorValueStructure::vector,
|
||||
preconditioning::OperatorSymmetry::symmetric, preconditioning::OperatorDefiniteness::positive_definite,
|
||||
preconditioning::OperatorRepresentation::assembled_sparse,
|
||||
preconditioning::OperatorDistribution::distributed_true_dof, preconditioning::OperatorFESpace::h_div>;
|
||||
|
||||
using LocalDenseBorder = preconditioning::OperatorCharacteristics<
|
||||
preconditioning::OperatorCategory::dense_border, preconditioning::OperatorValueStructure::block,
|
||||
preconditioning::OperatorSymmetry::nonsymmetric, preconditioning::OperatorDefiniteness::indefinite,
|
||||
preconditioning::OperatorRepresentation::assembled_dense, preconditioning::OperatorDistribution::local>;
|
||||
|
||||
using FixedAMG = preconditioning::backend::HypreBoomerAMG<preconditioning::backend::FixedCycles>;
|
||||
|
||||
STATIC_CHECK(preconditioning::backend::Registered<FixedAMG>);
|
||||
STATIC_CHECK(preconditioning::backend::Compatible<FixedAMG, ScalarElliptic>);
|
||||
STATIC_CHECK_FALSE(preconditioning::backend::Compatible<FixedAMG, VectorElliptic>);
|
||||
STATIC_CHECK(preconditioning::backend::Compatible<preconditioning::backend::DenseDirect, LocalDenseBorder>);
|
||||
STATIC_CHECK_FALSE(preconditioning::PreconditionerComponent<IncompatibleBackendComponent>);
|
||||
STATIC_CHECK(
|
||||
preconditioning::backend::applicationContract<FixedAMG> ==
|
||||
preconditioning::ApplicationContract::stationary_linear
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Preconditioner Plans Prove Complete Unique Ownership Of Every Equilibrium Block",
|
||||
tags::preconditioning_type_contract
|
||||
) {
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<DensityIdentity>);
|
||||
STATIC_CHECK(preconditioning::PreconditionerPlanType<IdentityPlan>);
|
||||
STATIC_CHECK_FALSE(preconditioning::PreconditionerPlanType<IncoherentPlanDeclaration>);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<IdentityPlan, Form>);
|
||||
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<IdentityPlan, Form, JacobianForm>);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<CentralIdentityPlan, CentralForm>);
|
||||
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<CentralIdentityPlan, CentralForm, CentralJacobianForm>);
|
||||
STATIC_CHECK(preconditioning::StationaryLinearPreconditionerPlan<CentralIdentityPlan>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Preconditioner Coverage Reports Missing Generated Borders And Rejects Accidental Overlap",
|
||||
tags::preconditioning_type_contract
|
||||
) {
|
||||
using IncompleteCoverage = preconditioning::PreconditionerCoverage<CentralForm, IncompleteCentralPlan>;
|
||||
using DuplicateCoverage = preconditioning::PreconditionerCoverage<CentralForm, DuplicateOwnershipPlan>;
|
||||
|
||||
STATIC_CHECK_FALSE(preconditioning::CompletePreconditionerFor<IncompleteCentralPlan, CentralForm>);
|
||||
STATIC_CHECK(IncompleteCoverage::MissingCorrectionBlocks::size == 1);
|
||||
STATIC_CHECK(IncompleteCoverage::MissingResidualBlocks::size == 1);
|
||||
STATIC_CHECK(
|
||||
blocks::contains_type_v<
|
||||
blocks::fixed_central_density::central_value::value, IncompleteCoverage::MissingCorrectionBlocks>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
blocks::contains_type_v<
|
||||
blocks::fixed_central_density::central_value::residual, IncompleteCoverage::MissingResidualBlocks>
|
||||
);
|
||||
|
||||
STATIC_CHECK_FALSE(preconditioning::CompletePreconditionerFor<DuplicateOwnershipPlan, CentralForm>);
|
||||
STATIC_CHECK(DuplicateCoverage::RepeatedCorrectionBlocks::size == 1);
|
||||
STATIC_CHECK(DuplicateCoverage::RepeatedResidualBlocks::size == 1);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<ExplicitOverlapPlan, CentralForm>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Preconditioner Coverage Rejects Blocks Outside The Compiled Stellar Form",
|
||||
tags::preconditioning_type_contract
|
||||
) {
|
||||
using Coverage = preconditioning::PreconditionerCoverage<Form, UnexpectedOwnershipPlan>;
|
||||
|
||||
STATIC_CHECK_FALSE(preconditioning::CompletePreconditionerFor<UnexpectedOwnershipPlan, Form>);
|
||||
STATIC_CHECK(Coverage::UnexpectedCorrectionBlocks::size == 1);
|
||||
STATIC_CHECK(Coverage::UnexpectedResidualBlocks::size == 1);
|
||||
STATIC_CHECK(blocks::contains_type_v<ExtraCorrection, Coverage::UnexpectedCorrectionBlocks>);
|
||||
STATIC_CHECK(blocks::contains_type_v<ExtraResidual, Coverage::UnexpectedResidualBlocks>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Preconditioner Component Dependencies Must Exist In The Compiled Jacobian Graph",
|
||||
tags::preconditioning_type_contract
|
||||
) {
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<CoupledGravity>);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<ValidCoupledPlan, Form>);
|
||||
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<ValidCoupledPlan, Form, JacobianForm>);
|
||||
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<InvalidCoupledPlan, Form>);
|
||||
STATIC_CHECK_FALSE(preconditioning::requiredCouplingsExist<InvalidCoupledPlan, JacobianForm>);
|
||||
STATIC_CHECK_FALSE(preconditioning::CompatiblePreconditionerFor<InvalidCoupledPlan, Form, JacobianForm>);
|
||||
}
|
||||
519
tests/preconditioning/specification_border.cpp
Normal file
519
tests/preconditioning/specification_border.cpp
Normal file
@@ -0,0 +1,519 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace backend = mean_field::preconditioning::backend;
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
|
||||
using BaseModel = mean_field::operators::StellarEquilibriumSpecificationModel;
|
||||
using CentralModel = mean_field::operators::CentralDensityStellarEquilibriumSpecificationModel;
|
||||
using ReorderedCentralModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||
mean_field::models::FixedCentralDensity,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::models::FixedTotalMass,
|
||||
mean_field::eos::Polytrope>>;
|
||||
using BaseProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel>;
|
||||
using CentralProblem = mean_field::equilibrium::StellarEquilibriumProblem<CentralModel>;
|
||||
using BaseBorder = preconditioning::CompiledSpecificationBorderFor<BaseModel>;
|
||||
using CentralBorder = preconditioning::CompiledSpecificationBorderFor<CentralModel>;
|
||||
using BaseComponent = decltype(preconditioning::specificationBorderBlock(std::declval<const BaseProblem &>()));
|
||||
using CentralComponent =
|
||||
decltype(preconditioning::specificationBorderBlock(std::declval<const CentralProblem &>()));
|
||||
using BasePlan = preconditioning::PreconditionerPlan<BaseComponent>;
|
||||
using CentralPlan = preconditioning::PreconditionerPlan<CentralComponent>;
|
||||
|
||||
class KnownBorderCouplings final {
|
||||
public:
|
||||
explicit KnownBorderCouplings(const int borderSize)
|
||||
: m_borderSize(borderSize),
|
||||
m_structureToBorder(
|
||||
borderSize,
|
||||
StructureSize()
|
||||
),
|
||||
m_borderToStructure(
|
||||
StructureSize(),
|
||||
borderSize
|
||||
),
|
||||
m_borderDiagonal(borderSize) {
|
||||
if (borderSize <= 0) {
|
||||
throw std::invalid_argument("The known border must have positive size.");
|
||||
}
|
||||
for (int row = 0; row < borderSize; ++row) {
|
||||
for (int column = 0; column < StructureSize(); ++column) {
|
||||
m_structureToBorder(row, column) = 0.04 * static_cast<double>((row + 1) * (column + 2));
|
||||
m_borderToStructure(column, row) = -0.03 * static_cast<double>((column + 1) * (row + 2));
|
||||
}
|
||||
for (int column = 0; column < borderSize; ++column) {
|
||||
m_borderDiagonal(row, column) =
|
||||
row == column ? 2.0 + static_cast<double>(row) : 0.01 * static_cast<double>(row + column + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr int StructureSize() noexcept {
|
||||
return 3;
|
||||
}
|
||||
|
||||
[[nodiscard]] int BorderSize() const noexcept {
|
||||
return m_borderSize;
|
||||
}
|
||||
|
||||
void ApplyStructureToBorder(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
m_structureToBorder.Mult(direction, action);
|
||||
}
|
||||
|
||||
void ApplyBorderToStructure(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
m_borderToStructure.Mult(direction, action);
|
||||
}
|
||||
|
||||
void ApplyBorderToBorder(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
m_borderDiagonal.Mult(direction, action);
|
||||
}
|
||||
|
||||
void IncreaseBorderDiagonal(const double increment) {
|
||||
for (int index = 0; index < m_borderSize; ++index) {
|
||||
m_borderDiagonal(index, index) += increment;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::DenseMatrix &StructureToBorder() const noexcept {
|
||||
return m_structureToBorder;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::DenseMatrix &BorderToStructure() const noexcept {
|
||||
return m_borderToStructure;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::DenseMatrix &BorderDiagonal() const noexcept {
|
||||
return m_borderDiagonal;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_borderSize;
|
||||
mfem::DenseMatrix m_structureToBorder;
|
||||
mfem::DenseMatrix m_borderToStructure;
|
||||
mfem::DenseMatrix m_borderDiagonal;
|
||||
};
|
||||
|
||||
[[nodiscard]] double relativeError(
|
||||
const mfem::Vector &left,
|
||||
const mfem::Vector &right
|
||||
) {
|
||||
mfem::Vector difference(left);
|
||||
difference -= right;
|
||||
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
|
||||
}
|
||||
|
||||
template <
|
||||
preconditioning::ApplicationContract StructureInverseContract =
|
||||
preconditioning::ApplicationContract::stationary_linear>
|
||||
void verifyKnownBorderFactorization(const int borderSize) {
|
||||
mfem::Vector structureDiagonal(KnownBorderCouplings::StructureSize());
|
||||
structureDiagonal(0) = 2.0;
|
||||
structureDiagonal(1) = 3.0;
|
||||
structureDiagonal(2) = 5.0;
|
||||
auto structureInverse = backend::prepare(backend::Diagonal{}, structureDiagonal);
|
||||
KnownBorderCouplings couplings(borderSize);
|
||||
using Factorization =
|
||||
preconditioning::SpecificationBorderFactorizationOperator<KnownBorderCouplings, StructureInverseContract>;
|
||||
Factorization factorization(structureInverse, couplings);
|
||||
constexpr bool cachesStructureResponse = Factorization::cachesStructureInverseBorderCoupling;
|
||||
|
||||
const auto expectedSchurEntry = [&](const int row, const int column) {
|
||||
double correction = 0.0;
|
||||
for (int inner = 0; inner < KnownBorderCouplings::StructureSize(); ++inner) {
|
||||
correction += couplings.StructureToBorder()(row, inner) * couplings.BorderToStructure()(inner, column) /
|
||||
structureDiagonal(inner);
|
||||
}
|
||||
return couplings.BorderDiagonal()(row, column) - correction;
|
||||
};
|
||||
for (int row = 0; row < borderSize; ++row) {
|
||||
for (int column = 0; column < borderSize; ++column) {
|
||||
CHECK(
|
||||
factorization.GetSchurComplement()(row, column) ==
|
||||
Catch::Approx(expectedSchurEntry(row, column)).margin(2.0e-14)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const int completeSize = KnownBorderCouplings::StructureSize() + borderSize;
|
||||
mfem::DenseMatrix completeMatrix(completeSize);
|
||||
completeMatrix = 0.0;
|
||||
for (int index = 0; index < KnownBorderCouplings::StructureSize(); ++index) {
|
||||
completeMatrix(index, index) = structureDiagonal(index);
|
||||
}
|
||||
for (int row = 0; row < KnownBorderCouplings::StructureSize(); ++row) {
|
||||
for (int column = 0; column < borderSize; ++column) {
|
||||
completeMatrix(row, KnownBorderCouplings::StructureSize() + column) =
|
||||
couplings.BorderToStructure()(row, column);
|
||||
completeMatrix(KnownBorderCouplings::StructureSize() + column, row) =
|
||||
couplings.StructureToBorder()(column, row);
|
||||
}
|
||||
}
|
||||
for (int row = 0; row < borderSize; ++row) {
|
||||
for (int column = 0; column < borderSize; ++column) {
|
||||
completeMatrix(
|
||||
KnownBorderCouplings::StructureSize() + row, KnownBorderCouplings::StructureSize() + column
|
||||
) = couplings.BorderDiagonal()(row, column);
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector rightHandSide(completeSize);
|
||||
for (int index = 0; index < completeSize; ++index) {
|
||||
rightHandSide(index) = 0.25 + 0.17 * static_cast<double>(index + 1);
|
||||
}
|
||||
mfem::Vector actual(completeSize);
|
||||
mfem::Vector expected(completeSize);
|
||||
factorization.Mult(rightHandSide, actual);
|
||||
mfem::DenseMatrixInverse exactInverse(completeMatrix);
|
||||
exactInverse.Mult(rightHandSide, expected);
|
||||
CHECK(relativeError(actual, expected) <= 2.0e-13);
|
||||
|
||||
const auto statisticsBeforeRefresh = factorization.GetStatistics();
|
||||
CHECK(statisticsBeforeRefresh.setups == 1);
|
||||
CHECK(statisticsBeforeRefresh.schurProbes == static_cast<std::uint64_t>(borderSize));
|
||||
CHECK(statisticsBeforeRefresh.applications == 1);
|
||||
CHECK(
|
||||
statisticsBeforeRefresh.structureInverseApplications ==
|
||||
static_cast<std::uint64_t>(borderSize + (cachesStructureResponse ? 1 : 2))
|
||||
);
|
||||
CHECK(
|
||||
statisticsBeforeRefresh.cachedStructureInverseBorderApplications ==
|
||||
static_cast<std::uint64_t>(cachesStructureResponse ? 1 : 0)
|
||||
);
|
||||
CHECK(statisticsBeforeRefresh.structureToBorderApplications == static_cast<std::uint64_t>(borderSize + 1));
|
||||
CHECK(
|
||||
statisticsBeforeRefresh.borderToStructureApplications ==
|
||||
static_cast<std::uint64_t>(borderSize + (cachesStructureResponse ? 0 : 1))
|
||||
);
|
||||
CHECK(statisticsBeforeRefresh.borderToBorderApplications == static_cast<std::uint64_t>(borderSize));
|
||||
CHECK(
|
||||
structureInverse.GetStatistics().applications ==
|
||||
static_cast<std::uint64_t>(borderSize + (cachesStructureResponse ? 1 : 2))
|
||||
);
|
||||
|
||||
for (int index = 0; index < KnownBorderCouplings::StructureSize(); ++index) {
|
||||
structureDiagonal(index) += 0.25 * static_cast<double>(index + 1);
|
||||
completeMatrix(index, index) = structureDiagonal(index);
|
||||
}
|
||||
structureInverse.Refresh(structureDiagonal);
|
||||
couplings.IncreaseBorderDiagonal(0.5);
|
||||
for (int index = 0; index < borderSize; ++index) {
|
||||
completeMatrix(
|
||||
KnownBorderCouplings::StructureSize() + index, KnownBorderCouplings::StructureSize() + index
|
||||
) += 0.5;
|
||||
}
|
||||
factorization.RefreshSchurComplement();
|
||||
CHECK(factorization.GetStatistics().setups == 2);
|
||||
CHECK(factorization.GetStatistics().schurProbes == static_cast<std::uint64_t>(2 * borderSize));
|
||||
CHECK(factorization.GetStatistics().borderToBorderApplications == static_cast<std::uint64_t>(2 * borderSize));
|
||||
CHECK(
|
||||
factorization.GetStatistics().structureInverseApplications ==
|
||||
static_cast<std::uint64_t>(2 * borderSize + (cachesStructureResponse ? 1 : 2))
|
||||
);
|
||||
CHECK(
|
||||
factorization.GetStatistics().cachedStructureInverseBorderApplications ==
|
||||
static_cast<std::uint64_t>(cachesStructureResponse ? 1 : 0)
|
||||
);
|
||||
CHECK(
|
||||
factorization.GetStatistics().structureToBorderApplications ==
|
||||
static_cast<std::uint64_t>(2 * borderSize + 1)
|
||||
);
|
||||
CHECK(
|
||||
factorization.GetStatistics().borderToStructureApplications ==
|
||||
static_cast<std::uint64_t>(2 * borderSize + (cachesStructureResponse ? 0 : 1))
|
||||
);
|
||||
for (int row = 0; row < borderSize; ++row) {
|
||||
for (int column = 0; column < borderSize; ++column) {
|
||||
CHECK(
|
||||
factorization.GetSchurComplement()(row, column) ==
|
||||
Catch::Approx(expectedSchurEntry(row, column)).margin(2.0e-14)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector refreshedActual(completeSize);
|
||||
mfem::Vector refreshedExpected(completeSize);
|
||||
factorization.Mult(rightHandSide, refreshedActual);
|
||||
mfem::DenseMatrixInverse refreshedExactInverse(completeMatrix);
|
||||
refreshedExactInverse.Mult(rightHandSide, refreshedExpected);
|
||||
CHECK(relativeError(refreshedActual, refreshedExpected) <= 2.0e-13);
|
||||
|
||||
const auto statisticsAfterRefreshApplication = factorization.GetStatistics();
|
||||
CHECK(statisticsAfterRefreshApplication.applications == 2);
|
||||
CHECK(
|
||||
statisticsAfterRefreshApplication.structureInverseApplications ==
|
||||
static_cast<std::uint64_t>(2 * borderSize + (cachesStructureResponse ? 2 : 4))
|
||||
);
|
||||
CHECK(
|
||||
statisticsAfterRefreshApplication.cachedStructureInverseBorderApplications ==
|
||||
static_cast<std::uint64_t>(cachesStructureResponse ? 2 : 0)
|
||||
);
|
||||
CHECK(
|
||||
statisticsAfterRefreshApplication.structureToBorderApplications ==
|
||||
static_cast<std::uint64_t>(2 * borderSize + 2)
|
||||
);
|
||||
CHECK(
|
||||
statisticsAfterRefreshApplication.borderToStructureApplications ==
|
||||
static_cast<std::uint64_t>(2 * borderSize + (cachesStructureResponse ? 0 : 2))
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies
|
||||
makeDependencies(const std::uint64_t revision = 1) {
|
||||
return {
|
||||
.discretization = {.identity = 9201, .revision = 1},
|
||||
.density = {.identity = 9203, .revision = revision},
|
||||
.surfaceDeformation = {.identity = 9207, .revision = revision},
|
||||
.gravityGradient = {.identity = 9211, .revision = revision},
|
||||
.gravityPotential = {.identity = 9217, .revision = revision},
|
||||
.enthalpy = {.identity = 9223, .revision = revision},
|
||||
.bernoulliConstant = {.identity = 9229, .revision = revision},
|
||||
.rotation = {.identity = 9231, .revision = revision},
|
||||
.targetMass = {.identity = 9237, .revision = 1}
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::physics::RigidRotation zeroRotation() {
|
||||
mfem::Vector angularVelocity(3);
|
||||
mfem::Vector center(3);
|
||||
angularVelocity = 0.0;
|
||||
center = 0.0;
|
||||
return {angularVelocity, center};
|
||||
}
|
||||
|
||||
template <
|
||||
typename View,
|
||||
typename Term>
|
||||
void assignStateBlock(
|
||||
const View &view,
|
||||
const Term &term,
|
||||
const mfem::Vector &source,
|
||||
mfem::Vector &state
|
||||
) {
|
||||
mfem::Vector destination = view.block(term);
|
||||
REQUIRE(destination.Size() == source.Size());
|
||||
destination = source;
|
||||
destination.SyncAliasMemory(state);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Model Specifications Compile Complete Canonical Preconditioning Borders",
|
||||
"[preconditioning][specification_border][unit][type_contract]"
|
||||
) {
|
||||
using ExpectedBaseCorrections = blocks::type_list<blocks::fixed_total_mass::mass_normalization::value>;
|
||||
using ExpectedBaseResiduals = blocks::type_list<blocks::fixed_total_mass::mass_normalization::residual>;
|
||||
using ExpectedCentralCorrections = blocks::type_list<
|
||||
blocks::fixed_total_mass::mass_normalization::value, blocks::fixed_central_density::central_value::value>;
|
||||
using ExpectedCentralResiduals = blocks::type_list<
|
||||
blocks::fixed_total_mass::mass_normalization::residual, blocks::fixed_central_density::central_value::residual>;
|
||||
|
||||
STATIC_CHECK(std::same_as<CentralModel, ReorderedCentralModel>);
|
||||
STATIC_CHECK(BaseBorder::valueArity == 1);
|
||||
STATIC_CHECK(BaseBorder::residualArity == 1);
|
||||
STATIC_CHECK(BaseBorder::specificationCount == 1);
|
||||
STATIC_CHECK(std::same_as<typename BaseBorder::CorrectionBlocks, ExpectedBaseCorrections>);
|
||||
STATIC_CHECK(std::same_as<typename BaseBorder::ResidualBlocks, ExpectedBaseResiduals>);
|
||||
STATIC_CHECK(BaseBorder::RequiredCouplings::size == 3);
|
||||
|
||||
STATIC_CHECK(CentralBorder::valueArity == 2);
|
||||
STATIC_CHECK(CentralBorder::residualArity == 2);
|
||||
STATIC_CHECK(CentralBorder::specificationCount == 2);
|
||||
STATIC_CHECK(std::same_as<typename CentralBorder::CorrectionBlocks, ExpectedCentralCorrections>);
|
||||
STATIC_CHECK(std::same_as<typename CentralBorder::ResidualBlocks, ExpectedCentralResiduals>);
|
||||
STATIC_CHECK(CentralBorder::RequiredCouplings::size == 5);
|
||||
STATIC_CHECK(
|
||||
preconditioning::specificationBorderValueOffset<mean_field::models::FixedTotalMass, CentralModel> == 0
|
||||
);
|
||||
STATIC_CHECK(
|
||||
preconditioning::specificationBorderValueOffset<mean_field::models::FixedCentralDensity, CentralModel> == 1
|
||||
);
|
||||
STATIC_CHECK(
|
||||
preconditioning::specificationBorderResidualOffset<mean_field::models::FixedTotalMass, CentralModel> == 0
|
||||
);
|
||||
STATIC_CHECK(
|
||||
preconditioning::specificationBorderResidualOffset<mean_field::models::FixedCentralDensity, CentralModel> == 1
|
||||
);
|
||||
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<BaseComponent>);
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<CentralComponent>);
|
||||
STATIC_CHECK(BaseComponent::RequiredCouplings::size == 19);
|
||||
STATIC_CHECK(CentralComponent::RequiredCouplings::size == 21);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<BasePlan, typename BaseProblem::FormType>);
|
||||
STATIC_CHECK(
|
||||
preconditioning::CompatiblePreconditionerFor<
|
||||
BasePlan, typename BaseProblem::FormType, typename BaseProblem::JacobianFormType>
|
||||
);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<CentralPlan, typename CentralProblem::FormType>);
|
||||
STATIC_CHECK(
|
||||
preconditioning::CompatiblePreconditionerFor<
|
||||
CentralPlan, typename CentralProblem::FormType, typename CentralProblem::JacobianFormType>
|
||||
);
|
||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename CentralComponent::BackendType>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Dense Specification Borders Cache Stationary Structure Responses And Reproduce Exact Block Factorizations",
|
||||
"[preconditioning][specification_border][unit][factorization]"
|
||||
) {
|
||||
SECTION("one generated scalar") {
|
||||
verifyKnownBorderFactorization(1);
|
||||
}
|
||||
SECTION("two generated scalars") {
|
||||
verifyKnownBorderFactorization(2);
|
||||
}
|
||||
SECTION("four generated scalars") {
|
||||
verifyKnownBorderFactorization(4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Flexible Specification Borders Preserve Per-Application Structure Solves",
|
||||
"[preconditioning][specification_border][unit][factorization]"
|
||||
) {
|
||||
verifyKnownBorderFactorization<preconditioning::ApplicationContract::flexible>(2);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Generated Specification Border Actions Match The Authoritative Stellar Jacobian",
|
||||
"[preconditioning][specification_border][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
const utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
constexpr double radius = utils::RADIUS;
|
||||
constexpr double mass = utils::MASS;
|
||||
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||
const auto stellarModel = model::StellarModel(
|
||||
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(stellarModel, finiteElements);
|
||||
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||
|
||||
preconditioning::SpecificationBorderJacobianOperator coupling(problem);
|
||||
REQUIRE(coupling.BorderSize() == 2);
|
||||
REQUIRE(coupling.StructureSize() + coupling.BorderSize() == problem.StateSize());
|
||||
const auto &offsets = coupling.GetStructureOffsets();
|
||||
|
||||
mfem::Vector groupedDirection(coupling.Width());
|
||||
for (int index = 0; index < groupedDirection.Size(); ++index) {
|
||||
groupedDirection(index) = 0.015 * std::sin(0.23 * static_cast<double>(index + 1));
|
||||
}
|
||||
const auto groupedBlock = [&](const int block) {
|
||||
return mfem::Vector(groupedDirection.GetData() + offsets[block], offsets[block + 1] - offsets[block]);
|
||||
};
|
||||
|
||||
mfem::Vector structureOnlyRoot(problem.StateSize());
|
||||
structureOnlyRoot = 0.0;
|
||||
const auto structureView = problem.GetManifest().directionView(structureOnlyRoot);
|
||||
assignStateBlock(structureView, blocks::density_field.mass_term, groupedBlock(0), structureOnlyRoot);
|
||||
assignStateBlock(
|
||||
structureView, blocks::surface_deformation_field.parameters_term, groupedBlock(1), structureOnlyRoot
|
||||
);
|
||||
assignStateBlock(structureView, blocks::enthalpy_field.specific_term, groupedBlock(2), structureOnlyRoot);
|
||||
assignStateBlock(structureView, blocks::gravity_field.gradient_term, groupedBlock(3), structureOnlyRoot);
|
||||
assignStateBlock(structureView, blocks::gravity_field.poisson_term, groupedBlock(4), structureOnlyRoot);
|
||||
|
||||
mfem::Vector borderOnlyRoot(problem.StateSize());
|
||||
borderOnlyRoot = 0.0;
|
||||
const auto borderView = problem.GetManifest().directionView(borderOnlyRoot);
|
||||
mfem::Vector massDirection(groupedDirection.GetData() + coupling.StructureSize(), 1);
|
||||
mfem::Vector centralDirection(groupedDirection.GetData() + coupling.StructureSize() + 1, 1);
|
||||
assignStateBlock(
|
||||
borderView, blocks::fixed_total_mass_constraint.mass_normalization_term, massDirection, borderOnlyRoot
|
||||
);
|
||||
assignStateBlock(
|
||||
borderView, blocks::fixed_central_density_phase.central_value_term, centralDirection, borderOnlyRoot
|
||||
);
|
||||
|
||||
mfem::Vector structureOnlyAction;
|
||||
mfem::Vector borderOnlyAction;
|
||||
problem.ApplyLinearization(structureOnlyRoot, structureOnlyAction);
|
||||
problem.ApplyLinearization(borderOnlyRoot, borderOnlyAction);
|
||||
auto structureOnlyResidual = problem.GetManifest().residualView(structureOnlyAction);
|
||||
auto borderOnlyResidual = problem.GetManifest().residualView(borderOnlyAction);
|
||||
|
||||
mfem::Vector expected(coupling.Height());
|
||||
expected = 0.0;
|
||||
expected.SetVector(borderOnlyResidual.block(blocks::density_field.mass_term), offsets[0]);
|
||||
expected.SetVector(borderOnlyResidual.block(blocks::surface_deformation_field.shape_equilibrium_term), offsets[1]);
|
||||
expected.SetVector(borderOnlyResidual.block(blocks::enthalpy_field.specific_term), offsets[2]);
|
||||
expected.SetVector(borderOnlyResidual.block(blocks::gravity_field.gradient_term), offsets[3]);
|
||||
expected.SetVector(borderOnlyResidual.block(blocks::gravity_field.poisson_term), offsets[4]);
|
||||
expected.SetVector(
|
||||
structureOnlyResidual.block(blocks::fixed_total_mass_constraint.mass_normalization_term),
|
||||
coupling.StructureSize()
|
||||
);
|
||||
expected.SetVector(
|
||||
structureOnlyResidual.block(blocks::fixed_central_density_phase.central_value_term),
|
||||
coupling.StructureSize() + 1
|
||||
);
|
||||
mfem::Vector borderDiagonal(2);
|
||||
borderDiagonal(0) = borderOnlyResidual.block(blocks::fixed_total_mass_constraint.mass_normalization_term)(0);
|
||||
borderDiagonal(1) = borderOnlyResidual.block(blocks::fixed_central_density_phase.central_value_term)(0);
|
||||
mfem::Vector expectedBorder(expected, coupling.StructureSize(), coupling.BorderSize());
|
||||
expectedBorder += borderDiagonal;
|
||||
expectedBorder.SyncAliasMemory(expected);
|
||||
|
||||
mfem::Vector actual(coupling.Height());
|
||||
coupling.Mult(groupedDirection, actual);
|
||||
CHECK(relativeError(actual, expected) <= 2.0e-12);
|
||||
|
||||
auto component = preconditioning::makePreconditioner(problem);
|
||||
using Component = decltype(component);
|
||||
STATIC_CHECK(std::same_as<Component, CentralComponent>);
|
||||
auto prepared = preconditioning::prepare(problem, component);
|
||||
using GroupedPreconditioner = typename decltype(prepared)::GroupedPreconditioner;
|
||||
using PreparedFactorization = typename GroupedPreconditioner::Factorization;
|
||||
STATIC_CHECK(PreparedFactorization::cachesStructureInverseBorderCoupling);
|
||||
mfem::Vector rightHandSide(prepared.Width());
|
||||
for (int index = 0; index < rightHandSide.Size(); ++index) {
|
||||
rightHandSide(index) = std::cos(0.11 * static_cast<double>(index + 1));
|
||||
}
|
||||
mfem::Vector correction(prepared.Height());
|
||||
prepared.Mult(rightHandSide, correction);
|
||||
for (int index = 0; index < correction.Size(); ++index) {
|
||||
REQUIRE(std::isfinite(correction(index)));
|
||||
}
|
||||
const auto &factorizationStatistics = prepared.GetGroupedPreconditioner().GetFactorization().GetStatistics();
|
||||
CHECK(factorizationStatistics.setups == 1);
|
||||
CHECK(factorizationStatistics.schurProbes == 2);
|
||||
CHECK(factorizationStatistics.applications == 1);
|
||||
CHECK(factorizationStatistics.structureInverseApplications == 3);
|
||||
CHECK(factorizationStatistics.cachedStructureInverseBorderApplications == 1);
|
||||
CHECK(factorizationStatistics.borderToStructureApplications == 2);
|
||||
const auto unchanged = prepared.Refresh();
|
||||
CHECK_FALSE(unchanged.DidAnyWork());
|
||||
CHECK(prepared.IsCurrent());
|
||||
}
|
||||
336
tests/preconditioning/stellar_equilibrium.cpp
Normal file
336
tests/preconditioning/stellar_equilibrium.cpp
Normal file
@@ -0,0 +1,336 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace preconditioning_runtime_test {
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
|
||||
using Form = blocks::surface_deformed_stellar_equilibrium_form;
|
||||
using JacobianForm = blocks::surface_deformed_stellar_equilibrium_jacobian_form;
|
||||
using Layout = blocks::form_layout<Form>;
|
||||
|
||||
class Manifest final {
|
||||
public:
|
||||
Manifest()
|
||||
: m_layout(
|
||||
std::array<
|
||||
int,
|
||||
Form::value_block_count>{
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
1
|
||||
},
|
||||
std::array<
|
||||
int,
|
||||
Form::residual_block_count>{
|
||||
4,
|
||||
5,
|
||||
2,
|
||||
3,
|
||||
6,
|
||||
1
|
||||
}
|
||||
) {
|
||||
}
|
||||
|
||||
[[nodiscard]] const Layout &layout() const noexcept {
|
||||
return m_layout;
|
||||
}
|
||||
|
||||
private:
|
||||
Layout m_layout;
|
||||
};
|
||||
|
||||
class Problem final {
|
||||
public:
|
||||
Problem() : m_linearization(m_manifest.layout().value_offsets().Last()) {
|
||||
m_snapshot.discretization = {.identity = 11, .revision = 1};
|
||||
m_snapshot.geometry = {.identity = 12, .revision = 1};
|
||||
m_snapshot.equationOfStateIdentity = &m_equationOfStateToken;
|
||||
m_snapshot.linearization.discretization = m_snapshot.discretization;
|
||||
m_snapshot.linearization.density = {.identity = 21, .revision = 1};
|
||||
}
|
||||
|
||||
void AdvanceDensity() noexcept {
|
||||
++m_snapshot.linearization.density.revision;
|
||||
}
|
||||
|
||||
void AdvanceGeometry() noexcept {
|
||||
++m_snapshot.geometry.revision;
|
||||
}
|
||||
|
||||
void SetPrepared(const bool prepared) noexcept {
|
||||
m_prepared = prepared;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||
return m_prepared;
|
||||
}
|
||||
|
||||
[[nodiscard]] int StateSize() const noexcept {
|
||||
return m_manifest.layout().value_offsets().Last();
|
||||
}
|
||||
|
||||
[[nodiscard]] int EquationSize() const noexcept {
|
||||
return m_manifest.layout().residual_offsets().Last();
|
||||
}
|
||||
|
||||
[[nodiscard]] const Manifest &GetManifest() const noexcept {
|
||||
return m_manifest;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator &GetLinearizationOperator() const noexcept {
|
||||
return m_linearization;
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::preconditioning::StellarPreconditionerLifecycleSnapshot Snapshot() const {
|
||||
return m_snapshot;
|
||||
}
|
||||
|
||||
private:
|
||||
Manifest m_manifest;
|
||||
mfem::IdentityOperator m_linearization;
|
||||
std::uint8_t m_equationOfStateToken{0};
|
||||
mean_field::preconditioning::StellarPreconditionerLifecycleSnapshot m_snapshot;
|
||||
bool m_prepared{true};
|
||||
};
|
||||
} // namespace preconditioning_runtime_test
|
||||
|
||||
template <> struct mean_field::preconditioning::StellarEquilibriumProblemTraits<preconditioning_runtime_test::Problem> {
|
||||
using Problem = preconditioning_runtime_test::Problem;
|
||||
using Form = preconditioning_runtime_test::Form;
|
||||
using JacobianForm = preconditioning_runtime_test::JacobianForm;
|
||||
using Manifest = preconditioning_runtime_test::Manifest;
|
||||
|
||||
static constexpr bool registered = true;
|
||||
|
||||
[[nodiscard]] static bool IsPrepared(const Problem &problem) noexcept {
|
||||
return problem.IsPrepared();
|
||||
}
|
||||
|
||||
[[nodiscard]] static int StateSize(const Problem &problem) noexcept {
|
||||
return problem.StateSize();
|
||||
}
|
||||
|
||||
[[nodiscard]] static int EquationSize(const Problem &problem) noexcept {
|
||||
return problem.EquationSize();
|
||||
}
|
||||
|
||||
[[nodiscard]] static const Manifest &ManifestOf(const Problem &problem) noexcept {
|
||||
return problem.GetManifest();
|
||||
}
|
||||
|
||||
[[nodiscard]] static const mfem::Operator &LinearizationOperator(const Problem &problem) noexcept {
|
||||
return problem.GetLinearizationOperator();
|
||||
}
|
||||
|
||||
[[nodiscard]] static mean_field::preconditioning::StellarPreconditionerLifecycleSnapshot
|
||||
Snapshot(const Problem &problem) {
|
||||
return problem.Snapshot();
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
|
||||
using ModelWithoutPhase = mean_field::operators::StellarEquilibriumSpecificationModel;
|
||||
using CentralDensityModel = mean_field::operators::CentralDensityStellarEquilibriumSpecificationModel;
|
||||
|
||||
using ProblemWithoutPhase = mean_field::equilibrium::StellarEquilibriumProblem<ModelWithoutPhase>;
|
||||
using CentralDensityProblem = mean_field::equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
||||
using PlanWithoutPhase = preconditioning::IdentityPreconditionerPlanFor<ProblemWithoutPhase>;
|
||||
using CentralDensityPlan = preconditioning::IdentityPreconditionerPlanFor<CentralDensityProblem>;
|
||||
|
||||
using RefreshingDensityIdentity = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<blocks::density::mass::value>,
|
||||
blocks::type_list<blocks::density::mass::residual>,
|
||||
blocks::type_list<>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
preconditioning::backend::Identity,
|
||||
preconditioning::PreparationDependencies<preconditioning::PreparationDependency::linearization>>;
|
||||
using SurfaceIdentity = preconditioning::IdentityBlock<
|
||||
blocks::surface_deformation::parameters::value,
|
||||
blocks::surface_deformation::shape_equilibrium::residual>;
|
||||
using GravityGradientIdentity =
|
||||
preconditioning::IdentityBlock<blocks::gravity::gradient::value, blocks::gravity::gradient::residual>;
|
||||
using GravityPotentialIdentity =
|
||||
preconditioning::IdentityBlock<blocks::gravity::poisson::value, blocks::gravity::poisson::residual>;
|
||||
using EnthalpyIdentity =
|
||||
preconditioning::IdentityBlock<blocks::enthalpy::specific::value, blocks::enthalpy::specific::residual>;
|
||||
using FixedMassIdentity = preconditioning::IdentityBlock<
|
||||
blocks::fixed_total_mass::mass_normalization::value,
|
||||
blocks::fixed_total_mass::mass_normalization::residual>;
|
||||
using SelectiveRefreshPlan = preconditioning::PreconditionerPlan<
|
||||
RefreshingDensityIdentity,
|
||||
SurfaceIdentity,
|
||||
GravityGradientIdentity,
|
||||
GravityPotentialIdentity,
|
||||
EnthalpyIdentity,
|
||||
FixedMassIdentity>;
|
||||
|
||||
[[nodiscard]] constexpr SelectiveRefreshPlan makeSelectiveRefreshPlan() {
|
||||
return SelectiveRefreshPlan{RefreshingDensityIdentity{}, SurfaceIdentity{}, GravityGradientIdentity{},
|
||||
GravityPotentialIdentity{}, EnthalpyIdentity{}, FixedMassIdentity{}};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Identity Plans Follow The Compiled Equilibrium Problem Type",
|
||||
tags::preconditioning_runtime_unit
|
||||
) {
|
||||
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<ProblemWithoutPhase>);
|
||||
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<CentralDensityProblem>);
|
||||
STATIC_CHECK(preconditioning::StellarPreconditionerProblem<ProblemWithoutPhase>);
|
||||
STATIC_CHECK(preconditioning::StellarPreconditionerProblem<CentralDensityProblem>);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<PlanWithoutPhase, typename ProblemWithoutPhase::FormType>);
|
||||
STATIC_CHECK(
|
||||
preconditioning::CompletePreconditionerFor<CentralDensityPlan, typename CentralDensityProblem::FormType>
|
||||
);
|
||||
STATIC_CHECK(PlanWithoutPhase::ComponentTypes::size == 6);
|
||||
STATIC_CHECK(CentralDensityPlan::ComponentTypes::size == 7);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Stellar Identity Preconditioning Is Bitwise Equivalent To The P0 Baseline",
|
||||
tags::preconditioning_runtime_unit
|
||||
) {
|
||||
preconditioning_runtime_test::Problem problem;
|
||||
auto plan = preconditioning::makeIdentityPlan(problem);
|
||||
using Plan = decltype(plan);
|
||||
using Problem = preconditioning_runtime_test::Problem;
|
||||
auto preconditioner = preconditioning::prepare(problem, std::move(plan));
|
||||
|
||||
STATIC_CHECK(preconditioning::PreparedPreconditionerPlanFor<Plan, Problem>);
|
||||
CHECK(preconditioner.Height() == problem.StateSize());
|
||||
CHECK(preconditioner.Width() == problem.EquationSize());
|
||||
CHECK(preconditioner.IsCurrent());
|
||||
CHECK(&preconditioner.GetLinearizationOperator() == &problem.GetLinearizationOperator());
|
||||
|
||||
preconditioner.SetOperator(problem.GetLinearizationOperator());
|
||||
|
||||
mfem::Vector residual(problem.EquationSize());
|
||||
mfem::Vector correction(problem.StateSize());
|
||||
for (int index = 0; index < residual.Size(); ++index) {
|
||||
residual(index) = static_cast<double>(index) - 10.25;
|
||||
}
|
||||
correction = -1.0;
|
||||
|
||||
const mfem::real_t *const correctionStorage = correction.GetData();
|
||||
const auto statisticsBefore = preconditioner.GetStatistics();
|
||||
preconditioner.Mult(residual, correction);
|
||||
const auto statisticsAfter = preconditioner.GetStatistics();
|
||||
|
||||
CHECK(correction.GetData() == correctionStorage);
|
||||
CHECK(std::memcmp(correction.GetData(), residual.GetData(), sizeof(mfem::real_t) * residual.Size()) == 0);
|
||||
|
||||
const mfem::Vector densityCorrection = preconditioner.GetCorrectionBlock<blocks::density::mass::value>(correction);
|
||||
const mfem::Vector densityResidual = preconditioner.GetResidualBlock<blocks::density::mass::residual>(residual);
|
||||
CHECK(densityCorrection.Size() == 2);
|
||||
CHECK(densityCorrection.GetData() == correction.GetData());
|
||||
CHECK(densityResidual.Size() == 2);
|
||||
CHECK(densityResidual.GetData() == residual.GetData() + 9);
|
||||
CHECK(statisticsAfter.setups == statisticsBefore.setups);
|
||||
CHECK(statisticsAfter.refreshes == statisticsBefore.refreshes);
|
||||
CHECK(statisticsAfter.componentSetups == 6);
|
||||
CHECK(statisticsAfter.applications == statisticsBefore.applications + 1);
|
||||
CHECK(statisticsAfter.backendApplications == statisticsBefore.backendApplications + 1);
|
||||
CHECK(statisticsAfter.innerIterations == 0);
|
||||
CHECK(statisticsAfter.operatorBindings == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Preconditioner Refresh Is Explicit And Dependency Aware",
|
||||
tags::preconditioning_runtime_unit
|
||||
) {
|
||||
preconditioning_runtime_test::Problem problem;
|
||||
auto preconditioner = preconditioning::prepare(problem, preconditioning::makeIdentityPlan(problem));
|
||||
mfem::Vector residual(problem.EquationSize());
|
||||
mfem::Vector correction(problem.StateSize());
|
||||
residual = 1.0;
|
||||
correction = 0.0;
|
||||
|
||||
const auto noChange = preconditioner.Refresh();
|
||||
CHECK_FALSE(noChange.changes.Any());
|
||||
CHECK_FALSE(noChange.DidAnyWork());
|
||||
CHECK(preconditioner.GetStatistics().noOpRefreshes == 1);
|
||||
|
||||
problem.AdvanceDensity();
|
||||
CHECK_FALSE(preconditioner.IsCurrent());
|
||||
CHECK_THROWS_AS(preconditioner.Mult(residual, correction), std::logic_error);
|
||||
|
||||
const auto linearizationRefresh = preconditioner.Refresh();
|
||||
CHECK(linearizationRefresh.changes.linearization);
|
||||
CHECK_FALSE(linearizationRefresh.changes.discretization);
|
||||
CHECK_FALSE(linearizationRefresh.changes.geometry);
|
||||
CHECK_FALSE(linearizationRefresh.DidAnyWork());
|
||||
CHECK(preconditioner.IsCurrent());
|
||||
CHECK(preconditioner.GetStatistics().refreshes == 1);
|
||||
CHECK(preconditioner.GetStatistics().componentRefreshes == 0);
|
||||
|
||||
problem.AdvanceGeometry();
|
||||
const auto geometryRefresh = preconditioner.Refresh();
|
||||
CHECK(geometryRefresh.changes.geometry);
|
||||
CHECK_FALSE(geometryRefresh.changes.linearization);
|
||||
CHECK(preconditioner.GetStatistics().refreshes == 2);
|
||||
|
||||
problem.SetPrepared(false);
|
||||
CHECK_FALSE(preconditioner.IsCurrent());
|
||||
CHECK_THROWS_AS(preconditioner.Refresh(), std::logic_error);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Preconditioner Refresh Touches Only Components With Changed Dependencies",
|
||||
tags::preconditioning_runtime_unit
|
||||
) {
|
||||
preconditioning_runtime_test::Problem problem;
|
||||
auto preconditioner = preconditioning::prepare(problem, makeSelectiveRefreshPlan());
|
||||
|
||||
problem.AdvanceGeometry();
|
||||
const auto geometryRefresh = preconditioner.Refresh();
|
||||
CHECK(geometryRefresh.changes.geometry);
|
||||
CHECK_FALSE(geometryRefresh.changes.linearization);
|
||||
CHECK_FALSE(geometryRefresh.DidAnyWork());
|
||||
CHECK(geometryRefresh.refreshedComponents == 0);
|
||||
|
||||
problem.AdvanceDensity();
|
||||
const auto linearizationRefresh = preconditioner.Refresh();
|
||||
CHECK_FALSE(linearizationRefresh.changes.geometry);
|
||||
CHECK(linearizationRefresh.changes.linearization);
|
||||
CHECK(linearizationRefresh.DidAnyWork());
|
||||
CHECK(linearizationRefresh.refreshedComponents == 1);
|
||||
CHECK(preconditioner.GetStatistics().componentRefreshes == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Preconditioner Application Requires Preallocated Compatible Vectors",
|
||||
tags::preconditioning_runtime_unit
|
||||
) {
|
||||
preconditioning_runtime_test::Problem problem;
|
||||
auto preconditioner = preconditioning::prepare(problem, preconditioning::makeIdentityPlan(problem));
|
||||
mfem::Vector residual(problem.EquationSize());
|
||||
mfem::Vector missingCorrection;
|
||||
mfem::IdentityOperator wrongOperator(problem.StateSize() - 1);
|
||||
|
||||
CHECK_THROWS_AS(preconditioner.Mult(residual, missingCorrection), std::invalid_argument);
|
||||
CHECK_THROWS_AS(preconditioner.SetOperator(wrongOperator), std::invalid_argument);
|
||||
|
||||
preconditioning_runtime_test::Problem unpreparedProblem;
|
||||
unpreparedProblem.SetPrepared(false);
|
||||
CHECK_THROWS_AS(
|
||||
preconditioning::prepare(unpreparedProblem, preconditioning::makeIdentityPlan(unpreparedProblem)),
|
||||
std::logic_error
|
||||
);
|
||||
}
|
||||
419
tests/preconditioning/stellar_structure.cpp
Normal file
419
tests/preconditioning/stellar_structure.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <numbers>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace backend = mean_field::preconditioning::backend;
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
|
||||
struct MaterialValue final : blocks::value_block_base { };
|
||||
struct GravityValue final : blocks::value_block_base { };
|
||||
struct MaterialResidual final : blocks::residual_block_base { };
|
||||
struct GravityResidual final : blocks::residual_block_base { };
|
||||
|
||||
using MockForm = blocks::block_form<
|
||||
blocks::type_list<MaterialValue, GravityValue>,
|
||||
blocks::type_list<MaterialResidual, GravityResidual>>;
|
||||
using MockJacobian = blocks::type_list<
|
||||
blocks::block_row<MaterialResidual, MaterialValue, GravityValue>,
|
||||
blocks::block_row<GravityResidual, MaterialValue, GravityValue>>;
|
||||
using MockMaterialComponent = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<MaterialValue>,
|
||||
blocks::type_list<MaterialResidual>,
|
||||
blocks::type_list<preconditioning::Coupling<MaterialResidual, MaterialValue>>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
backend::Identity,
|
||||
preconditioning::NoPreparationDependencies>;
|
||||
using MockGravityComponent = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<GravityValue>,
|
||||
blocks::type_list<GravityResidual>,
|
||||
blocks::type_list<preconditioning::Coupling<GravityResidual, GravityValue>>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
backend::Identity,
|
||||
preconditioning::NoPreparationDependencies>;
|
||||
using MockStructure = preconditioning::StellarStructureBlock<
|
||||
MockMaterialComponent,
|
||||
MockGravityComponent,
|
||||
MockForm,
|
||||
MockJacobian,
|
||||
preconditioning::ApproximateStellarBlockLDU>;
|
||||
|
||||
template <typename MaterialComponent, typename GravityComponent>
|
||||
concept MockComponentsCanCompose = requires {
|
||||
typename preconditioning::StellarStructureBlock<
|
||||
MaterialComponent, GravityComponent, MockForm, MockJacobian, preconditioning::IndependentStellarSubsystems>;
|
||||
};
|
||||
|
||||
class KnownCrossCouplings final {
|
||||
public:
|
||||
[[nodiscard]] constexpr int MaterialSize() const noexcept {
|
||||
return 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr int GravitySize() const noexcept {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ApplyMaterialToGravity(
|
||||
const mfem::Vector &materialDirection,
|
||||
mfem::Vector &gravityAction
|
||||
) const {
|
||||
gravityAction(0) = 3.0 * materialDirection(0);
|
||||
}
|
||||
|
||||
void ApplyGravityToMaterial(
|
||||
const mfem::Vector &gravityDirection,
|
||||
mfem::Vector &materialAction
|
||||
) const {
|
||||
materialAction(0) = 7.0 * gravityDirection(0);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Policy>
|
||||
[[nodiscard]] mfem::Vector applyKnownFactorization(
|
||||
Policy policy,
|
||||
preconditioning::StellarStructureFactorizationStatistics *statistics = nullptr
|
||||
) {
|
||||
mfem::Vector materialDiagonal(1);
|
||||
mfem::Vector gravityDiagonal(1);
|
||||
materialDiagonal(0) = 2.0;
|
||||
gravityDiagonal(0) = 5.0;
|
||||
auto materialInverse = backend::prepare(backend::Diagonal{}, materialDiagonal);
|
||||
auto gravityInverse = backend::prepare(backend::Diagonal{}, gravityDiagonal);
|
||||
const KnownCrossCouplings couplings;
|
||||
preconditioning::StellarStructureFactorizationOperator<Policy, KnownCrossCouplings> factorization(
|
||||
policy, materialInverse, gravityInverse, couplings
|
||||
);
|
||||
mfem::Vector rightHandSide(2);
|
||||
mfem::Vector action(2);
|
||||
rightHandSide(0) = 11.0;
|
||||
rightHandSide(1) = 13.0;
|
||||
factorization.Mult(rightHandSide, action);
|
||||
if (statistics != nullptr) {
|
||||
*statistics = factorization.GetStatistics();
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
using PolytropicModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::integral::FixedTotalMass,
|
||||
mean_field::constraint::FixedCentralDensity>>;
|
||||
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
||||
using MaterialComponent =
|
||||
decltype(preconditioning::materialSurfaceBlock(std::declval<const PolytropicProblem &>()));
|
||||
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
||||
using GravityComponent =
|
||||
preconditioning::GravityFieldBlock<backend::Diagonal, FixedAMG, preconditioning::GravityApproximateLDU>;
|
||||
using PolytropicStructure = decltype(preconditioning::stellarStructureBlock(
|
||||
std::declval<const PolytropicProblem &>(),
|
||||
std::declval<MaterialComponent>(),
|
||||
std::declval<GravityComponent>(),
|
||||
preconditioning::IndependentStellarSubsystems{}
|
||||
));
|
||||
|
||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies
|
||||
makeDependencies(const std::uint64_t revision = 1) {
|
||||
return {
|
||||
.discretization = {.identity = 9101, .revision = 1},
|
||||
.density = {.identity = 9103, .revision = revision},
|
||||
.surfaceDeformation = {.identity = 9107, .revision = revision},
|
||||
.gravityGradient = {.identity = 9111, .revision = revision},
|
||||
.gravityPotential = {.identity = 9117, .revision = revision},
|
||||
.enthalpy = {.identity = 9123, .revision = revision},
|
||||
.bernoulliConstant = {.identity = 9129, .revision = revision},
|
||||
.rotation = {.identity = 9131, .revision = revision},
|
||||
.targetMass = {.identity = 9137, .revision = 1}
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::physics::RigidRotation zeroRotation() {
|
||||
mfem::Vector angularVelocity(3);
|
||||
mfem::Vector center(3);
|
||||
angularVelocity = 0.0;
|
||||
center = 0.0;
|
||||
return {angularVelocity, center};
|
||||
}
|
||||
|
||||
[[nodiscard]] double relativeError(
|
||||
const mfem::Vector &left,
|
||||
const mfem::Vector &right
|
||||
) {
|
||||
mfem::Vector difference(left);
|
||||
difference -= right;
|
||||
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Structure Composition Derives Both Cross-Subsystem Graphs",
|
||||
"[preconditioning][stellar_structure][unit][type_contract]"
|
||||
) {
|
||||
using ExpectedMaterialToGravity = blocks::type_list<
|
||||
preconditioning::Coupling<blocks::gravity::gradient::residual, blocks::surface_deformation::parameters::value>,
|
||||
preconditioning::Coupling<blocks::gravity::poisson::residual, blocks::density::mass::value>,
|
||||
preconditioning::Coupling<blocks::gravity::poisson::residual, blocks::surface_deformation::parameters::value>>;
|
||||
using ExpectedGravityToMaterial = blocks::type_list<
|
||||
preconditioning::Coupling<
|
||||
blocks::surface_deformation::shape_equilibrium::residual, blocks::gravity::gradient::value>,
|
||||
preconditioning::Coupling<blocks::enthalpy::specific::residual, blocks::gravity::poisson::value>>;
|
||||
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<PolytropicStructure>);
|
||||
STATIC_CHECK(std::same_as<typename PolytropicStructure::MaterialToGravityCouplings, ExpectedMaterialToGravity>);
|
||||
STATIC_CHECK(std::same_as<typename PolytropicStructure::GravityToMaterialCouplings, ExpectedGravityToMaterial>);
|
||||
STATIC_CHECK(PolytropicStructure::MaterialToGravityCouplings::size == 3);
|
||||
STATIC_CHECK(PolytropicStructure::GravityToMaterialCouplings::size == 2);
|
||||
STATIC_CHECK(PolytropicStructure::RequiredCouplings::size == 16);
|
||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename PolytropicStructure::BackendType>);
|
||||
|
||||
using FixedMassIdentity = preconditioning::IdentityBlock<
|
||||
blocks::fixed_total_mass::mass_normalization::value, blocks::fixed_total_mass::mass_normalization::residual>;
|
||||
using FixedCentralDensityIdentity = preconditioning::IdentityBlock<
|
||||
blocks::fixed_central_density::central_value::value, blocks::fixed_central_density::central_value::residual>;
|
||||
using CompletePlan =
|
||||
preconditioning::PreconditionerPlan<PolytropicStructure, FixedMassIdentity, FixedCentralDensityIdentity>;
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<CompletePlan, typename PolytropicProblem::FormType>);
|
||||
STATIC_CHECK(
|
||||
preconditioning::CompatiblePreconditionerFor<
|
||||
CompletePlan, typename PolytropicProblem::FormType, typename PolytropicProblem::JacobianFormType>
|
||||
);
|
||||
|
||||
STATIC_CHECK(MockComponentsCanCompose<MockMaterialComponent, MockGravityComponent>);
|
||||
STATIC_CHECK_FALSE(MockComponentsCanCompose<MockMaterialComponent, MockMaterialComponent>);
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<MockStructure>);
|
||||
STATIC_CHECK(MockStructure::MaterialToGravityCouplings::size == 1);
|
||||
STATIC_CHECK(MockStructure::GravityToMaterialCouplings::size == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Structure Factorizations Preserve Independent Triangular And Approximate LDU Algebra",
|
||||
"[preconditioning][stellar_structure][unit][factorization]"
|
||||
) {
|
||||
const auto check = [](const mfem::Vector &value, const std::array<double, 2> expected) {
|
||||
REQUIRE(value.Size() == 2);
|
||||
CHECK(value(0) == Catch::Approx(expected[0]).margin(2.0e-14));
|
||||
CHECK(value(1) == Catch::Approx(expected[1]).margin(2.0e-14));
|
||||
mfem::Vector expectedVector(2);
|
||||
expectedVector(0) = expected[0];
|
||||
expectedVector(1) = expected[1];
|
||||
CHECK(relativeError(value, expectedVector) <= 2.0e-14);
|
||||
};
|
||||
|
||||
check(applyKnownFactorization(preconditioning::IndependentStellarSubsystems{}), {5.5, 2.6});
|
||||
check(applyKnownFactorization(preconditioning::MaterialThenGravityTriangular{}), {5.5, -0.7});
|
||||
check(applyKnownFactorization(preconditioning::GravityThenMaterialTriangular{}), {-3.6, 2.6});
|
||||
|
||||
preconditioning::StellarStructureFactorizationStatistics statistics;
|
||||
check(applyKnownFactorization(preconditioning::ApproximateStellarBlockLDU{}, &statistics), {7.95, -0.7});
|
||||
CHECK(statistics.applications == 1);
|
||||
CHECK(statistics.materialSurfaceInverseApplications == 2);
|
||||
CHECK(statistics.gravityInverseApplications == 1);
|
||||
CHECK(statistics.materialToGravityApplications == 1);
|
||||
CHECK(statistics.gravityToMaterialApplications == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Structure Cross Actions Are Exact Restricted Jacobian Actions And Compose Prepared Blocks",
|
||||
"[preconditioning][stellar_structure][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
const utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
constexpr double radius = utils::RADIUS;
|
||||
constexpr double mass = utils::MASS;
|
||||
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||
const auto stellarModel = model::StellarModel(
|
||||
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(stellarModel, finiteElements);
|
||||
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
|
||||
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
|
||||
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
|
||||
|
||||
preconditioning::StellarStructureCrossJacobianOperator cross(physical);
|
||||
mfem::Vector direction(cross.Width());
|
||||
for (int index = 0; index < direction.Size(); ++index) {
|
||||
direction(index) = 0.01 * std::sin(0.29 * static_cast<double>(index + 1));
|
||||
}
|
||||
mfem::Vector crossAction(cross.Height());
|
||||
cross.Mult(direction, crossAction);
|
||||
|
||||
const mfem::Vector materialDirection(direction.GetData(), cross.MaterialSize());
|
||||
const mfem::Vector gravityDirection(direction.GetData() + cross.MaterialSize(), cross.GravitySize());
|
||||
const auto &materialOffsets = cross.GetMaterialOffsets();
|
||||
const auto &gravityOffsets = cross.GetGravityOffsets();
|
||||
|
||||
mfem::Vector materialOnlyDirection(physical.Width());
|
||||
materialOnlyDirection = 0.0;
|
||||
auto materialOnlyView = physical.GetRootManifest().directionView(materialOnlyDirection);
|
||||
mfem::Vector materialDensity = materialOnlyView.block(blocks::density_field.mass_term);
|
||||
mfem::Vector materialSurface = materialOnlyView.block(blocks::surface_deformation_field.parameters_term);
|
||||
mfem::Vector materialEnthalpy = materialOnlyView.block(blocks::enthalpy_field.specific_term);
|
||||
const mfem::Vector sourceDensity(
|
||||
const_cast<mfem::real_t *>(materialDirection.GetData()) + materialOffsets[0],
|
||||
materialOffsets[1] - materialOffsets[0]
|
||||
);
|
||||
const mfem::Vector sourceSurface(
|
||||
const_cast<mfem::real_t *>(materialDirection.GetData()) + materialOffsets[1],
|
||||
materialOffsets[2] - materialOffsets[1]
|
||||
);
|
||||
const mfem::Vector sourceEnthalpy(
|
||||
const_cast<mfem::real_t *>(materialDirection.GetData()) + materialOffsets[2],
|
||||
materialOffsets[3] - materialOffsets[2]
|
||||
);
|
||||
materialDensity = sourceDensity;
|
||||
materialSurface = sourceSurface;
|
||||
materialEnthalpy = sourceEnthalpy;
|
||||
mfem::Vector materialOnlyAction;
|
||||
physical.Mult(materialOnlyDirection, materialOnlyAction);
|
||||
const auto materialOnlyActionView = physical.GetRootManifest().residualView(materialOnlyAction);
|
||||
|
||||
mfem::Vector gravityOnlyDirection(physical.Width());
|
||||
gravityOnlyDirection = 0.0;
|
||||
auto gravityOnlyView = physical.GetRootManifest().directionView(gravityOnlyDirection);
|
||||
mfem::Vector gravityGradient = gravityOnlyView.block(blocks::gravity_field.gradient_term);
|
||||
mfem::Vector gravityPotential = gravityOnlyView.block(blocks::gravity_field.poisson_term);
|
||||
const mfem::Vector sourceGravityGradient(
|
||||
const_cast<mfem::real_t *>(gravityDirection.GetData()) + gravityOffsets[0],
|
||||
gravityOffsets[1] - gravityOffsets[0]
|
||||
);
|
||||
const mfem::Vector sourceGravityPotential(
|
||||
const_cast<mfem::real_t *>(gravityDirection.GetData()) + gravityOffsets[1],
|
||||
gravityOffsets[2] - gravityOffsets[1]
|
||||
);
|
||||
gravityGradient = sourceGravityGradient;
|
||||
gravityPotential = sourceGravityPotential;
|
||||
mfem::Vector gravityOnlyAction;
|
||||
physical.Mult(gravityOnlyDirection, gravityOnlyAction);
|
||||
const auto gravityOnlyActionView = physical.GetRootManifest().residualView(gravityOnlyAction);
|
||||
|
||||
mfem::Vector expected(cross.Height());
|
||||
expected = 0.0;
|
||||
expected.SetVector(gravityOnlyActionView.block(blocks::density_field.mass_term), materialOffsets[0]);
|
||||
expected.SetVector(
|
||||
gravityOnlyActionView.block(blocks::surface_deformation_field.shape_equilibrium_term), materialOffsets[1]
|
||||
);
|
||||
expected.SetVector(gravityOnlyActionView.block(blocks::enthalpy_field.specific_term), materialOffsets[2]);
|
||||
expected.SetVector(
|
||||
materialOnlyActionView.block(blocks::gravity_field.gradient_term), cross.MaterialSize() + gravityOffsets[0]
|
||||
);
|
||||
expected.SetVector(
|
||||
materialOnlyActionView.block(blocks::gravity_field.poisson_term), cross.MaterialSize() + gravityOffsets[1]
|
||||
);
|
||||
|
||||
const mfem::Vector expectedMaterial(expected.GetData(), cross.MaterialSize());
|
||||
const mfem::Vector expectedGravity(expected.GetData() + cross.MaterialSize(), cross.GravitySize());
|
||||
const mfem::Vector expectedDensity(
|
||||
expectedMaterial.GetData() + materialOffsets[0], materialOffsets[1] - materialOffsets[0]
|
||||
);
|
||||
const mfem::Vector expectedSurface(
|
||||
expectedMaterial.GetData() + materialOffsets[1], materialOffsets[2] - materialOffsets[1]
|
||||
);
|
||||
const mfem::Vector expectedEnthalpy(
|
||||
expectedMaterial.GetData() + materialOffsets[2], materialOffsets[3] - materialOffsets[2]
|
||||
);
|
||||
const mfem::Vector expectedGravityGradient(
|
||||
expectedGravity.GetData() + gravityOffsets[0], gravityOffsets[1] - gravityOffsets[0]
|
||||
);
|
||||
const mfem::Vector expectedGravityPotential(
|
||||
expectedGravity.GetData() + gravityOffsets[1], gravityOffsets[2] - gravityOffsets[1]
|
||||
);
|
||||
|
||||
const mfem::Vector crossMaterial(crossAction.GetData(), cross.MaterialSize());
|
||||
const mfem::Vector crossGravity(crossAction.GetData() + cross.MaterialSize(), cross.GravitySize());
|
||||
const mfem::Vector crossDensity(
|
||||
crossMaterial.GetData() + materialOffsets[0], materialOffsets[1] - materialOffsets[0]
|
||||
);
|
||||
const mfem::Vector crossSurface(
|
||||
crossMaterial.GetData() + materialOffsets[1], materialOffsets[2] - materialOffsets[1]
|
||||
);
|
||||
const mfem::Vector crossEnthalpy(
|
||||
crossMaterial.GetData() + materialOffsets[2], materialOffsets[3] - materialOffsets[2]
|
||||
);
|
||||
const mfem::Vector crossGravityGradient(
|
||||
crossGravity.GetData() + gravityOffsets[0], gravityOffsets[1] - gravityOffsets[0]
|
||||
);
|
||||
const mfem::Vector crossGravityPotential(
|
||||
crossGravity.GetData() + gravityOffsets[1], gravityOffsets[2] - gravityOffsets[1]
|
||||
);
|
||||
|
||||
INFO(
|
||||
"gravity-to-material density-row error = " << relativeError(crossDensity, expectedDensity)
|
||||
<< ", actual norm = " << crossDensity.Norml2()
|
||||
<< ", expected norm = " << expectedDensity.Norml2()
|
||||
);
|
||||
INFO(
|
||||
"gravity-to-material surface-row error = " << relativeError(crossSurface, expectedSurface)
|
||||
<< ", actual norm = " << crossSurface.Norml2()
|
||||
<< ", expected norm = " << expectedSurface.Norml2()
|
||||
);
|
||||
INFO(
|
||||
"gravity-to-material enthalpy-row error = " << relativeError(crossEnthalpy, expectedEnthalpy)
|
||||
<< ", actual norm = " << crossEnthalpy.Norml2()
|
||||
<< ", expected norm = " << expectedEnthalpy.Norml2()
|
||||
);
|
||||
INFO(
|
||||
"material-to-gravity gradient-row error = " << relativeError(crossGravityGradient, expectedGravityGradient)
|
||||
<< ", actual norm = " << crossGravityGradient.Norml2()
|
||||
<< ", expected norm = " << expectedGravityGradient.Norml2()
|
||||
);
|
||||
INFO(
|
||||
"material-to-gravity Poisson-row error = " << relativeError(crossGravityPotential, expectedGravityPotential)
|
||||
<< ", actual norm = " << crossGravityPotential.Norml2()
|
||||
<< ", expected norm = " << expectedGravityPotential.Norml2()
|
||||
);
|
||||
CHECK(relativeError(crossDensity, expectedDensity) <= 2.0e-12);
|
||||
CHECK(relativeError(crossSurface, expectedSurface) <= 2.0e-12);
|
||||
CHECK(relativeError(crossEnthalpy, expectedEnthalpy) <= 2.0e-12);
|
||||
CHECK(relativeError(crossGravityGradient, expectedGravityGradient) <= 2.0e-12);
|
||||
CHECK(relativeError(crossGravityPotential, expectedGravityPotential) <= 2.0e-12);
|
||||
CHECK(relativeError(crossAction, expected) <= 2.0e-12);
|
||||
|
||||
auto materialBlock = preconditioning::materialSurfaceBlock(problem);
|
||||
auto gravityBlock = preconditioning::GravityFieldBlock(
|
||||
backend::Diagonal{}, FixedAMG{backend::FixedCycles{.cycles = 1}}, preconditioning::GravityApproximateLDU{}
|
||||
);
|
||||
auto structure = preconditioning::stellarStructureBlock(
|
||||
problem, materialBlock, gravityBlock, preconditioning::ApproximateStellarBlockLDU{}
|
||||
);
|
||||
auto prepared = preconditioning::prepare(problem, structure);
|
||||
mfem::Vector rightHandSide(prepared.Width());
|
||||
mfem::Vector correction(prepared.Height());
|
||||
for (int index = 0; index < rightHandSide.Size(); ++index) {
|
||||
rightHandSide(index) = std::cos(0.17 * static_cast<double>(index + 1));
|
||||
}
|
||||
prepared.Mult(rightHandSide, correction);
|
||||
for (int index = 0; index < correction.Size(); ++index) {
|
||||
REQUIRE(std::isfinite(correction(index)));
|
||||
}
|
||||
const auto &statistics = prepared.GetFactorization().GetStatistics();
|
||||
CHECK(statistics.applications == 1);
|
||||
CHECK(statistics.materialSurfaceInverseApplications == 2);
|
||||
CHECK(statistics.gravityInverseApplications == 1);
|
||||
CHECK(statistics.materialToGravityApplications == 1);
|
||||
CHECK(statistics.gravityToMaterialApplications == 1);
|
||||
|
||||
const auto unchanged = prepared.Refresh();
|
||||
CHECK_FALSE(unchanged.DidAnyWork());
|
||||
CHECK(prepared.IsCurrent());
|
||||
}
|
||||
Reference in New Issue
Block a user