feat(libmeanfield): centrifugal + pressure
This commit is contained in:
@@ -1,25 +1,27 @@
|
||||
#include <algorithm>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
|
||||
|
||||
TEST_CASE("STROID Volume vs sphere", tags::geometry & tags::volume) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
TEST_CASE(
|
||||
"STROID Volume vs sphere",
|
||||
tags::geometry &tags::volume
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
const double analytic_vol = (4.0 / 3.0) * M_PI * std::pow(utils::RADIUS, 3);
|
||||
const double stroid_vol = analysis::get_mesh_volume(f);
|
||||
const double stroid_vol = analysis::get_mesh_volume(f);
|
||||
|
||||
double s = analytic_vol / stroid_vol;
|
||||
double s = analytic_vol / stroid_vol;
|
||||
CHECK_THAT(stroid_vol, Catch::Matchers::WithinRel(analytic_vol, 1e-6));
|
||||
}
|
||||
1394
tests/integrators/centrifugal.cpp
Normal file
1394
tests/integrators/centrifugal.cpp
Normal file
File diff suppressed because it is too large
Load Diff
931
tests/integrators/gravity.cpp
Normal file
931
tests/integrators/gravity.cpp
Normal file
@@ -0,0 +1,931 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Force Integrator Jacobian Matches Residual Linearization",
|
||||
tags::unit &tags::solver &tags::integrator &tags::gravity
|
||||
) {
|
||||
constexpr int dim = 3;
|
||||
constexpr double finite_difference_step = 1.0e-3;
|
||||
constexpr double jacobian_tolerance = 1.0e-8;
|
||||
constexpr double zero_tolerance = 1.0e-14;
|
||||
|
||||
constexpr int velocity_block =
|
||||
solver::block_index(solver::FieldBlock::velocity);
|
||||
constexpr int density_block =
|
||||
solver::block_index(solver::FieldBlock::density);
|
||||
constexpr int gravity_gradient_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_gradient);
|
||||
constexpr int gravity_potential_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_potential);
|
||||
constexpr int displacement_block =
|
||||
solver::block_index(solver::FieldBlock::displacement);
|
||||
constexpr int block_count = solver::field_block_count;
|
||||
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(
|
||||
1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0
|
||||
);
|
||||
|
||||
mfem::H1_FECollection velocity_fec(2, dim);
|
||||
mfem::L2_FECollection density_fec(1, dim);
|
||||
mfem::RT_FECollection gravity_gradient_fec(1, dim);
|
||||
mfem::L2_FECollection gravity_potential_fec(1, dim);
|
||||
mfem::H1_FECollection displacement_fec(2, dim);
|
||||
|
||||
mfem::FiniteElementSpace velocity_fes(
|
||||
&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
|
||||
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
|
||||
mfem::FiniteElementSpace gravity_potential_fes(
|
||||
&mesh, &gravity_potential_fec
|
||||
);
|
||||
mfem::FiniteElementSpace displacement_fes(
|
||||
&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
|
||||
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
|
||||
INFO(
|
||||
std::format(
|
||||
"Domain mapping is has displacement field: {}",
|
||||
domain_mapper.HasDisplacementField()
|
||||
)
|
||||
);
|
||||
INFO(
|
||||
std::format(
|
||||
"Domain mapping is identity: {}", domain_mapper.CalcIsIdentity()
|
||||
)
|
||||
);
|
||||
|
||||
REQUIRE(domain_mapper.CalcIsIdentity());
|
||||
|
||||
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
|
||||
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
|
||||
const mfem::FiniteElement *gravity_gradient_element =
|
||||
gravity_gradient_fes.GetFE(0);
|
||||
const mfem::FiniteElement *gravity_potential_element =
|
||||
gravity_potential_fes.GetFE(0);
|
||||
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
|
||||
mfem::ElementTransformation *transformation =
|
||||
mesh.GetElementTransformation(0);
|
||||
|
||||
const int velocity_dofs_count = velocity_element->GetDof();
|
||||
const int density_dofs_count = density_element->GetDof();
|
||||
const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof();
|
||||
const int gravity_potential_dofs_count =
|
||||
gravity_potential_element->GetDof();
|
||||
const int displacement_dofs_count = displacement_element->GetDof();
|
||||
const int velocity_size = dim * velocity_dofs_count;
|
||||
const int displacement_size = dim * displacement_dofs_count;
|
||||
|
||||
mfem::Vector velocity_dofs(velocity_size);
|
||||
mfem::Vector density_dofs(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_dofs(gravity_gradient_dofs_count);
|
||||
mfem::Vector gravity_potential_dofs(gravity_potential_dofs_count);
|
||||
mfem::Vector displacement_dofs(displacement_size);
|
||||
velocity_dofs = 0.0;
|
||||
gravity_potential_dofs = 0.0;
|
||||
displacement_dofs = 0.0;
|
||||
|
||||
for (int i = 0; i < density_dofs_count; ++i) {
|
||||
density_dofs(i) = 0.8 + 0.07 * static_cast<double>(i + 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < gravity_gradient_dofs_count; ++i) {
|
||||
const double sign = i % 2 == 0 ? 1.0 : -1.0;
|
||||
gravity_gradient_dofs(i) = sign * 0.04 * static_cast<double>(i + 1);
|
||||
}
|
||||
|
||||
mfem::Vector density_direction(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_direction(gravity_gradient_dofs_count);
|
||||
mfem::Vector velocity_direction(velocity_size);
|
||||
mfem::Vector displacement_direction(displacement_size);
|
||||
|
||||
for (int i = 0; i < density_dofs_count; ++i) {
|
||||
density_direction(i) = 0.13 - 0.02 * static_cast<double>(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < gravity_gradient_dofs_count; ++i) {
|
||||
const double sign = i % 3 == 0 ? -1.0 : 1.0;
|
||||
gravity_gradient_direction(i) =
|
||||
sign * (0.03 + 0.005 * static_cast<double>(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < velocity_size; ++i) {
|
||||
velocity_direction(i) = 0.01 * static_cast<double>(i + 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < displacement_size; ++i) {
|
||||
displacement_direction(i) = -0.008 * static_cast<double>(i + 1);
|
||||
}
|
||||
|
||||
mfem::Array<const mfem::FiniteElement *> elements(block_count);
|
||||
elements[velocity_block] = velocity_element;
|
||||
elements[density_block] = density_element;
|
||||
elements[gravity_gradient_block] = gravity_gradient_element;
|
||||
elements[gravity_potential_block] = gravity_potential_element;
|
||||
elements[displacement_block] = displacement_element;
|
||||
|
||||
mfem::Array<const mfem::Vector *> element_state(block_count);
|
||||
element_state[velocity_block] = &velocity_dofs;
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
element_state[gravity_potential_block] = &gravity_potential_dofs;
|
||||
element_state[displacement_block] = &displacement_dofs;
|
||||
|
||||
mfem::Vector velocity_residual(velocity_size);
|
||||
mfem::Vector density_residual(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count);
|
||||
mfem::Vector gravity_potential_residual(gravity_potential_dofs_count);
|
||||
mfem::Vector displacement_residual(displacement_size);
|
||||
|
||||
mfem::Array<mfem::Vector *> element_residual(block_count);
|
||||
element_residual[velocity_block] = &velocity_residual;
|
||||
element_residual[density_block] = &density_residual;
|
||||
element_residual[gravity_gradient_block] = &gravity_gradient_residual;
|
||||
element_residual[gravity_potential_block] = &gravity_potential_residual;
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const int maximum_order = std::max(
|
||||
velocity_element->GetOrder(),
|
||||
std::max(
|
||||
density_element->GetOrder(), gravity_gradient_element->GetOrder()
|
||||
)
|
||||
);
|
||||
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(
|
||||
velocity_element->GetGeomType(), 2 * maximum_order + 8
|
||||
);
|
||||
integrator.SetIntegrationRule(integration_rule);
|
||||
|
||||
mfem::DenseMatrix dv_dv(velocity_size, velocity_size);
|
||||
mfem::DenseMatrix dv_drho(velocity_size, density_dofs_count);
|
||||
mfem::DenseMatrix dv_dgrad_phi(velocity_size, gravity_gradient_dofs_count);
|
||||
mfem::DenseMatrix dv_ddisplacement(velocity_size, displacement_size);
|
||||
dv_dv = 1.0;
|
||||
dv_drho = 1.0;
|
||||
dv_dgrad_phi = 1.0;
|
||||
dv_ddisplacement = 1.0;
|
||||
|
||||
mfem::Array2D<mfem::DenseMatrix *> element_matrices(
|
||||
block_count, block_count
|
||||
);
|
||||
|
||||
for (int row = 0; row < block_count; ++row) {
|
||||
for (int column = 0; column < block_count; ++column) {
|
||||
element_matrices(row, column) = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
element_matrices(velocity_block, velocity_block) = &dv_dv;
|
||||
element_matrices(velocity_block, density_block) = &dv_drho;
|
||||
element_matrices(velocity_block, gravity_gradient_block) = &dv_dgrad_phi;
|
||||
element_matrices(velocity_block, displacement_block) = &dv_ddisplacement;
|
||||
|
||||
integrator.AssembleElementGrad(
|
||||
elements, *transformation, element_state, element_matrices
|
||||
);
|
||||
|
||||
auto assemble_velocity_residual =
|
||||
[&](const mfem::Vector &density_state,
|
||||
const mfem::Vector &gravity_gradient_state) {
|
||||
element_state[density_block] = &density_state;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_state;
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
return mfem::Vector(velocity_residual);
|
||||
};
|
||||
|
||||
auto relative_error = [](mfem::Vector computed,
|
||||
const mfem::Vector &reference) {
|
||||
computed -= reference;
|
||||
return computed.Norml2() / std::max(reference.Norml2(), 1.0e-30);
|
||||
};
|
||||
|
||||
mfem::Vector density_plus(density_dofs);
|
||||
mfem::Vector density_minus(density_dofs);
|
||||
density_plus.Add(finite_difference_step, density_direction);
|
||||
density_minus.Add(-finite_difference_step, density_direction);
|
||||
|
||||
mfem::Vector density_residual_plus =
|
||||
assemble_velocity_residual(density_plus, gravity_gradient_dofs);
|
||||
mfem::Vector density_residual_minus =
|
||||
assemble_velocity_residual(density_minus, gravity_gradient_dofs);
|
||||
mfem::Vector density_finite_difference(density_residual_plus);
|
||||
density_finite_difference -= density_residual_minus;
|
||||
density_finite_difference *= 0.5 / finite_difference_step;
|
||||
|
||||
mfem::Vector density_jacobian_action(velocity_size);
|
||||
dv_drho.Mult(density_direction, density_jacobian_action);
|
||||
|
||||
mfem::Vector gravity_gradient_plus(gravity_gradient_dofs);
|
||||
mfem::Vector gravity_gradient_minus(gravity_gradient_dofs);
|
||||
gravity_gradient_plus.Add(
|
||||
finite_difference_step, gravity_gradient_direction
|
||||
);
|
||||
gravity_gradient_minus.Add(
|
||||
-finite_difference_step, gravity_gradient_direction
|
||||
);
|
||||
|
||||
mfem::Vector gravity_residual_plus =
|
||||
assemble_velocity_residual(density_dofs, gravity_gradient_plus);
|
||||
mfem::Vector gravity_residual_minus =
|
||||
assemble_velocity_residual(density_dofs, gravity_gradient_minus);
|
||||
mfem::Vector gravity_finite_difference(gravity_residual_plus);
|
||||
gravity_finite_difference -= gravity_residual_minus;
|
||||
gravity_finite_difference *= 0.5 / finite_difference_step;
|
||||
|
||||
mfem::Vector gravity_jacobian_action(velocity_size);
|
||||
dv_dgrad_phi.Mult(gravity_gradient_direction, gravity_jacobian_action);
|
||||
|
||||
mfem::Vector combined_density_plus(density_dofs);
|
||||
mfem::Vector combined_density_minus(density_dofs);
|
||||
mfem::Vector combined_gravity_plus(gravity_gradient_dofs);
|
||||
mfem::Vector combined_gravity_minus(gravity_gradient_dofs);
|
||||
combined_density_plus.Add(finite_difference_step, density_direction);
|
||||
combined_density_minus.Add(-finite_difference_step, density_direction);
|
||||
combined_gravity_plus.Add(
|
||||
finite_difference_step, gravity_gradient_direction
|
||||
);
|
||||
combined_gravity_minus.Add(
|
||||
-finite_difference_step, gravity_gradient_direction
|
||||
);
|
||||
|
||||
mfem::Vector combined_residual_plus = assemble_velocity_residual(
|
||||
combined_density_plus, combined_gravity_plus
|
||||
);
|
||||
mfem::Vector combined_residual_minus = assemble_velocity_residual(
|
||||
combined_density_minus, combined_gravity_minus
|
||||
);
|
||||
mfem::Vector combined_finite_difference(combined_residual_plus);
|
||||
combined_finite_difference -= combined_residual_minus;
|
||||
combined_finite_difference *= 0.5 / finite_difference_step;
|
||||
|
||||
mfem::Vector combined_jacobian_action(density_jacobian_action);
|
||||
combined_jacobian_action += gravity_jacobian_action;
|
||||
|
||||
mfem::Vector inactive_velocity_action(velocity_size);
|
||||
mfem::Vector inactive_displacement_action(velocity_size);
|
||||
dv_dv.Mult(velocity_direction, inactive_velocity_action);
|
||||
dv_ddisplacement.Mult(displacement_direction, inactive_displacement_action);
|
||||
|
||||
const double density_relative_error =
|
||||
relative_error(density_finite_difference, density_jacobian_action);
|
||||
const double gravity_relative_error =
|
||||
relative_error(gravity_finite_difference, gravity_jacobian_action);
|
||||
const double combined_relative_error =
|
||||
relative_error(combined_finite_difference, combined_jacobian_action);
|
||||
|
||||
INFO("Density Jacobian relative error = " << density_relative_error);
|
||||
INFO(
|
||||
"Gravity-gradient Jacobian relative error = " << gravity_relative_error
|
||||
);
|
||||
INFO("Combined Jacobian relative error = " << combined_relative_error);
|
||||
|
||||
CHECK_THAT(
|
||||
density_relative_error,
|
||||
Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
gravity_relative_error,
|
||||
Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
combined_relative_error,
|
||||
Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
inactive_velocity_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, zero_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
inactive_displacement_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, zero_tolerance)
|
||||
);
|
||||
|
||||
mfem::Vector field_coupled_density_action(density_jacobian_action);
|
||||
|
||||
integrator.SetJacobianMode(integrators::GravityForceJacobianMode::minimal);
|
||||
dv_dv = 1.0;
|
||||
dv_drho = 1.0;
|
||||
dv_dgrad_phi = 1.0;
|
||||
dv_ddisplacement = 1.0;
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
integrator.AssembleElementGrad(
|
||||
elements, *transformation, element_state, element_matrices
|
||||
);
|
||||
|
||||
mfem::Vector minimal_density_action(velocity_size);
|
||||
mfem::Vector minimal_gravity_action(velocity_size);
|
||||
dv_drho.Mult(density_direction, minimal_density_action);
|
||||
dv_dgrad_phi.Mult(gravity_gradient_direction, minimal_gravity_action);
|
||||
|
||||
const double minimal_density_difference =
|
||||
relative_error(minimal_density_action, field_coupled_density_action);
|
||||
|
||||
INFO(
|
||||
"Minimal-mode density-block difference = " << minimal_density_difference
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
minimal_density_difference,
|
||||
Catch::Matchers::WithinAbs(0.0, zero_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
minimal_gravity_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, zero_tolerance)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Force Integrator Matches Manufactured Cartesian Load",
|
||||
tags::unit &tags::solver &tags::integrator &tags::gravity
|
||||
) {
|
||||
constexpr int dim = 3;
|
||||
constexpr double tolerance = 1.0e-12;
|
||||
|
||||
constexpr int velocity_block =
|
||||
solver::block_index(solver::FieldBlock::velocity);
|
||||
constexpr int density_block =
|
||||
solver::block_index(solver::FieldBlock::density);
|
||||
constexpr int gravity_gradient_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_gradient);
|
||||
constexpr int gravity_potential_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_potential);
|
||||
constexpr int displacement_block =
|
||||
solver::block_index(solver::FieldBlock::displacement);
|
||||
constexpr int block_count = solver::field_block_count;
|
||||
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(
|
||||
1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0
|
||||
);
|
||||
|
||||
mfem::H1_FECollection velocity_fec(1, dim);
|
||||
mfem::L2_FECollection density_fec(1, dim);
|
||||
mfem::RT_FECollection gravity_gradient_fec(0, dim);
|
||||
mfem::H1_FECollection displacement_fec(1, dim);
|
||||
|
||||
mfem::FiniteElementSpace velocity_fes(
|
||||
&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
|
||||
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
|
||||
mfem::FiniteElementSpace displacement_fes(
|
||||
&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
|
||||
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
|
||||
|
||||
REQUIRE(domain_mapper.CalcIsIdentity());
|
||||
|
||||
auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); };
|
||||
|
||||
auto reference_gravity_gradient = [](const mfem::Vector &x,
|
||||
mfem::Vector &gradient) {
|
||||
gradient.SetSize(3);
|
||||
gradient(0) = 2.0 * x(0);
|
||||
gradient(1) = 3.0 * x(1);
|
||||
gradient(2) = 4.0 * x(2);
|
||||
};
|
||||
|
||||
mfem::FunctionCoefficient density_coefficient(reference_density);
|
||||
mfem::VectorFunctionCoefficient gravity_gradient_coefficient(
|
||||
dim, reference_gravity_gradient
|
||||
);
|
||||
|
||||
mfem::GridFunction density(&density_fes);
|
||||
mfem::GridFunction gravity_gradient(&gravity_gradient_fes);
|
||||
density.ProjectCoefficient(density_coefficient);
|
||||
gravity_gradient.ProjectCoefficient(gravity_gradient_coefficient);
|
||||
|
||||
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
|
||||
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
|
||||
const mfem::FiniteElement *gravity_gradient_element =
|
||||
gravity_gradient_fes.GetFE(0);
|
||||
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
|
||||
mfem::ElementTransformation *transformation =
|
||||
mesh.GetElementTransformation(0);
|
||||
|
||||
const int velocity_dofs_count = velocity_element->GetDof();
|
||||
const int density_dofs_count = density_element->GetDof();
|
||||
const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof();
|
||||
const int displacement_dofs_count = displacement_element->GetDof();
|
||||
const int velocity_size = dim * velocity_dofs_count;
|
||||
const int displacement_size = dim * displacement_dofs_count;
|
||||
|
||||
mfem::Array<int> density_dof_indices;
|
||||
mfem::Array<int> gravity_gradient_dof_indices;
|
||||
mfem::Vector density_dofs;
|
||||
mfem::Vector gravity_gradient_dofs;
|
||||
density_fes.GetElementDofs(0, density_dof_indices);
|
||||
gravity_gradient_fes.GetElementVDofs(0, gravity_gradient_dof_indices);
|
||||
density.GetSubVector(density_dof_indices, density_dofs);
|
||||
gravity_gradient.GetSubVector(
|
||||
gravity_gradient_dof_indices, gravity_gradient_dofs
|
||||
);
|
||||
|
||||
REQUIRE(density_dofs.Size() == density_dofs_count);
|
||||
REQUIRE(gravity_gradient_dofs.Size() == gravity_gradient_dofs_count);
|
||||
|
||||
mfem::Vector velocity_dofs(velocity_size);
|
||||
mfem::Vector gravity_potential_dofs(density_dofs_count);
|
||||
mfem::Vector displacement_dofs(displacement_size);
|
||||
velocity_dofs = 0.0;
|
||||
gravity_potential_dofs = 0.0;
|
||||
displacement_dofs = 0.0;
|
||||
|
||||
mfem::Array<const mfem::FiniteElement *> elements(block_count);
|
||||
elements[velocity_block] = velocity_element;
|
||||
elements[density_block] = density_element;
|
||||
elements[gravity_gradient_block] = gravity_gradient_element;
|
||||
elements[gravity_potential_block] = density_element;
|
||||
elements[displacement_block] = displacement_element;
|
||||
|
||||
mfem::Array<const mfem::Vector *> element_state(block_count);
|
||||
element_state[velocity_block] = &velocity_dofs;
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
element_state[gravity_potential_block] = &gravity_potential_dofs;
|
||||
element_state[displacement_block] = &displacement_dofs;
|
||||
|
||||
mfem::Vector velocity_residual(velocity_size);
|
||||
mfem::Vector density_residual(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count);
|
||||
mfem::Vector gravity_potential_residual(density_dofs_count);
|
||||
mfem::Vector displacement_residual(displacement_size);
|
||||
|
||||
mfem::Array<mfem::Vector *> element_residual(block_count);
|
||||
element_residual[velocity_block] = &velocity_residual;
|
||||
element_residual[density_block] = &density_residual;
|
||||
element_residual[gravity_gradient_block] = &gravity_gradient_residual;
|
||||
element_residual[gravity_potential_block] = &gravity_potential_residual;
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const mfem::IntegrationRule &integration_rule =
|
||||
mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
|
||||
integrator.SetIntegrationRule(integration_rule);
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
|
||||
mfem::Vector reference_velocity_residual(velocity_residual);
|
||||
|
||||
auto residual_action = [&](const int component,
|
||||
const int coordinate_weight) {
|
||||
mfem::Vector test_dofs(velocity_size);
|
||||
mfem::Vector x_physical(dim);
|
||||
test_dofs = 0.0;
|
||||
|
||||
const mfem::IntegrationRule &velocity_nodes =
|
||||
velocity_element->GetNodes();
|
||||
|
||||
for (int i = 0; i < velocity_dofs_count; ++i) {
|
||||
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
|
||||
transformation->SetIntPoint(&node);
|
||||
domain_mapper.GetPhysicalPoint(*transformation, node, x_physical);
|
||||
test_dofs(i + component * velocity_dofs_count) =
|
||||
coordinate_weight < 0 ? 1.0 : x_physical(coordinate_weight);
|
||||
}
|
||||
|
||||
return test_dofs * velocity_residual;
|
||||
};
|
||||
|
||||
CHECK_THAT(
|
||||
residual_action(0, -1), Catch::Matchers::WithinAbs(5.0 / 3.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(1, -1), Catch::Matchers::WithinAbs(9.0 / 4.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(2, -1), Catch::Matchers::WithinAbs(3.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(0, 0), Catch::Matchers::WithinAbs(7.0 / 6.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(1, 1), Catch::Matchers::WithinAbs(3.0 / 2.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(2, 2), Catch::Matchers::WithinAbs(2.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(1, 0), Catch::Matchers::WithinAbs(5.0 / 4.0, tolerance)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
density_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
gravity_gradient_residual.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
gravity_potential_residual.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
displacement_residual.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
|
||||
integrator.SetJacobianMode(integrators::GravityForceJacobianMode::minimal);
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
|
||||
mfem::Vector minimal_difference(velocity_residual);
|
||||
minimal_difference -= reference_velocity_residual;
|
||||
|
||||
integrator.SetJacobianMode(integrators::GravityForceJacobianMode::exact);
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
|
||||
mfem::Vector exact_difference(velocity_residual);
|
||||
exact_difference -= reference_velocity_residual;
|
||||
|
||||
CHECK_THAT(
|
||||
minimal_difference.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
exact_difference.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
}
|
||||
TEST_CASE(
|
||||
"Gravity Force Integrator Preserves Gravity Identities",
|
||||
tags::unit &tags::solver &tags::integrator &tags::gravity
|
||||
) {
|
||||
constexpr int dim = 3;
|
||||
constexpr double density_value = 1.7;
|
||||
constexpr double gravity_scale = 2.4;
|
||||
constexpr double density_scale = 0.6;
|
||||
constexpr double tolerance = 1.0e-12;
|
||||
|
||||
constexpr int velocity_block =
|
||||
solver::block_index(solver::FieldBlock::velocity);
|
||||
constexpr int density_block =
|
||||
solver::block_index(solver::FieldBlock::density);
|
||||
constexpr int gravity_gradient_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_gradient);
|
||||
constexpr int gravity_potential_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_potential);
|
||||
constexpr int displacement_block =
|
||||
solver::block_index(solver::FieldBlock::displacement);
|
||||
constexpr int block_count = solver::field_block_count;
|
||||
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(
|
||||
1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0
|
||||
);
|
||||
|
||||
mfem::H1_FECollection velocity_fec(1, dim);
|
||||
mfem::L2_FECollection density_fec(0, dim);
|
||||
mfem::RT_FECollection gravity_gradient_fec(0, dim);
|
||||
mfem::H1_FECollection displacement_fec(1, dim);
|
||||
|
||||
mfem::FiniteElementSpace velocity_fes(
|
||||
&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
|
||||
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
|
||||
mfem::FiniteElementSpace displacement_fes(
|
||||
&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
|
||||
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
|
||||
|
||||
REQUIRE(domain_mapper.HasDisplacementField());
|
||||
|
||||
auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) {
|
||||
gravity.SetSize(3);
|
||||
gravity(0) = x(0) - 0.5;
|
||||
gravity(1) = x(1) - 0.5;
|
||||
gravity(2) = x(2) - 0.5;
|
||||
};
|
||||
|
||||
mfem::ConstantCoefficient density_coefficient(density_value);
|
||||
mfem::VectorFunctionCoefficient gravity_coefficient(dim, radial_gravity);
|
||||
|
||||
mfem::GridFunction density(&density_fes);
|
||||
mfem::GridFunction gravity_gradient(&gravity_gradient_fes);
|
||||
density.ProjectCoefficient(density_coefficient);
|
||||
gravity_gradient.ProjectCoefficient(gravity_coefficient);
|
||||
|
||||
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
|
||||
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
|
||||
const mfem::FiniteElement *gravity_gradient_element =
|
||||
gravity_gradient_fes.GetFE(0);
|
||||
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
|
||||
mfem::ElementTransformation *transformation =
|
||||
mesh.GetElementTransformation(0);
|
||||
|
||||
const int velocity_dofs_count = velocity_element->GetDof();
|
||||
const int density_dofs_count = density_element->GetDof();
|
||||
const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof();
|
||||
const int displacement_dofs_count = displacement_element->GetDof();
|
||||
const int velocity_size = dim * velocity_dofs_count;
|
||||
const int displacement_size = dim * displacement_dofs_count;
|
||||
|
||||
mfem::Array<int> density_dof_indices;
|
||||
mfem::Array<int> gravity_gradient_dof_indices;
|
||||
mfem::Vector density_dofs;
|
||||
mfem::Vector gravity_gradient_dofs;
|
||||
density_fes.GetElementDofs(0, density_dof_indices);
|
||||
gravity_gradient_fes.GetElementVDofs(0, gravity_gradient_dof_indices);
|
||||
density.GetSubVector(density_dof_indices, density_dofs);
|
||||
gravity_gradient.GetSubVector(
|
||||
gravity_gradient_dof_indices, gravity_gradient_dofs
|
||||
);
|
||||
|
||||
mfem::Vector zero_density(density_dofs_count);
|
||||
mfem::Vector zero_gravity(gravity_gradient_dofs_count);
|
||||
mfem::Vector velocity_dofs(velocity_size);
|
||||
mfem::Vector gravity_potential_dofs(density_dofs_count);
|
||||
mfem::Vector displacement_dofs(displacement_size);
|
||||
zero_density = 0.0;
|
||||
zero_gravity = 0.0;
|
||||
velocity_dofs = 0.0;
|
||||
gravity_potential_dofs = 0.0;
|
||||
displacement_dofs = 0.0;
|
||||
|
||||
mfem::Array<const mfem::FiniteElement *> elements(block_count);
|
||||
elements[velocity_block] = velocity_element;
|
||||
elements[density_block] = density_element;
|
||||
elements[gravity_gradient_block] = gravity_gradient_element;
|
||||
elements[gravity_potential_block] = density_element;
|
||||
elements[displacement_block] = displacement_element;
|
||||
|
||||
mfem::Array<const mfem::Vector *> element_state(block_count);
|
||||
element_state[velocity_block] = &velocity_dofs;
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
element_state[gravity_potential_block] = &gravity_potential_dofs;
|
||||
element_state[displacement_block] = &displacement_dofs;
|
||||
|
||||
mfem::Vector velocity_residual(velocity_size);
|
||||
mfem::Vector density_residual(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count);
|
||||
mfem::Vector gravity_potential_residual(density_dofs_count);
|
||||
mfem::Vector displacement_residual(displacement_size);
|
||||
|
||||
mfem::Array<mfem::Vector *> element_residual(block_count);
|
||||
element_residual[velocity_block] = &velocity_residual;
|
||||
element_residual[density_block] = &density_residual;
|
||||
element_residual[gravity_gradient_block] = &gravity_gradient_residual;
|
||||
element_residual[gravity_potential_block] = &gravity_potential_residual;
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const mfem::IntegrationRule &integration_rule =
|
||||
mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
|
||||
integrator.SetIntegrationRule(integration_rule);
|
||||
|
||||
auto assemble_velocity_residual = [&](const mfem::Vector &density_state,
|
||||
const mfem::Vector &gravity_state) {
|
||||
element_state[density_block] = &density_state;
|
||||
element_state[gravity_gradient_block] = &gravity_state;
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
return mfem::Vector(velocity_residual);
|
||||
};
|
||||
|
||||
auto scaled_difference_norm = [](mfem::Vector computed,
|
||||
const mfem::Vector &reference,
|
||||
const double scale) {
|
||||
computed.Add(-scale, reference);
|
||||
return computed.Norml2();
|
||||
};
|
||||
|
||||
const mfem::Vector base_residual =
|
||||
assemble_velocity_residual(density_dofs, gravity_gradient_dofs);
|
||||
|
||||
REQUIRE(base_residual.Norml2() > tolerance);
|
||||
|
||||
const mfem::Vector zero_field_residual =
|
||||
assemble_velocity_residual(density_dofs, zero_gravity);
|
||||
|
||||
mfem::Vector reversed_gravity(gravity_gradient_dofs);
|
||||
reversed_gravity *= -1.0;
|
||||
const mfem::Vector reversed_residual =
|
||||
assemble_velocity_residual(density_dofs, reversed_gravity);
|
||||
|
||||
mfem::Vector scaled_gravity(gravity_gradient_dofs);
|
||||
scaled_gravity *= gravity_scale;
|
||||
const mfem::Vector gravity_scaled_residual =
|
||||
assemble_velocity_residual(density_dofs, scaled_gravity);
|
||||
|
||||
mfem::Vector scaled_density(density_dofs);
|
||||
scaled_density *= density_scale;
|
||||
const mfem::Vector density_scaled_residual =
|
||||
assemble_velocity_residual(scaled_density, gravity_gradient_dofs);
|
||||
const mfem::Vector jointly_scaled_residual =
|
||||
assemble_velocity_residual(scaled_density, scaled_gravity);
|
||||
|
||||
CHECK_THAT(
|
||||
zero_field_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(reversed_residual, base_residual, -1.0),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(
|
||||
gravity_scaled_residual, base_residual, gravity_scale
|
||||
),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(
|
||||
density_scaled_residual, base_residual, density_scale
|
||||
),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(
|
||||
jointly_scaled_residual, base_residual,
|
||||
density_scale * gravity_scale
|
||||
),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
|
||||
auto make_test_dofs = [&](auto &&test_function) {
|
||||
mfem::Vector test_dofs(velocity_size);
|
||||
mfem::Vector x_physical(dim);
|
||||
mfem::Vector centered_position(dim);
|
||||
mfem::Vector test_value(dim);
|
||||
test_dofs = 0.0;
|
||||
|
||||
const mfem::IntegrationRule &velocity_nodes =
|
||||
velocity_element->GetNodes();
|
||||
|
||||
for (int i = 0; i < velocity_dofs_count; ++i) {
|
||||
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
|
||||
transformation->SetIntPoint(&node);
|
||||
domain_mapper.GetPhysicalPoint(*transformation, node, x_physical);
|
||||
|
||||
for (int component = 0; component < dim; ++component) {
|
||||
centered_position(component) = x_physical(component) - 0.5;
|
||||
}
|
||||
|
||||
test_function(centered_position, test_value);
|
||||
|
||||
for (int component = 0; component < dim; ++component) {
|
||||
test_dofs(i + component * velocity_dofs_count) =
|
||||
test_value(component);
|
||||
}
|
||||
}
|
||||
|
||||
return test_dofs;
|
||||
};
|
||||
|
||||
const mfem::Vector force_x_test =
|
||||
make_test_dofs([](const mfem::Vector &, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value = 0.0;
|
||||
value(0) = 1.0;
|
||||
});
|
||||
|
||||
const mfem::Vector force_y_test =
|
||||
make_test_dofs([](const mfem::Vector &, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value = 0.0;
|
||||
value(1) = 1.0;
|
||||
});
|
||||
|
||||
const mfem::Vector force_z_test =
|
||||
make_test_dofs([](const mfem::Vector &, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value = 0.0;
|
||||
value(2) = 1.0;
|
||||
});
|
||||
|
||||
const mfem::Vector torque_x_test =
|
||||
make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = 0.0;
|
||||
value(1) = -position(2);
|
||||
value(2) = position(1);
|
||||
});
|
||||
|
||||
const mfem::Vector torque_y_test =
|
||||
make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = position(2);
|
||||
value(1) = 0.0;
|
||||
value(2) = -position(0);
|
||||
});
|
||||
|
||||
const mfem::Vector torque_z_test =
|
||||
make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = -position(1);
|
||||
value(1) = position(0);
|
||||
value(2) = 0.0;
|
||||
});
|
||||
|
||||
CHECK_THAT(
|
||||
force_x_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
force_y_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
force_z_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
torque_x_test * base_residual,
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
torque_y_test * base_residual,
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
torque_z_test * base_residual,
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
|
||||
mfem::DenseMatrix dv_drho(velocity_size, density_dofs_count);
|
||||
mfem::DenseMatrix dv_dgrad_phi(velocity_size, gravity_gradient_dofs_count);
|
||||
|
||||
mfem::Array2D<mfem::DenseMatrix *> element_matrices(
|
||||
block_count, block_count
|
||||
);
|
||||
|
||||
for (int row = 0; row < block_count; ++row) {
|
||||
for (int column = 0; column < block_count; ++column) {
|
||||
element_matrices(row, column) = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
element_matrices(velocity_block, density_block) = &dv_drho;
|
||||
element_matrices(velocity_block, gravity_gradient_block) = &dv_dgrad_phi;
|
||||
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &zero_gravity;
|
||||
integrator.AssembleElementGrad(
|
||||
elements, *transformation, element_state, element_matrices
|
||||
);
|
||||
|
||||
mfem::Vector zero_gravity_density_action(velocity_size);
|
||||
mfem::Vector zero_gravity_field_action(velocity_size);
|
||||
dv_drho.Mult(density_dofs, zero_gravity_density_action);
|
||||
dv_dgrad_phi.Mult(gravity_gradient_dofs, zero_gravity_field_action);
|
||||
|
||||
CHECK_THAT(
|
||||
zero_gravity_density_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(zero_gravity_field_action, base_residual, 1.0),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
|
||||
element_state[density_block] = &zero_density;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
integrator.AssembleElementGrad(
|
||||
elements, *transformation, element_state, element_matrices
|
||||
);
|
||||
|
||||
mfem::Vector zero_density_density_action(velocity_size);
|
||||
mfem::Vector zero_density_field_action(velocity_size);
|
||||
dv_drho.Mult(density_dofs, zero_density_density_action);
|
||||
dv_dgrad_phi.Mult(gravity_gradient_dofs, zero_density_field_action);
|
||||
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(zero_density_density_action, base_residual, 1.0),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
zero_density_field_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
}
|
||||
922
tests/mapping/compactification/kelvin.cpp
Normal file
922
tests/mapping/compactification/kelvin.cpp
Normal file
@@ -0,0 +1,922 @@
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
namespace {
|
||||
constexpr int dimension = 3;
|
||||
|
||||
mfem::Vector make_vector(
|
||||
const double x,
|
||||
const double y,
|
||||
const double z
|
||||
) {
|
||||
mfem::Vector vector(dimension);
|
||||
vector(0) = x;
|
||||
vector(1) = y;
|
||||
vector(2) = z;
|
||||
return vector;
|
||||
}
|
||||
|
||||
mfem::DenseMatrix make_identity() {
|
||||
mfem::DenseMatrix matrix(dimension);
|
||||
matrix = 0.0;
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
matrix(i, i) = 1.0;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
void check_vector(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected,
|
||||
const double tolerance
|
||||
) {
|
||||
REQUIRE(actual.Size() == expected.Size());
|
||||
for (int i = 0; i < actual.Size(); ++i)
|
||||
CHECK_THAT(actual(i), WithinAbs(expected(i), tolerance));
|
||||
}
|
||||
|
||||
void check_matrix(
|
||||
const mfem::DenseMatrix &actual,
|
||||
const mfem::DenseMatrix &expected,
|
||||
const double tolerance
|
||||
) {
|
||||
REQUIRE(actual.Height() == expected.Height());
|
||||
REQUIRE(actual.Width() == expected.Width());
|
||||
|
||||
for (int i = 0; i < actual.Height(); ++i) {
|
||||
for (int j = 0; j < actual.Width(); ++j)
|
||||
CHECK_THAT(actual(i, j), WithinAbs(expected(i, j), tolerance));
|
||||
}
|
||||
}
|
||||
|
||||
struct AnalyticFactors {
|
||||
double computational_radius;
|
||||
double scale;
|
||||
double scale_derivative;
|
||||
};
|
||||
|
||||
AnalyticFactors compute_analytic_factors(
|
||||
const double r_star,
|
||||
const double r_inf,
|
||||
const double coordinate
|
||||
) {
|
||||
const double radial_extent = r_inf - r_star;
|
||||
const double computational_radius = r_star + coordinate * radial_extent;
|
||||
const double scale =
|
||||
r_star / (computational_radius * (1.0 - coordinate));
|
||||
const double scale_derivative =
|
||||
scale *
|
||||
(1.0 / (1.0 - coordinate) - radial_extent / computational_radius);
|
||||
return {
|
||||
.computational_radius = computational_radius,
|
||||
.scale = scale,
|
||||
.scale_derivative = scale_derivative
|
||||
};
|
||||
}
|
||||
|
||||
mapping::MappingStatus evaluate_affine_map(
|
||||
const mapping::compactification::ExteriorDomainMap &exterior_map,
|
||||
const mfem::Vector &reference_position,
|
||||
const mfem::DenseMatrix &affine_jacobian,
|
||||
const mfem::Vector &offset,
|
||||
const double compactification_coordinate,
|
||||
const mfem::Vector &compactification_coordinate_gradient,
|
||||
mapping::compactification::ExteriorMapResult &result
|
||||
) {
|
||||
mfem::Vector displaced_position(dimension);
|
||||
affine_jacobian.Mult(reference_position, displaced_position);
|
||||
displaced_position += offset;
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
.reference_position = reference_position,
|
||||
.displaced_position = displaced_position,
|
||||
.displacement_jacobian = affine_jacobian,
|
||||
.compactification_coordinate = compactification_coordinate,
|
||||
.compactification_coordinate_gradient =
|
||||
compactification_coordinate_gradient
|
||||
};
|
||||
|
||||
return exterior_map.Evaluate(input, result);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Validates Its Configuration",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
CHECK_NOTHROW(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
)
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 0.0, .r_inf_ref = 4.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = -1.0, .r_inf_ref = 4.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 2.0, .r_inf_ref = 2.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 3.0, .r_inf_ref = 2.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0,
|
||||
.r_inf_ref = std::numeric_limits<double>::infinity()}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0,
|
||||
.r_inf_ref = 4.0,
|
||||
.coordinate_tolerance = -1.0e-12}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0, .coordinate_tolerance = 1.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0,
|
||||
.r_inf_ref = 4.0,
|
||||
.coordinate_tolerance = std::numeric_limits<double>::quiet_NaN()}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Reports Its Configuration",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double coordinate_tolerance = 3.0e-11;
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.25,
|
||||
.r_inf_ref = 5.5,
|
||||
.coordinate_tolerance = coordinate_tolerance}
|
||||
);
|
||||
|
||||
CHECK(compactification.GetName() == "KelvinCompactification");
|
||||
CHECK_THAT(
|
||||
compactification.GetReferenceStellarRadius(), WithinAbs(1.25, 0.0)
|
||||
);
|
||||
CHECK_THAT(
|
||||
compactification.GetReferenceInfinityRadius(), WithinAbs(5.5, 0.0)
|
||||
);
|
||||
CHECK_THAT(
|
||||
compactification.GetCoordinateTolerance(),
|
||||
WithinAbs(coordinate_tolerance, 0.0)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Uses The Exterior Coordinate Rather Than "
|
||||
"Euclidean Radius",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double tolerance = 0.0;
|
||||
constexpr double coordinate = 0.37;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::DenseMatrix displacement_jacobian = make_identity();
|
||||
const mfem::Vector displaced_position = make_vector(1.4, -0.2, 0.3);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.2, -0.1, 0.05);
|
||||
const mfem::Vector reference_a = make_vector(0.2, 0.1, -0.1);
|
||||
const mfem::Vector reference_b = make_vector(12.0, -7.0, 4.0);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input_a{
|
||||
reference_a, displaced_position, displacement_jacobian, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
const mapping::compactification::ExteriorMapInput input_b{
|
||||
reference_b, displaced_position, displacement_jacobian, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult result_a;
|
||||
mapping::compactification::ExteriorMapResult result_b;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_a, result_a) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_b, result_b) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
check_vector(
|
||||
result_a.physical_position, result_b.physical_position, tolerance
|
||||
);
|
||||
check_matrix(
|
||||
result_a.mapping_jacobian, result_b.mapping_jacobian, tolerance
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Matches Its Analytic Radial Map",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double r_star = 1.0;
|
||||
constexpr double r_inf = 4.0;
|
||||
constexpr double radial_extent = r_inf - r_star;
|
||||
constexpr double tolerance = 2.0e-12;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = r_star, .r_inf_ref = r_inf}
|
||||
);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
const mfem::Vector coordinate_gradient =
|
||||
make_vector(1.0 / radial_extent, 0.0, 0.0);
|
||||
|
||||
for (const double computational_radius :
|
||||
std::array{1.0, 1.25, 2.0, 3.0, 3.75}) {
|
||||
CAPTURE(computational_radius);
|
||||
|
||||
const double coordinate =
|
||||
(computational_radius - r_star) / radial_extent;
|
||||
const mfem::Vector reference_position =
|
||||
make_vector(computational_radius, 0.0, 0.0);
|
||||
const mfem::Vector displaced_position(reference_position);
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, identity, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
const double expected_radius =
|
||||
r_star * radial_extent / (r_inf - computational_radius);
|
||||
const double expected_radial_derivative =
|
||||
r_star * radial_extent /
|
||||
std::pow(r_inf - computational_radius, 2.0);
|
||||
const double expected_tangential_scale =
|
||||
expected_radius / computational_radius;
|
||||
|
||||
const mfem::Vector expected_position =
|
||||
make_vector(expected_radius, 0.0, 0.0);
|
||||
mfem::DenseMatrix expected_jacobian(dimension);
|
||||
expected_jacobian = 0.0;
|
||||
expected_jacobian(0, 0) = expected_radial_derivative;
|
||||
expected_jacobian(1, 1) = expected_tangential_scale;
|
||||
expected_jacobian(2, 2) = expected_tangential_scale;
|
||||
|
||||
check_vector(result.physical_position, expected_position, tolerance);
|
||||
check_matrix(result.mapping_jacobian, expected_jacobian, tolerance);
|
||||
CHECK(result.mapping_jacobian.Det() > 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Matches Its Full Cartesian Formula",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double r_star = 1.0;
|
||||
constexpr double r_inf = 4.0;
|
||||
constexpr double coordinate = 0.42;
|
||||
constexpr double tolerance = 1.0e-12;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = r_star, .r_inf_ref = r_inf}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(0.8, 0.4, -0.2);
|
||||
const mfem::Vector displaced_position = make_vector(1.1, 0.5, -0.1);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.20, -0.10, 0.05);
|
||||
|
||||
mfem::DenseMatrix displacement_jacobian(dimension);
|
||||
displacement_jacobian(0, 0) = 1.10;
|
||||
displacement_jacobian(0, 1) = 0.05;
|
||||
displacement_jacobian(0, 2) = 0.00;
|
||||
displacement_jacobian(1, 0) = -0.02;
|
||||
displacement_jacobian(1, 1) = 0.95;
|
||||
displacement_jacobian(1, 2) = 0.03;
|
||||
displacement_jacobian(2, 0) = 0.01;
|
||||
displacement_jacobian(2, 1) = -0.04;
|
||||
displacement_jacobian(2, 2) = 1.05;
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, displacement_jacobian,
|
||||
coordinate, coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
const AnalyticFactors factors =
|
||||
compute_analytic_factors(r_star, r_inf, coordinate);
|
||||
mfem::Vector expected_position(displaced_position);
|
||||
expected_position *= factors.scale;
|
||||
|
||||
mfem::DenseMatrix expected_jacobian(displacement_jacobian);
|
||||
expected_jacobian *= factors.scale;
|
||||
|
||||
for (int i = 0; i < dimension; ++i) {
|
||||
for (int j = 0; j < dimension; ++j)
|
||||
expected_jacobian(i, j) += displaced_position(i) *
|
||||
factors.scale_derivative *
|
||||
coordinate_gradient(j);
|
||||
}
|
||||
|
||||
check_vector(result.physical_position, expected_position, tolerance);
|
||||
check_matrix(result.mapping_jacobian, expected_jacobian, tolerance);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Jacobian Matches Coordinate Finite Differences",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double base_coordinate = 0.38;
|
||||
constexpr double difference_step = 1.0e-6;
|
||||
constexpr double tolerance = 3.0e-9;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
|
||||
mfem::DenseMatrix affine_jacobian(dimension);
|
||||
affine_jacobian(0, 0) = 1.05;
|
||||
affine_jacobian(0, 1) = 0.02;
|
||||
affine_jacobian(0, 2) = 0.00;
|
||||
affine_jacobian(1, 0) = -0.01;
|
||||
affine_jacobian(1, 1) = 0.98;
|
||||
affine_jacobian(1, 2) = 0.03;
|
||||
affine_jacobian(2, 0) = 0.02;
|
||||
affine_jacobian(2, 1) = 0.00;
|
||||
affine_jacobian(2, 2) = 1.02;
|
||||
|
||||
const mfem::Vector offset = make_vector(0.04, -0.03, 0.02);
|
||||
const mfem::Vector reference_position = make_vector(1.4, 0.3, -0.2);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.11, -0.07, 0.05);
|
||||
|
||||
mapping::compactification::ExteriorMapResult base_result;
|
||||
REQUIRE(
|
||||
evaluate_affine_map(
|
||||
compactification, reference_position, affine_jacobian, offset,
|
||||
base_coordinate, coordinate_gradient, base_result
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
|
||||
mfem::Vector reference_plus(reference_position);
|
||||
mfem::Vector reference_minus(reference_position);
|
||||
reference_plus(coordinate) += difference_step;
|
||||
reference_minus(coordinate) -= difference_step;
|
||||
|
||||
const double compactification_plus =
|
||||
base_coordinate + difference_step * coordinate_gradient(coordinate);
|
||||
const double compactification_minus =
|
||||
base_coordinate - difference_step * coordinate_gradient(coordinate);
|
||||
|
||||
mapping::compactification::ExteriorMapResult result_plus;
|
||||
mapping::compactification::ExteriorMapResult result_minus;
|
||||
|
||||
REQUIRE(
|
||||
evaluate_affine_map(
|
||||
compactification, reference_plus, affine_jacobian, offset,
|
||||
compactification_plus, coordinate_gradient, result_plus
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
evaluate_affine_map(
|
||||
compactification, reference_minus, affine_jacobian, offset,
|
||||
compactification_minus, coordinate_gradient, result_minus
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
const double finite_difference =
|
||||
(result_plus.physical_position(component) -
|
||||
result_minus.physical_position(component)) /
|
||||
(2.0 * difference_step);
|
||||
CHECK_THAT(
|
||||
finite_difference,
|
||||
WithinAbs(
|
||||
base_result.mapping_jacobian(component, coordinate),
|
||||
tolerance
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Variation Matches State Finite Differences",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double coordinate = 0.46;
|
||||
constexpr double difference_step = 1.0e-6;
|
||||
constexpr double tolerance = 2.0e-10;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(1.3, -0.2, 0.4);
|
||||
const mfem::Vector displaced_position = make_vector(1.4, -0.1, 0.35);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.12, -0.04, 0.08);
|
||||
const mfem::Vector position_direction = make_vector(0.03, -0.05, 0.02);
|
||||
|
||||
mfem::DenseMatrix displacement_jacobian = make_identity();
|
||||
displacement_jacobian(0, 1) = 0.04;
|
||||
displacement_jacobian(1, 2) = -0.03;
|
||||
displacement_jacobian(2, 0) = 0.02;
|
||||
|
||||
mfem::DenseMatrix jacobian_direction(dimension);
|
||||
jacobian_direction(0, 0) = 0.02;
|
||||
jacobian_direction(0, 1) = -0.01;
|
||||
jacobian_direction(0, 2) = 0.03;
|
||||
jacobian_direction(1, 0) = 0.01;
|
||||
jacobian_direction(1, 1) = -0.02;
|
||||
jacobian_direction(1, 2) = 0.00;
|
||||
jacobian_direction(2, 0) = -0.01;
|
||||
jacobian_direction(2, 1) = 0.02;
|
||||
jacobian_direction(2, 2) = 0.01;
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, displacement_jacobian,
|
||||
coordinate, coordinate_gradient
|
||||
};
|
||||
const mapping::compactification::ExteriorMapDirection direction{
|
||||
position_direction, jacobian_direction
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult base_result;
|
||||
mapping::compactification::ExteriorMapVariation variation;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, base_result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.EvaluateVariation(
|
||||
input, base_result, direction, variation
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
mfem::Vector displaced_plus(displaced_position);
|
||||
mfem::Vector displaced_minus(displaced_position);
|
||||
displaced_plus.Add(difference_step, position_direction);
|
||||
displaced_minus.Add(-difference_step, position_direction);
|
||||
|
||||
mfem::DenseMatrix jacobian_plus(displacement_jacobian);
|
||||
mfem::DenseMatrix jacobian_minus(displacement_jacobian);
|
||||
jacobian_plus.Add(difference_step, jacobian_direction);
|
||||
jacobian_minus.Add(-difference_step, jacobian_direction);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input_plus{
|
||||
reference_position, displaced_plus, jacobian_plus, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
const mapping::compactification::ExteriorMapInput input_minus{
|
||||
reference_position, displaced_minus, jacobian_minus, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult result_plus;
|
||||
mapping::compactification::ExteriorMapResult result_minus;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_plus, result_plus) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_minus, result_minus) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
for (int i = 0; i < dimension; ++i) {
|
||||
const double position_finite_difference =
|
||||
(result_plus.physical_position(i) -
|
||||
result_minus.physical_position(i)) /
|
||||
(2.0 * difference_step);
|
||||
CHECK_THAT(
|
||||
position_finite_difference,
|
||||
WithinAbs(variation.physical_position_variation(i), tolerance)
|
||||
);
|
||||
|
||||
for (int j = 0; j < dimension; ++j) {
|
||||
const double jacobian_finite_difference =
|
||||
(result_plus.mapping_jacobian(i, j) -
|
||||
result_minus.mapping_jacobian(i, j)) /
|
||||
(2.0 * difference_step);
|
||||
CHECK_THAT(
|
||||
jacobian_finite_difference,
|
||||
WithinAbs(variation.mapping_jacobian_variation(i, j), tolerance)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Preserves Rotational Covariance",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double coordinate = 0.31;
|
||||
constexpr double tolerance = 1.0e-12;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(1.3, 0.4, -0.2);
|
||||
const mfem::Vector displaced_position = make_vector(1.4, 0.2, -0.1);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.16, -0.08, 0.03);
|
||||
|
||||
mfem::DenseMatrix displacement_jacobian = make_identity();
|
||||
displacement_jacobian(0, 1) = 0.05;
|
||||
displacement_jacobian(1, 0) = -0.02;
|
||||
displacement_jacobian(2, 1) = 0.03;
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, displacement_jacobian,
|
||||
coordinate, coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
mfem::DenseMatrix rotation(dimension);
|
||||
rotation = 0.0;
|
||||
rotation(0, 1) = -1.0;
|
||||
rotation(1, 0) = 1.0;
|
||||
rotation(2, 2) = 1.0;
|
||||
|
||||
mfem::Vector rotated_reference(dimension);
|
||||
mfem::Vector rotated_displaced(dimension);
|
||||
mfem::Vector rotated_coordinate_gradient(dimension);
|
||||
rotation.Mult(reference_position, rotated_reference);
|
||||
rotation.Mult(displaced_position, rotated_displaced);
|
||||
rotation.Mult(coordinate_gradient, rotated_coordinate_gradient);
|
||||
|
||||
mfem::DenseMatrix temporary(dimension);
|
||||
mfem::DenseMatrix rotated_displacement_jacobian(dimension);
|
||||
mfem::Mult(rotation, displacement_jacobian, temporary);
|
||||
mfem::MultABt(temporary, rotation, rotated_displacement_jacobian);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput rotated_input{
|
||||
rotated_reference, rotated_displaced, rotated_displacement_jacobian,
|
||||
coordinate, rotated_coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult rotated_result;
|
||||
REQUIRE(
|
||||
compactification.Evaluate(rotated_input, rotated_result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
mfem::Vector expected_position(dimension);
|
||||
rotation.Mult(result.physical_position, expected_position);
|
||||
|
||||
mfem::DenseMatrix expected_jacobian(dimension);
|
||||
mfem::Mult(rotation, result.mapping_jacobian, temporary);
|
||||
mfem::MultABt(temporary, rotation, expected_jacobian);
|
||||
|
||||
check_vector(
|
||||
rotated_result.physical_position, expected_position, tolerance
|
||||
);
|
||||
check_matrix(rotated_result.mapping_jacobian, expected_jacobian, tolerance);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Is Continuous At The Mesh Defined Stellar Surface",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double tolerance = 1.0e-14;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(
|
||||
-0.5260553366425769, 0.5260553366425769, -0.6553163792879153
|
||||
);
|
||||
const mfem::Vector displaced_position = make_vector(-0.55, 0.51, -0.63);
|
||||
const mfem::Vector coordinate_gradient = make_vector(-0.18, 0.18, -0.22);
|
||||
const mfem::DenseMatrix displacement_jacobian = make_identity();
|
||||
|
||||
REQUIRE(reference_position.Norml2() < 1.0);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, displacement_jacobian, 0.0,
|
||||
coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
check_vector(result.physical_position, displaced_position, tolerance);
|
||||
CHECK(result.mapping_jacobian.Det() > 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Has Correct Infinity And Coordinate Bound "
|
||||
"Behavior",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double r_star = 1.0;
|
||||
constexpr double r_inf = 4.0;
|
||||
constexpr double radial_extent = r_inf - r_star;
|
||||
constexpr double coordinate_tolerance = 1.0e-12;
|
||||
constexpr double tolerance = 2.0e-11;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = r_star,
|
||||
.r_inf_ref = r_inf,
|
||||
.coordinate_tolerance = coordinate_tolerance}
|
||||
);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
const mfem::Vector coordinate_gradient =
|
||||
make_vector(1.0 / radial_extent, 0.0, 0.0);
|
||||
|
||||
for (const double coordinate :
|
||||
std::array{0.0, 0.25, 0.75, 0.95, 0.99, 0.999}) {
|
||||
CAPTURE(coordinate);
|
||||
|
||||
const double computational_radius = r_star + coordinate * radial_extent;
|
||||
const mfem::Vector reference_position =
|
||||
make_vector(computational_radius, 0.0, 0.0);
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, reference_position, identity, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
CHECK_THAT(
|
||||
result.physical_position.Norml2() * (1.0 - coordinate),
|
||||
WithinAbs(r_star, tolerance)
|
||||
);
|
||||
}
|
||||
|
||||
const mfem::Vector reference_position = make_vector(r_inf, 0.0, 0.0);
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity, 1.0,
|
||||
coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::at_compactified_infinity
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity,
|
||||
1.0 - 0.5 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::at_compactified_infinity
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity,
|
||||
1.0 + 0.5 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::at_compactified_infinity
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity,
|
||||
1.0 + 2.0 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::outside_reference_domain
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity,
|
||||
-2.0 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::outside_reference_domain
|
||||
);
|
||||
|
||||
const mfem::Vector surface_position = make_vector(0.97, 0.0, 0.0);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{surface_position, surface_position, identity,
|
||||
-0.5 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
check_vector(result.physical_position, surface_position, tolerance);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Rejects Invalid Inputs And Inverted Maps",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
|
||||
const mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0);
|
||||
const mfem::Vector displaced_position(reference_position);
|
||||
const mfem::Vector coordinate_gradient = make_vector(1.0 / 3.0, 0.0, 0.0);
|
||||
const mfem::Vector zero_gradient = make_vector(0.0, 0.0, 0.0);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
mfem::Vector wrong_dimension(2);
|
||||
wrong_dimension = 1.0;
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{wrong_dimension, displaced_position, identity, 1.0 / 3.0,
|
||||
coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::invalid_dimension
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position, identity, 1.0 / 3.0,
|
||||
wrong_dimension},
|
||||
result
|
||||
) == mapping::MappingStatus::invalid_dimension
|
||||
);
|
||||
|
||||
mfem::Vector non_finite_position(reference_position);
|
||||
non_finite_position(1) = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{non_finite_position, displaced_position, identity, 1.0 / 3.0,
|
||||
coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_finite_input
|
||||
);
|
||||
|
||||
mfem::Vector non_finite_gradient(coordinate_gradient);
|
||||
non_finite_gradient(2) = std::numeric_limits<double>::infinity();
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position, identity, 1.0 / 3.0,
|
||||
non_finite_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_finite_input
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position, identity,
|
||||
std::numeric_limits<double>::quiet_NaN(), coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_finite_input
|
||||
);
|
||||
|
||||
mfem::DenseMatrix singular_displacement_jacobian(dimension);
|
||||
singular_displacement_jacobian = 0.0;
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position,
|
||||
singular_displacement_jacobian, 1.0 / 3.0, zero_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_positive_determinant
|
||||
);
|
||||
|
||||
mfem::DenseMatrix inverted_displacement_jacobian = make_identity();
|
||||
inverted_displacement_jacobian(0, 0) = -1.0;
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position,
|
||||
inverted_displacement_jacobian, 1.0 / 3.0, zero_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_positive_determinant
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Variation Rejects Invalid Inputs",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0);
|
||||
const mfem::Vector displaced_position(reference_position);
|
||||
const mfem::Vector coordinate_gradient = make_vector(1.0 / 3.0, 0.0, 0.0);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, identity, 1.0 / 3.0,
|
||||
coordinate_gradient
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
const mfem::Vector valid_position_direction =
|
||||
make_vector(0.01, -0.02, 0.03);
|
||||
const mfem::DenseMatrix valid_jacobian_direction = make_identity();
|
||||
mapping::compactification::ExteriorMapVariation variation;
|
||||
|
||||
mfem::Vector wrong_dimension(2);
|
||||
wrong_dimension = 0.0;
|
||||
CHECK(
|
||||
compactification.EvaluateVariation(
|
||||
input, result, {wrong_dimension, valid_jacobian_direction},
|
||||
variation
|
||||
) == mapping::MappingStatus::invalid_dimension
|
||||
);
|
||||
|
||||
mfem::DenseMatrix wrong_jacobian_dimension(2);
|
||||
wrong_jacobian_dimension = 0.0;
|
||||
CHECK(
|
||||
compactification.EvaluateVariation(
|
||||
input, result, {valid_position_direction, wrong_jacobian_dimension},
|
||||
variation
|
||||
) == mapping::MappingStatus::invalid_dimension
|
||||
);
|
||||
|
||||
mfem::Vector non_finite_direction(valid_position_direction);
|
||||
non_finite_direction(0) = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK(
|
||||
compactification.EvaluateVariation(
|
||||
input, result, {non_finite_direction, valid_jacobian_direction},
|
||||
variation
|
||||
) == mapping::MappingStatus::non_finite_input
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Evaluations Are Independent",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double tolerance = 0.0;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
const mfem::Vector gradient_a = make_vector(0.12, 0.03, -0.02);
|
||||
const mfem::Vector gradient_b = make_vector(-0.04, 0.15, 0.01);
|
||||
const mfem::Vector reference_a = make_vector(1.5, 0.2, 0.1);
|
||||
const mfem::Vector reference_b = make_vector(2.5, -0.3, 0.4);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input_a{
|
||||
reference_a, reference_a, identity, 0.25, gradient_a
|
||||
};
|
||||
const mapping::compactification::ExteriorMapInput input_b{
|
||||
reference_b, reference_b, identity, 0.70, gradient_b
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult first_a;
|
||||
mapping::compactification::ExteriorMapResult result_b;
|
||||
mapping::compactification::ExteriorMapResult second_a;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_a, first_a) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_b, result_b) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_a, second_a) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
check_vector(
|
||||
first_a.physical_position, second_a.physical_position, tolerance
|
||||
);
|
||||
check_matrix(
|
||||
first_a.mapping_jacobian, second_a.mapping_jacobian, tolerance
|
||||
);
|
||||
}
|
||||
3486
tests/mapping/domain_mapper.cpp
Normal file
3486
tests/mapping/domain_mapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
454
tests/mapping/hdiv_mass_tensor.cpp
Normal file
454
tests/mapping/hdiv_mass_tensor.cpp
Normal file
@@ -0,0 +1,454 @@
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
using namespace mean_field;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
namespace {
|
||||
constexpr int dimension = 3;
|
||||
|
||||
mfem::DenseMatrix make_matrix(
|
||||
const std::array<
|
||||
double,
|
||||
9> &values
|
||||
) {
|
||||
mfem::DenseMatrix matrix(dimension);
|
||||
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column)
|
||||
matrix(row, column) = values[row * dimension + column];
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
mfem::DenseMatrix make_identity_matrix() {
|
||||
mfem::DenseMatrix identity(dimension);
|
||||
identity = 0.0;
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
identity(i, i) = 1.0;
|
||||
return identity;
|
||||
}
|
||||
|
||||
mapping::MappingPointContext
|
||||
make_context(const mfem::DenseMatrix &jacobian) {
|
||||
mapping::MappingPointContext context;
|
||||
context.mapping_jacobian = jacobian;
|
||||
context.mapping_determinant = jacobian.Det();
|
||||
context.inverse_mapping_jacobian.SetSize(dimension);
|
||||
mfem::CalcInverse(jacobian, context.inverse_mapping_jacobian);
|
||||
context.physical_position.SetSize(dimension);
|
||||
context.physical_position = 0.0;
|
||||
return context;
|
||||
}
|
||||
|
||||
double determinant_variation(
|
||||
const mfem::DenseMatrix &jacobian,
|
||||
const mfem::DenseMatrix &jacobian_variation
|
||||
) {
|
||||
mfem::DenseMatrix inverse_jacobian(dimension);
|
||||
mfem::DenseMatrix product(dimension);
|
||||
mfem::CalcInverse(jacobian, inverse_jacobian);
|
||||
mfem::Mult(inverse_jacobian, jacobian_variation, product);
|
||||
|
||||
double trace = 0.0;
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
trace += product(i, i);
|
||||
return jacobian.Det() * trace;
|
||||
}
|
||||
|
||||
mapping::MappingPointVariation make_variation(
|
||||
const mfem::DenseMatrix &jacobian,
|
||||
const mfem::DenseMatrix &jacobian_variation
|
||||
) {
|
||||
mapping::MappingPointVariation variation;
|
||||
variation.mapping_jacobian_variation = jacobian_variation;
|
||||
variation.mapping_determinant_variation =
|
||||
determinant_variation(jacobian, jacobian_variation);
|
||||
variation.physical_position_variation.SetSize(dimension);
|
||||
variation.physical_position_variation = 0.0;
|
||||
return variation;
|
||||
}
|
||||
|
||||
double matrix_norm(const mfem::DenseMatrix &matrix) {
|
||||
double norm_squared = 0.0;
|
||||
|
||||
for (int row = 0; row < matrix.Height(); ++row) {
|
||||
for (int column = 0; column < matrix.Width(); ++column)
|
||||
norm_squared += matrix(row, column) * matrix(row, column);
|
||||
}
|
||||
|
||||
return std::sqrt(norm_squared);
|
||||
}
|
||||
|
||||
double relative_matrix_error(
|
||||
const mfem::DenseMatrix &computed,
|
||||
const mfem::DenseMatrix &reference
|
||||
) {
|
||||
REQUIRE(computed.Height() == reference.Height());
|
||||
REQUIRE(computed.Width() == reference.Width());
|
||||
|
||||
mfem::DenseMatrix difference(computed);
|
||||
difference -= reference;
|
||||
|
||||
return matrix_norm(difference) /
|
||||
std::max(
|
||||
matrix_norm(reference),
|
||||
std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
}
|
||||
|
||||
double matrix_asymmetry(const mfem::DenseMatrix &matrix) {
|
||||
double asymmetry_squared = 0.0;
|
||||
|
||||
for (int row = 0; row < matrix.Height(); ++row) {
|
||||
for (int column = 0; column < matrix.Width(); ++column) {
|
||||
const double difference =
|
||||
matrix(row, column) - matrix(column, row);
|
||||
asymmetry_squared += difference * difference;
|
||||
}
|
||||
}
|
||||
|
||||
return std::sqrt(asymmetry_squared);
|
||||
}
|
||||
|
||||
mfem::DenseMatrix centered_mass_tensor_difference(
|
||||
const mfem::DenseMatrix &jacobian,
|
||||
const mfem::DenseMatrix &jacobian_variation,
|
||||
const double step
|
||||
) {
|
||||
mfem::DenseMatrix plus_jacobian(jacobian);
|
||||
mfem::DenseMatrix minus_jacobian(jacobian);
|
||||
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
plus_jacobian(row, column) +=
|
||||
step * jacobian_variation(row, column);
|
||||
minus_jacobian(row, column) -=
|
||||
step * jacobian_variation(row, column);
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(plus_jacobian.Det() > 0.0);
|
||||
REQUIRE(minus_jacobian.Det() > 0.0);
|
||||
|
||||
const mapping::MappingPointContext plus_context =
|
||||
make_context(plus_jacobian);
|
||||
const mapping::MappingPointContext minus_context =
|
||||
make_context(minus_jacobian);
|
||||
|
||||
mfem::DenseMatrix plus_tensor;
|
||||
mfem::DenseMatrix minus_tensor;
|
||||
mapping::ComputeHDivMassTensor(plus_context, plus_tensor);
|
||||
mapping::ComputeHDivMassTensor(minus_context, minus_tensor);
|
||||
|
||||
plus_tensor -= minus_tensor;
|
||||
plus_tensor *= 1 / (2.0 * step);
|
||||
return plus_tensor;
|
||||
}
|
||||
|
||||
void check_zero_matrix(
|
||||
const mfem::DenseMatrix &matrix,
|
||||
const double tolerance
|
||||
) {
|
||||
for (int row = 0; row < matrix.Height(); ++row) {
|
||||
for (int column = 0; column < matrix.Width(); ++column)
|
||||
CHECK_THAT(matrix(row, column), WithinAbs(0.0, tolerance));
|
||||
}
|
||||
}
|
||||
|
||||
struct TensorVariationCase {
|
||||
std::string name;
|
||||
mfem::DenseMatrix jacobian;
|
||||
mfem::DenseMatrix jacobian_variation;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Matches Centered Differences",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
std::vector<TensorVariationCase> cases;
|
||||
|
||||
cases.push_back(
|
||||
{"identity with general variation", make_identity_matrix(),
|
||||
make_matrix({0.12, -0.07, 0.03, 0.05, -0.09, 0.04, -0.02, 0.08, 0.06})}
|
||||
);
|
||||
|
||||
cases.push_back(
|
||||
{"anisotropic stretch",
|
||||
make_matrix({1.20, 0.00, 0.00, 0.00, 0.85, 0.00, 0.00, 0.00, 1.10}),
|
||||
make_matrix({0.08, 0.01, -0.03, 0.02, -0.05, 0.04, 0.01, -0.02, 0.07})}
|
||||
);
|
||||
|
||||
cases.push_back(
|
||||
{"sheared mapping",
|
||||
make_matrix({1.10, 0.20, -0.05, 0.04, 0.90, 0.12, -0.03, 0.08, 1.15}),
|
||||
make_matrix(
|
||||
{0.06, -0.04, 0.02, 0.03, 0.05, -0.07, -0.01, 0.04, -0.02}
|
||||
)}
|
||||
);
|
||||
|
||||
cases.push_back(
|
||||
{"strong general mapping",
|
||||
make_matrix({1.35, 0.31, -0.18, -0.12, 0.78, 0.22, 0.09, -0.16, 1.27}),
|
||||
make_matrix({-0.11, 0.08, 0.05, 0.07, 0.09, -0.04, -0.06, 0.03, 0.12})}
|
||||
);
|
||||
|
||||
for (const TensorVariationCase &test_case : cases) {
|
||||
DYNAMIC_SECTION(test_case.name) {
|
||||
REQUIRE(test_case.jacobian.Det() > 0.0);
|
||||
|
||||
const mapping::MappingPointContext context =
|
||||
make_context(test_case.jacobian);
|
||||
const mapping::MappingPointVariation variation = make_variation(
|
||||
test_case.jacobian, test_case.jacobian_variation
|
||||
);
|
||||
|
||||
mfem::DenseMatrix analytic_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, analytic_variation
|
||||
);
|
||||
|
||||
const mfem::DenseMatrix finite_difference =
|
||||
centered_mass_tensor_difference(
|
||||
test_case.jacobian, test_case.jacobian_variation, 1.0e-6
|
||||
);
|
||||
const double relative_error =
|
||||
relative_matrix_error(analytic_variation, finite_difference);
|
||||
const double asymmetry = matrix_asymmetry(analytic_variation);
|
||||
|
||||
INFO("Mapping determinant = " << context.mapping_determinant);
|
||||
INFO(
|
||||
"Determinant variation = "
|
||||
<< variation.mapping_determinant_variation
|
||||
);
|
||||
INFO(
|
||||
"Analytic variation norm = " << matrix_norm(analytic_variation)
|
||||
);
|
||||
INFO(
|
||||
"Finite-difference variation norm = "
|
||||
<< matrix_norm(finite_difference)
|
||||
);
|
||||
INFO("Relative tensor-variation error = " << relative_error);
|
||||
INFO("Tensor-variation asymmetry = " << asymmetry);
|
||||
|
||||
CHECK(relative_error < 2.0e-9);
|
||||
CHECK(asymmetry < 2.0e-14);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Has Second Order Centered Difference "
|
||||
"Convergence",
|
||||
tags::unit &tags::transformations &tags::convergence
|
||||
) {
|
||||
const mfem::DenseMatrix jacobian =
|
||||
make_matrix({1.18, 0.17, -0.09, -0.04, 0.92, 0.14, 0.07, -0.11, 1.23});
|
||||
|
||||
const mfem::DenseMatrix jacobian_variation =
|
||||
make_matrix({0.09, -0.06, 0.04, 0.03, 0.07, -0.05, -0.02, 0.08, -0.03});
|
||||
|
||||
const mapping::MappingPointContext context = make_context(jacobian);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(jacobian, jacobian_variation);
|
||||
|
||||
mfem::DenseMatrix analytic_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, analytic_variation
|
||||
);
|
||||
|
||||
const std::array<double, 3> steps{4.0e-2, 2.0e-2, 1.0e-2};
|
||||
std::array<double, 3> errors{};
|
||||
|
||||
for (int i = 0; i < static_cast<int>(steps.size()); ++i) {
|
||||
const mfem::DenseMatrix finite_difference =
|
||||
centered_mass_tensor_difference(
|
||||
jacobian, jacobian_variation, steps[i]
|
||||
);
|
||||
errors[i] =
|
||||
relative_matrix_error(finite_difference, analytic_variation);
|
||||
INFO("Step = " << steps[i] << ", relative error = " << errors[i]);
|
||||
}
|
||||
|
||||
const double first_reduction = errors[1] / errors[0];
|
||||
const double second_reduction = errors[2] / errors[1];
|
||||
|
||||
INFO("First error-reduction ratio = " << first_reduction);
|
||||
INFO("Second error-reduction ratio = " << second_reduction);
|
||||
|
||||
CHECK(first_reduction < 0.30);
|
||||
CHECK(second_reduction < 0.30);
|
||||
CHECK(errors[2] < 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Vanishes For Translation",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
const mfem::DenseMatrix jacobian =
|
||||
make_matrix({1.12, 0.08, -0.03, 0.02, 0.94, 0.07, -0.01, 0.05, 1.09});
|
||||
|
||||
mfem::DenseMatrix zero_jacobian_variation(dimension);
|
||||
zero_jacobian_variation = 0.0;
|
||||
|
||||
mapping::MappingPointContext context = make_context(jacobian);
|
||||
mapping::MappingPointVariation variation =
|
||||
make_variation(jacobian, zero_jacobian_variation);
|
||||
variation.physical_position_variation.SetSize(dimension);
|
||||
variation.physical_position_variation(0) = 0.7;
|
||||
variation.physical_position_variation(1) = -0.4;
|
||||
variation.physical_position_variation(2) = 0.9;
|
||||
|
||||
mfem::DenseMatrix tensor_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, tensor_variation
|
||||
);
|
||||
|
||||
CHECK_THAT(variation.mapping_determinant_variation, WithinAbs(0.0, 0.0));
|
||||
check_zero_matrix(tensor_variation, 1.0e-14);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Vanishes For Infinitesimal Rotation At "
|
||||
"Identity",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
const mfem::DenseMatrix identity = make_identity_matrix();
|
||||
|
||||
const mfem::DenseMatrix rotation_variation =
|
||||
make_matrix({0.0, -0.30, 0.20, 0.30, 0.0, -0.15, -0.20, 0.15, 0.0});
|
||||
|
||||
const mapping::MappingPointContext context = make_context(identity);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(identity, rotation_variation);
|
||||
|
||||
mfem::DenseMatrix tensor_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, tensor_variation
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15)
|
||||
);
|
||||
check_zero_matrix(tensor_variation, 1.0e-14);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Matches Isotropic Scaling At Identity",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
constexpr double scaling_variation = 0.17;
|
||||
|
||||
const mfem::DenseMatrix identity = make_identity_matrix();
|
||||
mfem::DenseMatrix jacobian_variation(dimension);
|
||||
jacobian_variation = 0.0;
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
jacobian_variation(i, i) = scaling_variation;
|
||||
|
||||
const mapping::MappingPointContext context = make_context(identity);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(identity, jacobian_variation);
|
||||
|
||||
mfem::DenseMatrix tensor_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, tensor_variation
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
variation.mapping_determinant_variation,
|
||||
WithinAbs(3.0 * scaling_variation, 1.0e-14)
|
||||
);
|
||||
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
const double expected = row == column ? -scaling_variation : 0.0;
|
||||
CHECK_THAT(
|
||||
tensor_variation(row, column), WithinAbs(expected, 1.0e-14)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Symmetrizes Simple Shear At Identity",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
constexpr double shear_variation = 0.23;
|
||||
|
||||
const mfem::DenseMatrix identity = make_identity_matrix();
|
||||
mfem::DenseMatrix jacobian_variation(dimension);
|
||||
jacobian_variation = 0.0;
|
||||
jacobian_variation(0, 1) = shear_variation;
|
||||
|
||||
const mapping::MappingPointContext context = make_context(identity);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(identity, jacobian_variation);
|
||||
|
||||
mfem::DenseMatrix tensor_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, tensor_variation
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15)
|
||||
);
|
||||
CHECK_THAT(tensor_variation(0, 1), WithinAbs(shear_variation, 1.0e-14));
|
||||
CHECK_THAT(tensor_variation(1, 0), WithinAbs(shear_variation, 1.0e-14));
|
||||
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
if ((row == 0 && column == 1) || (row == 1 && column == 0))
|
||||
continue;
|
||||
CHECK_THAT(tensor_variation(row, column), WithinAbs(0.0, 1.0e-14));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Mapping Determinant Variation Matches Jacobi Formula",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
const mfem::DenseMatrix jacobian =
|
||||
make_matrix({1.24, 0.19, -0.07, -0.06, 0.88, 0.16, 0.04, -0.12, 1.19});
|
||||
|
||||
const mfem::DenseMatrix jacobian_variation =
|
||||
make_matrix({0.08, -0.03, 0.05, 0.02, 0.06, -0.04, -0.01, 0.07, -0.02});
|
||||
|
||||
const mapping::MappingPointContext context = make_context(jacobian);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(jacobian, jacobian_variation);
|
||||
constexpr double difference_step = 1.0e-3;
|
||||
|
||||
mfem::DenseMatrix plus_one(context.mapping_jacobian);
|
||||
mfem::DenseMatrix plus_two(context.mapping_jacobian);
|
||||
mfem::DenseMatrix minus_one(context.mapping_jacobian);
|
||||
mfem::DenseMatrix minus_two(context.mapping_jacobian);
|
||||
|
||||
plus_one.Add(difference_step, variation.mapping_jacobian_variation);
|
||||
plus_two.Add(2.0 * difference_step, variation.mapping_jacobian_variation);
|
||||
minus_one.Add(-difference_step, variation.mapping_jacobian_variation);
|
||||
minus_two.Add(-2.0 * difference_step, variation.mapping_jacobian_variation);
|
||||
|
||||
const double finite_difference = (minus_two.Det() - 8.0 * minus_one.Det() +
|
||||
8.0 * plus_one.Det() - plus_two.Det()) /
|
||||
(12.0 * difference_step);
|
||||
const double analytic = variation.mapping_determinant_variation;
|
||||
const double relative_error = std::abs(finite_difference - analytic) /
|
||||
std::max(std::abs(analytic), 1.0e-14);
|
||||
|
||||
INFO("Analytic determinant variation = " << analytic);
|
||||
INFO("Finite-difference determinant variation = " << finite_difference);
|
||||
INFO("Relative determinant-variation error = " << relative_error);
|
||||
|
||||
CHECK(relative_error < 2.0e-11);
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace barotropic_closure_context_test_utils {
|
||||
mfem::Vector project_field(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
mfem::Coefficient &coefficient
|
||||
) {
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
mfem::Vector make_density(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.55 + 0.025 * position(0) - 0.010 * position(1);
|
||||
});
|
||||
|
||||
return project_field(*f.densityFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.90 + 0.020 * position(0) - 0.010 * position(1) +
|
||||
0.005 * position(2);
|
||||
});
|
||||
|
||||
return project_field(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
} // namespace barotropic_closure_context_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Context Tracks Independent Revisions",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mean_field::operators::context::barotropic::
|
||||
BarotropicClosureLinearizationContext context(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
mfem::Vector density =
|
||||
barotropic_closure_context_test_utils::make_density(f);
|
||||
|
||||
mfem::Vector enthalpy =
|
||||
barotropic_closure_context_test_utils::make_enthalpy(f);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.0);
|
||||
|
||||
mean_field::operators::context::barotropic::BarotropicClosureRevisions
|
||||
revisions{.density = 3, .enthalpy = 5, .displacement = 7};
|
||||
|
||||
CHECK_FALSE(context.IsPrepared());
|
||||
CHECK(context.GetPreparationCount() == 0);
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
REQUIRE(context.IsPrepared());
|
||||
|
||||
CHECK(context.MatchesRevisions(revisions));
|
||||
CHECK(context.GetRevisions() == revisions);
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
|
||||
CHECK(context.GetOperator().GetPreparationCount() == 1);
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
|
||||
const double frozenDensityValue = context.GetBaseDensityTrue()(0);
|
||||
|
||||
density(0) += 0.125;
|
||||
|
||||
CHECK(context.GetBaseDensityTrue()(0) == frozenDensityValue);
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
CHECK(context.GetBaseDensityTrue()(0) == frozenDensityValue);
|
||||
|
||||
++revisions.density;
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 2);
|
||||
CHECK(context.GetBaseDensityTrue()(0) == density(0));
|
||||
|
||||
enthalpy(0) += 0.050;
|
||||
++revisions.enthalpy;
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 3);
|
||||
CHECK(context.GetBaseEnthalpyTrue()(0) == enthalpy(0));
|
||||
|
||||
displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
++revisions.displacement;
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 4);
|
||||
CHECK(context.GetRevisions() == revisions);
|
||||
CHECK(context.MatchesRevisions(revisions));
|
||||
|
||||
CHECK(context.GetOperator().GetPreparationCount() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Context Reprepares A Consistent Frozen State",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mean_field::operators::context::barotropic::
|
||||
BarotropicClosureLinearizationContext context(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
const mfem::Vector density =
|
||||
barotropic_closure_context_test_utils::make_density(f);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
barotropic_closure_context_test_utils::make_enthalpy(f);
|
||||
|
||||
const mfem::Vector identityDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.0);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.37
|
||||
);
|
||||
|
||||
const mfem::Vector enthalpyVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 0.71
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.displacementFes->GetTrueVSize(), 0.37
|
||||
);
|
||||
|
||||
mean_field::operators::context::barotropic::BarotropicClosureRevisions
|
||||
revisions{.density = 11, .enthalpy = 13, .displacement = 17};
|
||||
|
||||
context.Prepare(density, enthalpy, identityDisplacement, revisions);
|
||||
|
||||
mfem::Vector initialResidual;
|
||||
mfem::Vector initialAction;
|
||||
|
||||
context.BuildResidual(initialResidual);
|
||||
|
||||
context.GetOperator().Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
initialAction
|
||||
);
|
||||
|
||||
mfem::Vector changedDensity(density);
|
||||
changedDensity.Add(0.025, densityVariation);
|
||||
|
||||
mfem::Vector changedEnthalpy(enthalpy);
|
||||
changedEnthalpy.Add(0.015, enthalpyVariation);
|
||||
|
||||
const mfem::Vector deformedDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
context.Prepare(
|
||||
changedDensity, changedEnthalpy, deformedDisplacement, revisions
|
||||
);
|
||||
|
||||
mfem::Vector unchangedResidual;
|
||||
mfem::Vector unchangedAction;
|
||||
|
||||
context.BuildResidual(unchangedResidual);
|
||||
|
||||
context.GetOperator().Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
unchangedAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
unchangedResidual, initialResidual, communicator
|
||||
) < 5.0e-15
|
||||
);
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
unchangedAction, initialAction, communicator
|
||||
) < 5.0e-15
|
||||
);
|
||||
|
||||
++revisions.density;
|
||||
++revisions.enthalpy;
|
||||
++revisions.displacement;
|
||||
|
||||
context.Prepare(
|
||||
changedDensity, changedEnthalpy, deformedDisplacement, revisions
|
||||
);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector preparedAction;
|
||||
|
||||
context.BuildResidual(preparedResidual);
|
||||
|
||||
context.GetOperator().Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
preparedAction
|
||||
);
|
||||
|
||||
mfem::Vector referenceResidual;
|
||||
mfem::Vector referenceDensityAction;
|
||||
mfem::Vector referenceEnthalpyAction;
|
||||
mfem::Vector referenceDisplacementAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, changedDensity, changedEnthalpy,
|
||||
deformedDisplacement, referenceResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation,
|
||||
deformedDisplacement, referenceDensityAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
|
||||
f, *f.domainMapperStateless, barotrope, changedEnthalpy,
|
||||
enthalpyVariation, deformedDisplacement, referenceEnthalpyAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, changedDensity,
|
||||
changedEnthalpy, deformedDisplacement, displacementVariation,
|
||||
referenceDisplacementAction
|
||||
);
|
||||
|
||||
mfem::Vector referenceAction(referenceDensityAction);
|
||||
referenceAction += referenceEnthalpyAction;
|
||||
referenceAction += referenceDisplacementAction;
|
||||
const double residualError = gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, communicator
|
||||
);
|
||||
|
||||
const double actionError = gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, referenceAction, communicator
|
||||
);
|
||||
|
||||
const double residualChange = gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, initialResidual, communicator
|
||||
);
|
||||
|
||||
const double actionChange = gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, initialAction, communicator
|
||||
);
|
||||
|
||||
INFO("Prepared-context residual error = " << residualError);
|
||||
|
||||
INFO("Prepared-context Jacobian error = " << actionError);
|
||||
|
||||
INFO("Residual change after valid revision = " << residualChange);
|
||||
|
||||
INFO("Jacobian change after valid revision = " << actionChange);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 2);
|
||||
CHECK(residualError < 5.0e-12);
|
||||
CHECK(actionError < 5.0e-12);
|
||||
CHECK(residualChange > 1.0e-6);
|
||||
CHECK(actionChange > 1.0e-6);
|
||||
}
|
||||
306
tests/operators/contexts/gravity_field_context.cpp
Normal file
306
tests/operators/contexts/gravity_field_context.cpp
Normal file
@@ -0,0 +1,306 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
namespace prepared_test = gravity_prepared_test_utils;
|
||||
namespace gravity_context = operators::context::gravity_field;
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Field Linearization Context Applies Selective Invalidation",
|
||||
tags::integration &tags::gravity &tags::contexts
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
gravity_context::GravityFieldLinearizationContext context(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
|
||||
mfem::Vector density = prepared_test::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.11
|
||||
);
|
||||
mfem::Vector displacement = prepared_test::make_displacement(f, 0.0);
|
||||
mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.37
|
||||
);
|
||||
mfem::Vector gravity_potential = prepared_test::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.63
|
||||
);
|
||||
|
||||
gravity_context::GravityFieldRevisions revisions;
|
||||
|
||||
auto make_state = [&]() {
|
||||
return gravity_context::GravityFieldStateView{
|
||||
.density = density,
|
||||
.displacement = displacement,
|
||||
.gravity_gradient = gravity_gradient,
|
||||
.gravity_potential = gravity_potential
|
||||
};
|
||||
};
|
||||
|
||||
REQUIRE_FALSE(context.IsPrepared());
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport initial_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
REQUIRE(context.IsPrepared());
|
||||
CHECK(initial_report.geometry.reconstructed_operators);
|
||||
CHECK(initial_report.geometry.rebuilt_mass_operator);
|
||||
CHECK(initial_report.geometry.rebuilt_source_operator);
|
||||
CHECK(initial_report.geometry.refreshed_variation_state);
|
||||
CHECK(initial_report.updated_density);
|
||||
CHECK(initial_report.updated_gravity_gradient);
|
||||
CHECK(initial_report.DidAnyWork());
|
||||
|
||||
const auto initial_mass_preparations =
|
||||
context.GetGeometryContext().GetMassOperator().GetPreparationCount();
|
||||
const auto initial_source_preparations =
|
||||
context.GetGeometryContext().GetSourceOperator().GetPreparationCount();
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport repeated_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK_FALSE(repeated_report.DidAnyWork());
|
||||
CHECK(
|
||||
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
|
||||
initial_mass_preparations
|
||||
);
|
||||
CHECK(
|
||||
context.GetGeometryContext()
|
||||
.GetSourceOperator()
|
||||
.GetPreparationCount() == initial_source_preparations
|
||||
);
|
||||
|
||||
gravity_potential(0) += 0.25;
|
||||
++revisions.gravity_potential.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport potential_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK_FALSE(potential_report.DidAnyWork());
|
||||
CHECK(
|
||||
context.GetRevisions().gravity_potential == revisions.gravity_potential
|
||||
);
|
||||
|
||||
density(0) += 0.5;
|
||||
++revisions.density.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport density_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK(density_report.updated_density);
|
||||
CHECK_FALSE(density_report.updated_gravity_gradient);
|
||||
CHECK_FALSE(density_report.geometry.DidAnyWork());
|
||||
CHECK(context.GetDensity()(0) == density(0));
|
||||
|
||||
gravity_gradient(0) -= 0.4;
|
||||
++revisions.gravity_gradient.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport gradient_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK_FALSE(gradient_report.updated_density);
|
||||
CHECK(gradient_report.updated_gravity_gradient);
|
||||
CHECK_FALSE(gradient_report.geometry.DidAnyWork());
|
||||
CHECK(context.GetGravityGradient()(0) == gravity_gradient(0));
|
||||
|
||||
displacement = prepared_test::make_displacement(f, 1.0);
|
||||
++revisions.displacement.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport displacement_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK_FALSE(displacement_report.geometry.reconstructed_operators);
|
||||
CHECK(displacement_report.geometry.rebuilt_mass_operator);
|
||||
CHECK(displacement_report.geometry.rebuilt_source_operator);
|
||||
CHECK(displacement_report.geometry.refreshed_variation_state);
|
||||
CHECK_FALSE(displacement_report.updated_density);
|
||||
CHECK_FALSE(displacement_report.updated_gravity_gradient);
|
||||
CHECK(
|
||||
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
|
||||
initial_mass_preparations + 1
|
||||
);
|
||||
CHECK(
|
||||
context.GetGeometryContext()
|
||||
.GetSourceOperator()
|
||||
.GetPreparationCount() == initial_source_preparations + 1
|
||||
);
|
||||
|
||||
++revisions.discretization.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport discretization_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK(discretization_report.geometry.reconstructed_operators);
|
||||
CHECK(discretization_report.geometry.rebuilt_mass_operator);
|
||||
CHECK(discretization_report.geometry.rebuilt_source_operator);
|
||||
CHECK(discretization_report.updated_density);
|
||||
CHECK(discretization_report.updated_gravity_gradient);
|
||||
CHECK(
|
||||
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
|
||||
1
|
||||
);
|
||||
CHECK(
|
||||
context.GetGeometryContext()
|
||||
.GetSourceOperator()
|
||||
.GetPreparationCount() == 1
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Field Linearization Context Owns Frozen Base Fields",
|
||||
tags::integration &tags::gravity &tags::contexts
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
gravity_context::GravityFieldLinearizationContext context(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
|
||||
mfem::Vector density = prepared_test::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.13
|
||||
);
|
||||
mfem::Vector displacement = prepared_test::make_displacement(f, 0.4);
|
||||
mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.47
|
||||
);
|
||||
mfem::Vector gravity_potential = prepared_test::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.71
|
||||
);
|
||||
|
||||
gravity_context::GravityFieldRevisions revisions;
|
||||
|
||||
context.Prepare(
|
||||
{.density = density,
|
||||
.displacement = displacement,
|
||||
.gravity_gradient = gravity_gradient,
|
||||
.gravity_potential = gravity_potential},
|
||||
revisions
|
||||
);
|
||||
|
||||
const mfem::Vector frozen_density = context.GetDensity();
|
||||
const mfem::Vector frozen_displacement =
|
||||
context.GetGeometryContext().GetDisplacement();
|
||||
const mfem::Vector frozen_gravity_gradient = context.GetGravityGradient();
|
||||
|
||||
density = 0.0;
|
||||
displacement = 0.0;
|
||||
gravity_gradient = 0.0;
|
||||
gravity_potential = 0.0;
|
||||
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetDensity(), frozen_density, f.mesh->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetGeometryContext().GetDisplacement(), frozen_displacement,
|
||||
f.displacementFes->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetGravityGradient(), frozen_gravity_gradient,
|
||||
f.gravityFluxFes->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport
|
||||
unchanged_revision_report = context.Prepare(
|
||||
{.density = density,
|
||||
.displacement = displacement,
|
||||
.gravity_gradient = gravity_gradient,
|
||||
.gravity_potential = gravity_potential},
|
||||
revisions
|
||||
);
|
||||
|
||||
CHECK_FALSE(unchanged_revision_report.DidAnyWork());
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetDensity(), frozen_density, f.mesh->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetGeometryContext().GetDisplacement(), frozen_displacement,
|
||||
f.displacementFes->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetGravityGradient(), frozen_gravity_gradient,
|
||||
f.gravityFluxFes->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Field Geometry Contexts Have Independent Prepared State",
|
||||
tags::integration &tags::gravity &tags::contexts
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
gravity_context::GravityFieldGeometryContext first_context(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
gravity_context::GravityFieldGeometryContext second_context(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
|
||||
const mfem::Vector first_displacement =
|
||||
prepared_test::make_displacement(f, 0.0);
|
||||
const mfem::Vector second_displacement =
|
||||
prepared_test::make_displacement(f, 1.0);
|
||||
const mfem::Vector gravity_gradient =
|
||||
prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.35
|
||||
);
|
||||
|
||||
first_context.Prepare(first_displacement, {.value = 0}, {.value = 0});
|
||||
second_context.Prepare(second_displacement, {.value = 0}, {.value = 0});
|
||||
|
||||
mfem::Vector first_action;
|
||||
mfem::Vector second_action_before;
|
||||
mfem::Vector second_action_after;
|
||||
|
||||
first_context.GetMassOperator().Mult(gravity_gradient, first_action);
|
||||
second_context.GetMassOperator().Mult(
|
||||
gravity_gradient, second_action_before
|
||||
);
|
||||
|
||||
const mfem::Vector updated_first_displacement =
|
||||
prepared_test::make_displacement(f, 0.6);
|
||||
first_context.Prepare(
|
||||
updated_first_displacement, {.value = 0}, {.value = 1}
|
||||
);
|
||||
|
||||
second_context.GetMassOperator().Mult(
|
||||
gravity_gradient, second_action_after
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
const double independent_context_error = prepared_test::relative_error(
|
||||
second_action_after, second_action_before, communicator
|
||||
);
|
||||
const double distinct_geometry_difference = prepared_test::relative_error(
|
||||
first_action, second_action_before, communicator
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Second-context change after preparing first context = "
|
||||
<< independent_context_error
|
||||
);
|
||||
INFO(
|
||||
"Difference between independently prepared geometries = "
|
||||
<< distinct_geometry_difference
|
||||
);
|
||||
|
||||
CHECK(independent_context_error < 2.0e-14);
|
||||
CHECK(distinct_geometry_difference > 1.0e-5);
|
||||
}
|
||||
382
tests/operators/contexts/hydrostatic_equilibrium_context.cpp
Normal file
382
tests/operators/contexts/hydrostatic_equilibrium_context.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace hydrostatic_context_test_utils {
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies
|
||||
make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 101, .revision = 2},
|
||||
.enthalpy = {.identity = 103, .revision = 3},
|
||||
.gravityPotential = {.identity = 107, .revision = 5},
|
||||
.displacement = {.identity = 109, .revision = 7},
|
||||
.rotation = {.identity = 113, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 127, .revision = 13}
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
|
||||
make_state(
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement,
|
||||
const double bernoulliConstant
|
||||
) {
|
||||
return {
|
||||
.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant
|
||||
};
|
||||
}
|
||||
|
||||
void check_base_only(
|
||||
const mean_field::operators::context::hydrostatic::
|
||||
HydrostaticPreparationReport &report
|
||||
) {
|
||||
CHECK_FALSE(report.preparedStaticDependencies);
|
||||
CHECK_FALSE(report.preparedGeometryState);
|
||||
CHECK_FALSE(report.preparedRotationDependencies);
|
||||
CHECK(report.preparedBaseState);
|
||||
}
|
||||
} // namespace hydrostatic_context_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Hydrostatic Context Applies Selective Invalidation",
|
||||
tags::barotrope &tags::contexts &tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext
|
||||
context(f, *f.domainMapperStateless);
|
||||
|
||||
mfem::Vector enthalpy =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 0.17
|
||||
);
|
||||
|
||||
mfem::Vector gravityPotential =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.31
|
||||
);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.35);
|
||||
|
||||
double bernoulliConstant = 0.73;
|
||||
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies dependencies =
|
||||
hydrostatic_context_test_utils::make_dependencies();
|
||||
|
||||
CHECK_FALSE(context.IsPrepared());
|
||||
CHECK_FALSE(context.MatchesDependencies(dependencies));
|
||||
|
||||
const auto initialStatistics = context.GetPreparationStatistics();
|
||||
|
||||
CHECK(initialStatistics.staticPreparations == 0);
|
||||
CHECK(initialStatistics.geometryPreparations == 0);
|
||||
CHECK(initialStatistics.rotationPreparations == 0);
|
||||
CHECK(initialStatistics.baseStatePreparations == 0);
|
||||
|
||||
const auto initialReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
REQUIRE(context.IsPrepared());
|
||||
CHECK(context.MatchesDependencies(dependencies));
|
||||
CHECK(context.GetDependencies() == dependencies);
|
||||
|
||||
CHECK(initialReport.preparedStaticDependencies);
|
||||
CHECK(initialReport.preparedGeometryState);
|
||||
CHECK(initialReport.preparedRotationDependencies);
|
||||
CHECK(initialReport.preparedBaseState);
|
||||
CHECK(initialReport.updatedEnthalpy);
|
||||
CHECK(initialReport.updatedGravityPotential);
|
||||
CHECK(initialReport.updatedDisplacement);
|
||||
CHECK(initialReport.updatedBernoulliConstant);
|
||||
CHECK(initialReport.DidAnyWork());
|
||||
|
||||
const mfem::Vector frozenEnthalpy = context.GetBaseEnthalpyTrue();
|
||||
|
||||
const mfem::Vector frozenGravityPotential =
|
||||
context.GetBaseGravityPotentialTrue();
|
||||
|
||||
const mfem::Vector frozenDisplacement = context.GetDisplacementTrue();
|
||||
|
||||
const double frozenBernoulliConstant = context.GetBernoulliConstant();
|
||||
|
||||
enthalpy(0) += 0.125;
|
||||
gravityPotential(0) -= 0.075;
|
||||
displacement(0) += 0.050;
|
||||
bernoulliConstant += 0.20;
|
||||
|
||||
const auto repeatedReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(repeatedReport.DidAnyWork());
|
||||
CHECK_FALSE(repeatedReport.updatedEnthalpy);
|
||||
CHECK_FALSE(repeatedReport.updatedGravityPotential);
|
||||
CHECK_FALSE(repeatedReport.updatedDisplacement);
|
||||
CHECK_FALSE(repeatedReport.updatedBernoulliConstant);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
context.GetBaseEnthalpyTrue(), frozenEnthalpy, communicator
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
context.GetBaseGravityPotentialTrue(), frozenGravityPotential,
|
||||
communicator
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
context.GetDisplacementTrue(), frozenDisplacement, communicator
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
CHECK(context.GetBernoulliConstant() == frozenBernoulliConstant);
|
||||
|
||||
++dependencies.enthalpy.revision;
|
||||
|
||||
const auto enthalpyReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
hydrostatic_context_test_utils::check_base_only(enthalpyReport);
|
||||
|
||||
CHECK(enthalpyReport.updatedEnthalpy);
|
||||
CHECK_FALSE(enthalpyReport.updatedGravityPotential);
|
||||
CHECK_FALSE(enthalpyReport.updatedDisplacement);
|
||||
CHECK_FALSE(enthalpyReport.updatedBernoulliConstant);
|
||||
CHECK(context.GetBaseEnthalpyTrue()(0) == enthalpy(0));
|
||||
|
||||
++dependencies.gravityPotential.revision;
|
||||
|
||||
const auto gravityPotentialReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
hydrostatic_context_test_utils::check_base_only(gravityPotentialReport);
|
||||
|
||||
CHECK_FALSE(gravityPotentialReport.updatedEnthalpy);
|
||||
CHECK(gravityPotentialReport.updatedGravityPotential);
|
||||
CHECK_FALSE(gravityPotentialReport.updatedDisplacement);
|
||||
CHECK_FALSE(gravityPotentialReport.updatedBernoulliConstant);
|
||||
|
||||
CHECK(context.GetBaseGravityPotentialTrue()(0) == gravityPotential(0));
|
||||
|
||||
++dependencies.bernoulliConstant.revision;
|
||||
|
||||
const auto bernoulliReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
hydrostatic_context_test_utils::check_base_only(bernoulliReport);
|
||||
|
||||
CHECK_FALSE(bernoulliReport.updatedEnthalpy);
|
||||
CHECK_FALSE(bernoulliReport.updatedGravityPotential);
|
||||
CHECK_FALSE(bernoulliReport.updatedDisplacement);
|
||||
CHECK(bernoulliReport.updatedBernoulliConstant);
|
||||
|
||||
CHECK(context.GetBernoulliConstant() == bernoulliConstant);
|
||||
|
||||
++dependencies.rotation.revision;
|
||||
|
||||
const auto rotationReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(rotationReport.preparedStaticDependencies);
|
||||
CHECK_FALSE(rotationReport.preparedGeometryState);
|
||||
CHECK(rotationReport.preparedRotationDependencies);
|
||||
CHECK(rotationReport.preparedBaseState);
|
||||
CHECK_FALSE(rotationReport.updatedEnthalpy);
|
||||
CHECK_FALSE(rotationReport.updatedGravityPotential);
|
||||
CHECK_FALSE(rotationReport.updatedDisplacement);
|
||||
CHECK_FALSE(rotationReport.updatedBernoulliConstant);
|
||||
|
||||
++dependencies.displacement.revision;
|
||||
|
||||
const auto displacementReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(displacementReport.preparedStaticDependencies);
|
||||
|
||||
CHECK(displacementReport.preparedGeometryState);
|
||||
CHECK(displacementReport.preparedRotationDependencies);
|
||||
CHECK(displacementReport.preparedBaseState);
|
||||
CHECK_FALSE(displacementReport.updatedEnthalpy);
|
||||
CHECK_FALSE(displacementReport.updatedGravityPotential);
|
||||
CHECK(displacementReport.updatedDisplacement);
|
||||
CHECK_FALSE(displacementReport.updatedBernoulliConstant);
|
||||
|
||||
CHECK(context.GetDisplacementTrue()(0) == displacement(0));
|
||||
|
||||
++dependencies.discretization.revision;
|
||||
|
||||
const auto discretizationReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK(discretizationReport.preparedStaticDependencies);
|
||||
CHECK(discretizationReport.preparedGeometryState);
|
||||
CHECK(discretizationReport.preparedRotationDependencies);
|
||||
CHECK(discretizationReport.preparedBaseState);
|
||||
CHECK(discretizationReport.updatedEnthalpy);
|
||||
CHECK(discretizationReport.updatedGravityPotential);
|
||||
CHECK(discretizationReport.updatedDisplacement);
|
||||
CHECK(discretizationReport.updatedBernoulliConstant);
|
||||
|
||||
const auto finalStatistics = context.GetPreparationStatistics();
|
||||
|
||||
CHECK(finalStatistics.staticPreparations == 2);
|
||||
CHECK(finalStatistics.geometryPreparations == 3);
|
||||
CHECK(finalStatistics.rotationPreparations == 4);
|
||||
CHECK(finalStatistics.baseStatePreparations == 7);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hydrostatic Context Uses Identity In Every Dependency",
|
||||
tags::barotrope &tags::contexts &tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext
|
||||
context(f, *f.domainMapperStateless);
|
||||
|
||||
mfem::Vector enthalpy =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 0.23
|
||||
);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.41
|
||||
);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.60);
|
||||
|
||||
constexpr double bernoulliConstant = 0.81;
|
||||
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies dependencies =
|
||||
hydrostatic_context_test_utils::make_dependencies();
|
||||
|
||||
const auto preparedEnthalpyDependency = dependencies.enthalpy;
|
||||
|
||||
auto olderSameIdentity = preparedEnthalpyDependency;
|
||||
--olderSameIdentity.revision;
|
||||
|
||||
auto resetNewIdentity = preparedEnthalpyDependency;
|
||||
++resetNewIdentity.identity;
|
||||
resetNewIdentity.revision = 0;
|
||||
|
||||
CHECK_FALSE(olderSameIdentity.CanFollow(preparedEnthalpyDependency));
|
||||
|
||||
CHECK(resetNewIdentity.CanFollow(preparedEnthalpyDependency));
|
||||
|
||||
context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
const mfem::Vector firstFrozenEnthalpy = context.GetBaseEnthalpyTrue();
|
||||
|
||||
enthalpy(0) += 0.33;
|
||||
|
||||
const auto sameStampReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(sameStampReport.DidAnyWork());
|
||||
CHECK(context.GetBaseEnthalpyTrue()(0) == firstFrozenEnthalpy(0));
|
||||
|
||||
++dependencies.enthalpy.identity;
|
||||
dependencies.enthalpy.revision = 0;
|
||||
|
||||
const auto newIdentityReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
hydrostatic_context_test_utils::check_base_only(newIdentityReport);
|
||||
|
||||
CHECK(newIdentityReport.updatedEnthalpy);
|
||||
CHECK(context.GetBaseEnthalpyTrue()(0) == enthalpy(0));
|
||||
CHECK(context.MatchesDependencies(dependencies));
|
||||
|
||||
++dependencies.rotation.identity;
|
||||
dependencies.rotation.revision = 0;
|
||||
|
||||
const auto newRotationIdentityReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(newRotationIdentityReport.preparedStaticDependencies);
|
||||
|
||||
CHECK_FALSE(newRotationIdentityReport.preparedGeometryState);
|
||||
|
||||
CHECK(newRotationIdentityReport.preparedRotationDependencies);
|
||||
|
||||
CHECK(newRotationIdentityReport.preparedBaseState);
|
||||
|
||||
const auto statistics = context.GetPreparationStatistics();
|
||||
|
||||
CHECK(statistics.staticPreparations == 1);
|
||||
CHECK(statistics.geometryPreparations == 1);
|
||||
CHECK(statistics.rotationPreparations == 2);
|
||||
CHECK(statistics.baseStatePreparations == 3);
|
||||
}
|
||||
4483
tests/operators/gravity_field.cpp
Normal file
4483
tests/operators/gravity_field.cpp
Normal file
File diff suppressed because it is too large
Load Diff
628
tests/operators/kernels/barotropic_closure_kernels.cpp
Normal file
628
tests/operators/kernels/barotropic_closure_kernels.cpp
Normal file
@@ -0,0 +1,628 @@
|
||||
#include <memory>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
mfem::Vector make_zero_displacement(const mean_field::fem::FEM &f) {
|
||||
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
|
||||
displacement = 0.0;
|
||||
return displacement;
|
||||
}
|
||||
|
||||
mfem::Vector project_constant(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const double value
|
||||
) {
|
||||
mfem::ConstantCoefficient coefficient(value);
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
namespace barotropic_closure_geometry_test_utils {
|
||||
mfem::Vector project_scalar_field(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
mfem::Coefficient &coefficient
|
||||
) {
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
mfem::Vector make_base_density(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.55 + 0.025 * position(0) - 0.010 * position(1) +
|
||||
0.006 * position(2);
|
||||
}
|
||||
);
|
||||
|
||||
return project_scalar_field(*f.densityFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.90 + 0.020 * position(0) - 0.010 * position(1) +
|
||||
0.005 * position(2);
|
||||
}
|
||||
);
|
||||
|
||||
return project_scalar_field(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
} // namespace barotropic_closure_geometry_test_utils
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Vanishes For A Representable Constant State",
|
||||
tags::hydro &tags::residuals &tags::unit &tags::closure &tags::kernels
|
||||
&tags::barotrope
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
constexpr double enthalpyValue = 0.8;
|
||||
|
||||
const double densityValue = barotrope.density_from_enthalpy(enthalpyValue);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
project_constant(*f.enthalpyFes, enthalpyValue);
|
||||
|
||||
const mfem::Vector density = project_constant(*f.densityFes, densityValue);
|
||||
|
||||
const mfem::Vector displacement = make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residual;
|
||||
mfem::Vector scale;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, density, enthalpy, displacement,
|
||||
residual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, density, displacement, scale
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double relativeResidual =
|
||||
gravity_prepared_test_utils::global_norm(residual, communicator) /
|
||||
gravity_prepared_test_utils::global_norm(scale, communicator);
|
||||
|
||||
INFO("Relative constant-state closure residual = " << relativeResidual);
|
||||
|
||||
CHECK(relativeResidual < 5.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Density Action Matches The Stellar Mass Matrix",
|
||||
tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels
|
||||
&tags::barotrope
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector displacement = make_zero_displacement(f);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.37
|
||||
);
|
||||
|
||||
mfem::Vector kernelAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
|
||||
kernelAction
|
||||
);
|
||||
|
||||
mfem::Array<int> stellarMarker(f.mesh->attributes.Max());
|
||||
stellarMarker = 0;
|
||||
|
||||
const int vacuumAttribute =
|
||||
f.domainMapperStateless->GetVacuumElementAttribute();
|
||||
|
||||
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size();
|
||||
++attributeIndex) {
|
||||
const int attribute = f.mesh->attributes[attributeIndex];
|
||||
|
||||
if (attribute != vacuumAttribute) {
|
||||
stellarMarker[attribute - 1] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
mfem::ParBilinearForm massForm(f.densityFes.get());
|
||||
|
||||
massForm.AddDomainIntegrator(new mfem::MassIntegrator(), stellarMarker);
|
||||
|
||||
massForm.Assemble();
|
||||
massForm.Finalize();
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> massMatrix(
|
||||
massForm.ParallelAssemble()
|
||||
);
|
||||
|
||||
REQUIRE(massMatrix != nullptr);
|
||||
REQUIRE(massMatrix->Width() == densityVariation.Size());
|
||||
|
||||
mfem::Vector referenceAction(massMatrix->Height());
|
||||
referenceAction = 0.0;
|
||||
|
||||
massMatrix->Mult(densityVariation, referenceAction);
|
||||
const double relativeError = gravity_prepared_test_utils::relative_error(
|
||||
kernelAction, referenceAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Density-action mass-matrix error = " << relativeError);
|
||||
|
||||
CHECK(relativeError < 5.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Jacobian Matches A Combined Centered Difference",
|
||||
tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels
|
||||
&tags::barotrope
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mfem::FunctionCoefficient densityCoefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.4 + 0.03 * position(0) - 0.01 * position(1);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::FunctionCoefficient enthalpyCoefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.9 + 0.02 * position(0) - 0.01 * position(1);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::FunctionCoefficient enthalpyVariationCoefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.07 + 0.015 * position(0) + 0.008 * position(2);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::ParGridFunction densityField(f.densityFes.get());
|
||||
mfem::ParGridFunction enthalpyField(f.enthalpyFes.get());
|
||||
mfem::ParGridFunction enthalpyVariationField(f.enthalpyFes.get());
|
||||
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
enthalpyField.ProjectCoefficient(enthalpyCoefficient);
|
||||
enthalpyVariationField.ProjectCoefficient(enthalpyVariationCoefficient);
|
||||
|
||||
mfem::Vector density;
|
||||
mfem::Vector enthalpy;
|
||||
mfem::Vector enthalpyVariation;
|
||||
|
||||
densityField.GetTrueDofs(density);
|
||||
enthalpyField.GetTrueDofs(enthalpy);
|
||||
enthalpyVariationField.GetTrueDofs(enthalpyVariation);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.63
|
||||
);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
constexpr double differenceStep = 1.0e-6;
|
||||
|
||||
const mfem::Vector plusDensity =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
density, 1.0, densityVariation, differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector minusDensity =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
density, 1.0, densityVariation, -differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector plusEnthalpy =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
enthalpy, 1.0, enthalpyVariation, differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector minusEnthalpy =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
enthalpy, 1.0, enthalpyVariation, -differenceStep
|
||||
);
|
||||
|
||||
mfem::Vector plusResidual;
|
||||
mfem::Vector minusResidual;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy,
|
||||
displacement, plusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy,
|
||||
displacement, minusResidual
|
||||
);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference *= 1.0 / (2.0 * differenceStep);
|
||||
|
||||
mfem::Vector densityAction;
|
||||
mfem::Vector enthalpyAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
|
||||
densityAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpy, enthalpyVariation,
|
||||
displacement, enthalpyAction
|
||||
);
|
||||
|
||||
mfem::Vector analyticAction(densityAction);
|
||||
analyticAction += enthalpyAction;
|
||||
|
||||
const double relativeError = gravity_prepared_test_utils::relative_error(
|
||||
analyticAction, finiteDifference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Combined EOS Jacobian error = " << relativeError);
|
||||
|
||||
CHECK(relativeError < 2.0e-8);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Density Action Excludes Vacuum And Uses Mapped Volume",
|
||||
tags::hydro &tags::mapping &tags::unit &tags::closure &tags::barotrope
|
||||
&tags::kernels
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector stellarDensity =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, true);
|
||||
|
||||
const mfem::Vector vacuumDensity =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, false);
|
||||
|
||||
const mfem::Vector identityDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.0);
|
||||
|
||||
const mfem::Vector deformedDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
mfem::Vector stellarAction;
|
||||
mfem::Vector vacuumAction;
|
||||
mfem::Vector deformedAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, stellarDensity,
|
||||
identityDisplacement, stellarAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, vacuumDensity,
|
||||
identityDisplacement, vacuumAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, stellarDensity,
|
||||
deformedDisplacement, deformedAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double stellarNorm =
|
||||
gravity_prepared_test_utils::global_norm(stellarAction, communicator);
|
||||
|
||||
const double vacuumNorm =
|
||||
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
|
||||
|
||||
const double geometryChange = gravity_prepared_test_utils::relative_error(
|
||||
deformedAction, stellarAction, communicator
|
||||
);
|
||||
|
||||
INFO("Stellar action norm = " << stellarNorm);
|
||||
INFO("Vacuum action norm = " << vacuumNorm);
|
||||
INFO("Relative mapped-volume change = " << geometryChange);
|
||||
|
||||
CHECK(stellarNorm > 0.0);
|
||||
CHECK(vacuumNorm <= 1.0e-13 * stellarNorm);
|
||||
CHECK(geometryChange > 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Displacement Action Matches Centered Differences",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::integration
|
||||
&tags::jacobian &tags::mapping &tags::physics
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector baseDensity =
|
||||
barotropic_closure_geometry_test_utils::make_base_density(f);
|
||||
|
||||
const mfem::Vector baseEnthalpy =
|
||||
barotropic_closure_geometry_test_utils::make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.65);
|
||||
|
||||
constexpr double differenceStep = 1.0e-5;
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
for (const double deformationScale : {0.0, 1.0}) {
|
||||
DYNAMIC_SECTION("Base deformation scale = " << deformationScale) {
|
||||
const mfem::Vector baseDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(
|
||||
f, deformationScale
|
||||
);
|
||||
|
||||
mfem::Vector plusDisplacement(baseDisplacement);
|
||||
|
||||
mfem::Vector minusDisplacement(baseDisplacement);
|
||||
|
||||
plusDisplacement.Add(differenceStep, displacementVariation);
|
||||
|
||||
minusDisplacement.Add(-differenceStep, displacementVariation);
|
||||
|
||||
mfem::Vector plusResidual;
|
||||
mfem::Vector minusResidual;
|
||||
mfem::Vector analyticAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, plusDisplacement, plusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, minusDisplacement, minusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, baseDisplacement, displacementVariation,
|
||||
analyticAction
|
||||
);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference *= 1.0 / (2.0 * differenceStep);
|
||||
|
||||
const double analyticNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
analyticAction, communicator
|
||||
);
|
||||
|
||||
const double finiteDifferenceNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
finiteDifference, communicator
|
||||
);
|
||||
|
||||
const double relativeError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
analyticAction, finiteDifference, communicator
|
||||
);
|
||||
|
||||
INFO("Base deformation scale = " << deformationScale);
|
||||
|
||||
INFO("Analytic geometry-action norm = " << analyticNorm);
|
||||
|
||||
INFO(
|
||||
"Finite-difference geometry-action norm = "
|
||||
<< finiteDifferenceNorm
|
||||
);
|
||||
|
||||
INFO("Geometry-action relative error = " << relativeError);
|
||||
|
||||
REQUIRE(analyticNorm > 1.0e-12);
|
||||
REQUIRE(finiteDifferenceNorm > 1.0e-12);
|
||||
|
||||
CHECK(relativeError < 5.0e-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Displacement Action Is Linear In Its Direction",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::jacobian &tags::mapping
|
||||
&tags::physics &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector baseDensity =
|
||||
barotropic_closure_geometry_test_utils::make_base_density(f);
|
||||
|
||||
const mfem::Vector baseEnthalpy =
|
||||
barotropic_closure_geometry_test_utils::make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector baseDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.8);
|
||||
|
||||
const mfem::Vector firstDirection =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.4);
|
||||
|
||||
mfem::Vector secondDirection =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.displacementFes->GetTrueVSize(), 0.91
|
||||
);
|
||||
|
||||
secondDirection *= 0.01;
|
||||
|
||||
constexpr double firstScale = 1.7;
|
||||
constexpr double secondScale = -0.43;
|
||||
|
||||
mfem::Vector combinedDirection(firstDirection);
|
||||
|
||||
combinedDirection *= firstScale;
|
||||
|
||||
combinedDirection.Add(secondScale, secondDirection);
|
||||
|
||||
mfem::Vector zeroDirection(f.displacementFes->GetTrueVSize());
|
||||
zeroDirection = 0.0;
|
||||
|
||||
mfem::Vector firstAction;
|
||||
mfem::Vector secondAction;
|
||||
mfem::Vector combinedAction;
|
||||
mfem::Vector zeroAction;
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
baseDisplacement, firstDirection, firstAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
baseDisplacement, secondDirection, secondAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
baseDisplacement, combinedDirection, combinedAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
baseDisplacement, zeroDirection, zeroAction
|
||||
);
|
||||
|
||||
mfem::Vector expectedAction(firstAction);
|
||||
|
||||
expectedAction *= firstScale;
|
||||
|
||||
expectedAction.Add(secondScale, secondAction);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double expectedNorm =
|
||||
gravity_prepared_test_utils::global_norm(expectedAction, communicator);
|
||||
|
||||
const double linearityError = gravity_prepared_test_utils::relative_error(
|
||||
combinedAction, expectedAction, communicator
|
||||
);
|
||||
|
||||
const double zeroActionNorm =
|
||||
gravity_prepared_test_utils::global_norm(zeroAction, communicator);
|
||||
|
||||
INFO("Expected combined-action norm = " << expectedNorm);
|
||||
|
||||
INFO("Directional-linearity error = " << linearityError);
|
||||
|
||||
INFO("Zero-direction action norm = " << zeroActionNorm);
|
||||
|
||||
REQUIRE(expectedNorm > 1.0e-12);
|
||||
|
||||
CHECK(linearityError < 5.0e-12);
|
||||
|
||||
CHECK(zeroActionNorm <= 5.0e-14 * expectedNorm);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Displacement Action Excludes Vacuum",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::physics
|
||||
&tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector stellarDensity =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, true);
|
||||
|
||||
const mfem::Vector vacuumDensity =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, false);
|
||||
|
||||
mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
|
||||
zeroEnthalpy = 0.0;
|
||||
|
||||
const mfem::Vector baseDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.7);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.5);
|
||||
|
||||
mfem::Vector stellarAction;
|
||||
mfem::Vector vacuumAction;
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, stellarDensity,
|
||||
zeroEnthalpy, baseDisplacement, displacementVariation, stellarAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, vacuumDensity, zeroEnthalpy,
|
||||
baseDisplacement, displacementVariation, vacuumAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double stellarNorm =
|
||||
gravity_prepared_test_utils::global_norm(stellarAction, communicator);
|
||||
|
||||
const double vacuumNorm =
|
||||
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
|
||||
|
||||
INFO("Stellar geometry-action norm = " << stellarNorm);
|
||||
|
||||
INFO("Vacuum geometry-action norm = " << vacuumNorm);
|
||||
|
||||
REQUIRE(stellarNorm > 1.0e-12);
|
||||
|
||||
CHECK(vacuumNorm <= 1.0e-13 * stellarNorm);
|
||||
}
|
||||
0
tests/operators/kernels/gravity_kernels.cpp
Normal file
0
tests/operators/kernels/gravity_kernels.cpp
Normal file
1054
tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp
Normal file
1054
tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp
Normal file
File diff suppressed because it is too large
Load Diff
424
tests/operators/kernels/pressure_force_kernels.cpp
Normal file
424
tests/operators/kernels/pressure_force_kernels.cpp
Normal file
@@ -0,0 +1,424 @@
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <array>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace pressure_force_kernel_test_utils {
|
||||
[[nodiscard]] mfem::Vector make_deterministic_vector(
|
||||
const int size,
|
||||
const double phase
|
||||
) {
|
||||
mfem::Vector vector(size);
|
||||
|
||||
for (int index = 0; index < size; ++index) {
|
||||
const double position = static_cast<double>(index + 1);
|
||||
|
||||
vector(index) = 0.71 + 0.19 * std::sin(0.31 * position + phase) +
|
||||
0.08 * std::cos(0.17 * position - 0.5 * phase);
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_zero_displacement(const mean_field::fem::FEM &f) {
|
||||
mfem::Vector displacementTrue(f.displacementFes->GetTrueVSize());
|
||||
displacementTrue = 0.0;
|
||||
return displacementTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_vacuum_only_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::Vector enthalpyTrue =
|
||||
make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.43);
|
||||
|
||||
mfem::Array<int> stellarElementMask;
|
||||
mean_field::utils::populate_element_mask(
|
||||
f.mesh.get(), mean_field::utils::DOMAINS::STELLAR,
|
||||
stellarElementMask
|
||||
);
|
||||
|
||||
mfem::Array<int> stellarEnthalpyTrueDofs;
|
||||
mean_field::utils::populate_domain_tdofs(
|
||||
f.enthalpyFes.get(), stellarElementMask, stellarEnthalpyTrueDofs
|
||||
);
|
||||
|
||||
for (int listIndex = 0; listIndex < stellarEnthalpyTrueDofs.Size();
|
||||
++listIndex) {
|
||||
const int trueDof = stellarEnthalpyTrueDofs[listIndex];
|
||||
|
||||
MFEM_VERIFY(
|
||||
trueDof >= 0 && trueDof < enthalpyTrue.Size(),
|
||||
"The stellar enthalpy true-DOF mask contains an "
|
||||
"invalid index."
|
||||
);
|
||||
|
||||
enthalpyTrue(trueDof) = 0.0;
|
||||
}
|
||||
|
||||
return enthalpyTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 1.10 + 0.07 * position(0) - 0.04 * position(1) +
|
||||
0.03 * position(2);
|
||||
});
|
||||
|
||||
mfem::ParGridFunction enthalpyField(f.enthalpyFes.get());
|
||||
|
||||
enthalpyField.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector enthalpyTrue;
|
||||
enthalpyField.GetTrueDofs(enthalpyTrue);
|
||||
|
||||
return enthalpyTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector make_component_test_field(
|
||||
const mean_field::fem::FEM &f,
|
||||
const int component,
|
||||
const int coordinate
|
||||
) {
|
||||
const int dimension = f.mesh->Dimension();
|
||||
|
||||
MFEM_VERIFY(
|
||||
component >= 0 && component < dimension,
|
||||
"The requested vector component is invalid."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
coordinate >= -1 && coordinate < dimension,
|
||||
"The requested coordinate is invalid."
|
||||
);
|
||||
|
||||
/*
|
||||
* coordinate == -1 gives the rigid translation e_component.
|
||||
*
|
||||
* Otherwise this gives
|
||||
*
|
||||
* w = x_coordinate e_component.
|
||||
*/
|
||||
mfem::VectorFunctionCoefficient coefficient(
|
||||
dimension,
|
||||
[component, coordinate,
|
||||
dimension](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(dimension);
|
||||
value = 0.0;
|
||||
|
||||
value(component) = coordinate < 0 ? 1.0 : position(coordinate);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::ParGridFunction field(f.displacementFes.get());
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector fieldTrue;
|
||||
field.GetTrueDofs(fieldTrue);
|
||||
|
||||
return fieldTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] double global_dot(
|
||||
const mfem::Vector &left,
|
||||
const mfem::Vector &right,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
left.Size() == right.Size(),
|
||||
"The global dot-product vectors have different sizes."
|
||||
);
|
||||
|
||||
const double localDot = left * right;
|
||||
double globalDot = 0.0;
|
||||
|
||||
MPI_Allreduce(
|
||||
&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator
|
||||
);
|
||||
|
||||
return globalDot;
|
||||
}
|
||||
} // namespace pressure_force_kernel_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Vanishes For Zero Enthalpy",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
|
||||
enthalpyTrue = 0.0;
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
|
||||
|
||||
const double residualNorm = gravity_prepared_test_utils::global_norm(
|
||||
residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
CHECK(residualNorm == 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Excludes Vacuum Enthalpy Exactly",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
const mfem::Vector enthalpyTrue =
|
||||
pressure_force_kernel_test_utils::make_vacuum_only_enthalpy(f);
|
||||
|
||||
const double enthalpyNorm = gravity_prepared_test_utils::global_norm(
|
||||
enthalpyTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
/*
|
||||
* Ensure this is a real exclusion test rather than another
|
||||
* all-zero-input test.
|
||||
*/
|
||||
REQUIRE(enthalpyNorm > 0.0);
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
|
||||
|
||||
const double residualNorm = gravity_prepared_test_utils::global_norm(
|
||||
residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
CHECK(residualNorm == 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Is Nonzero For Positive Stellar Pressure",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
/*
|
||||
* With n = 3 and K = 1/4:
|
||||
*
|
||||
* P(1) = 1/4.
|
||||
*/
|
||||
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
|
||||
enthalpyTrue = 1.0;
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
const double residualNorm = gravity_prepared_test_utils::global_norm(
|
||||
residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Positive-pressure residual norm = " << residualNorm);
|
||||
|
||||
CHECK(std::isfinite(residualNorm));
|
||||
|
||||
CHECK(residualNorm > 100.0 * std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Does No Work Against Rigid Translations",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
&tags::accuracy
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
REQUIRE(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
const mfem::Vector enthalpyTrue =
|
||||
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
const double residualNorm = gravity_prepared_test_utils::global_norm(
|
||||
residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
REQUIRE(residualNorm > 0.0);
|
||||
|
||||
const int dimension = f.mesh->Dimension();
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
const mfem::Vector translationTrue =
|
||||
pressure_force_kernel_test_utils::make_component_test_field(
|
||||
f, component, -1
|
||||
);
|
||||
|
||||
const double translationNorm = gravity_prepared_test_utils::global_norm(
|
||||
translationTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double translationWork =
|
||||
pressure_force_kernel_test_utils::global_dot(
|
||||
translationTrue, residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double dotProductScale =
|
||||
std::fmax(residualNorm * translationNorm, 1.0);
|
||||
|
||||
CAPTURE(component, translationWork, dotProductScale);
|
||||
|
||||
CHECK(std::abs(translationWork) <= 5.0e-12 * dotProductScale);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Respects byNODES Component Layout",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
&tags::accuracy
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
REQUIRE(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
const mfem::Vector enthalpyTrue =
|
||||
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
const int dimension = f.mesh->Dimension();
|
||||
|
||||
REQUIRE(dimension == 3);
|
||||
|
||||
mfem::DenseMatrix virtualWork(dimension, dimension);
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
|
||||
const mfem::Vector affineTestTrue =
|
||||
pressure_force_kernel_test_utils::make_component_test_field(
|
||||
f, component, coordinate
|
||||
);
|
||||
|
||||
virtualWork(component, coordinate) =
|
||||
pressure_force_kernel_test_utils::global_dot(
|
||||
affineTestTrue, residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double meanDiagonalWork = 0.0;
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
meanDiagonalWork += virtualWork(component, component);
|
||||
}
|
||||
|
||||
meanDiagonalWork /= static_cast<double>(dimension);
|
||||
|
||||
// INFO(
|
||||
// "Affine pressure virtual-work tensor:\n"
|
||||
// << virtualWork
|
||||
// );
|
||||
|
||||
INFO("Mean diagonal virtual work = " << meanDiagonalWork);
|
||||
|
||||
REQUIRE(
|
||||
std::abs(meanDiagonalWork) >
|
||||
100.0 * std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
|
||||
const double comparisonTolerance = 1.0e-8 * std::abs(meanDiagonalWork);
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
|
||||
const double computedWork = virtualWork(component, coordinate);
|
||||
|
||||
CAPTURE(
|
||||
component, coordinate, computedWork, meanDiagonalWork,
|
||||
comparisonTolerance
|
||||
);
|
||||
|
||||
if (component == coordinate) {
|
||||
CHECK(
|
||||
std::abs(computedWork - meanDiagonalWork) <=
|
||||
comparisonTolerance
|
||||
);
|
||||
} else {
|
||||
CHECK(std::abs(computedWork) <= comparisonTolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
995
tests/operators/prepared_barotropic_closure.cpp
Normal file
995
tests/operators/prepared_barotropic_closure.cpp
Normal file
@@ -0,0 +1,995 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
mfem::Vector project_scalar(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
mfem::Coefficient &coefficient
|
||||
) {
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
mfem::Vector make_base_density(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.42 + 0.025 * position(0) - 0.012 * position(1) +
|
||||
0.007 * position(2);
|
||||
});
|
||||
|
||||
return project_scalar(*f.densityFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.92 + 0.018 * position(0) - 0.011 * position(1) +
|
||||
0.006 * position(2);
|
||||
});
|
||||
|
||||
return project_scalar(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy_variation(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.065 + 0.014 * position(0) + 0.009 * position(2);
|
||||
});
|
||||
|
||||
return project_scalar(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_combined_variation(
|
||||
const mfem::Vector &densityVariation,
|
||||
const mfem::Vector &enthalpyVariation
|
||||
) {
|
||||
mfem::Vector combinedVariation(
|
||||
densityVariation.Size() + enthalpyVariation.Size()
|
||||
);
|
||||
|
||||
for (int densityDof = 0; densityDof < densityVariation.Size();
|
||||
++densityDof) {
|
||||
combinedVariation(densityDof) = densityVariation(densityDof);
|
||||
}
|
||||
|
||||
for (int enthalpyDof = 0; enthalpyDof < enthalpyVariation.Size();
|
||||
++enthalpyDof) {
|
||||
combinedVariation(densityVariation.Size() + enthalpyDof) =
|
||||
enthalpyVariation(enthalpyDof);
|
||||
}
|
||||
|
||||
return combinedVariation;
|
||||
}
|
||||
struct ClosureCondition {
|
||||
const char *name;
|
||||
|
||||
double polytropicIndex;
|
||||
double polytropicConstant;
|
||||
|
||||
double enthalpyOffset;
|
||||
double enthalpyGradient;
|
||||
|
||||
double densityFactor;
|
||||
double densityOffset;
|
||||
double densityGradient;
|
||||
|
||||
double deformationScale;
|
||||
double directionPhase;
|
||||
};
|
||||
|
||||
inline constexpr std::array<ClosureCondition, 3> conditions{
|
||||
{{.name = "Linear barotrope on identity geometry",
|
||||
.polytropicIndex = 1.0,
|
||||
.polytropicConstant = 0.8,
|
||||
.enthalpyOffset = 0.65,
|
||||
.enthalpyGradient = 0.06,
|
||||
.densityFactor = 0.80,
|
||||
.densityOffset = 0.015,
|
||||
.densityGradient = 0.004,
|
||||
.deformationScale = 0.0,
|
||||
.directionPhase = 0.31},
|
||||
{.name = "Fractional barotrope on moderate deformation",
|
||||
.polytropicIndex = 1.5,
|
||||
.polytropicConstant = 1.2,
|
||||
.enthalpyOffset = 0.90,
|
||||
.enthalpyGradient = 0.09,
|
||||
.densityFactor = 1.15,
|
||||
.densityOffset = -0.003,
|
||||
.densityGradient = 0.003,
|
||||
.deformationScale = 0.45,
|
||||
.directionPhase = 0.53},
|
||||
{.name = "Target n=3 barotrope on strong deformation",
|
||||
.polytropicIndex = 3.0,
|
||||
.polytropicConstant = 1.5,
|
||||
.enthalpyOffset = 1.20,
|
||||
.enthalpyGradient = 0.12,
|
||||
.densityFactor = 1.40,
|
||||
.densityOffset = 0.006,
|
||||
.densityGradient = 0.002,
|
||||
.deformationScale = 1.0,
|
||||
.directionPhase = 0.79}}
|
||||
};
|
||||
|
||||
double evaluate_enthalpy(
|
||||
const mfem::Vector &position,
|
||||
const ClosureCondition &condition
|
||||
) {
|
||||
return condition.enthalpyOffset +
|
||||
condition.enthalpyGradient *
|
||||
(0.50 * position(0) - 0.30 * position(1) +
|
||||
0.20 * position(2));
|
||||
}
|
||||
|
||||
mfem::Vector project_scalar_field(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
mfem::Coefficient &coefficient
|
||||
) {
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy(
|
||||
const mean_field::fem::FEM &f,
|
||||
const ClosureCondition &condition
|
||||
) {
|
||||
mfem::FunctionCoefficient coefficient(
|
||||
[condition](const mfem::Vector &position) {
|
||||
return evaluate_enthalpy(position, condition);
|
||||
}
|
||||
);
|
||||
|
||||
return project_scalar_field(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_density(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope,
|
||||
const ClosureCondition &condition
|
||||
) {
|
||||
mfem::FunctionCoefficient coefficient(
|
||||
[&barotrope, condition](const mfem::Vector &position) {
|
||||
const double enthalpy = evaluate_enthalpy(position, condition);
|
||||
|
||||
return condition.densityFactor *
|
||||
barotrope.density_from_enthalpy(enthalpy) +
|
||||
condition.densityOffset +
|
||||
condition.densityGradient *
|
||||
(0.40 * position(0) + 0.25 * position(1) -
|
||||
0.15 * position(2));
|
||||
}
|
||||
);
|
||||
|
||||
return project_scalar_field(*f.densityFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_constant_field(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const double value
|
||||
) {
|
||||
mfem::ConstantCoefficient coefficient(value);
|
||||
|
||||
return project_scalar_field(finiteElementSpace, coefficient);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Barotropic Closure Matches Stateless Kernels",
|
||||
tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
REQUIRE_FALSE(preparedOperator.IsPrepared());
|
||||
REQUIRE(preparedOperator.Height() == f.densityFes->GetTrueVSize());
|
||||
REQUIRE(
|
||||
preparedOperator.Width() ==
|
||||
f.densityFes->GetTrueVSize() + f.enthalpyFes->GetTrueVSize()
|
||||
);
|
||||
REQUIRE(preparedOperator.GetDensitySize() == f.densityFes->GetTrueVSize());
|
||||
REQUIRE(
|
||||
preparedOperator.GetEnthalpySize() == f.enthalpyFes->GetTrueVSize()
|
||||
);
|
||||
|
||||
const mfem::Vector baseDensity = make_base_density(f);
|
||||
|
||||
const mfem::Vector baseEnthalpy = make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.43
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.63);
|
||||
|
||||
const mfem::Vector enthalpyVariation = make_enthalpy_variation(f);
|
||||
|
||||
mfem::Vector zeroDensity(f.densityFes->GetTrueVSize());
|
||||
zeroDensity = 0.0;
|
||||
|
||||
mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
|
||||
zeroEnthalpy = 0.0;
|
||||
|
||||
preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector preparedDensityAction;
|
||||
mfem::Vector preparedEnthalpyAction;
|
||||
mfem::Vector preparedSplitAction;
|
||||
mfem::Vector preparedCombinedAction;
|
||||
|
||||
preparedOperator.BuildResidual(preparedResidual);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, zeroEnthalpy, displacementVariation,
|
||||
preparedDensityAction
|
||||
);
|
||||
|
||||
preparedOperator.Mult(
|
||||
zeroDensity, enthalpyVariation, displacementVariation,
|
||||
preparedEnthalpyAction
|
||||
);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
preparedSplitAction
|
||||
);
|
||||
|
||||
const mfem::Vector combinedVariation =
|
||||
make_combined_variation(densityVariation, enthalpyVariation);
|
||||
|
||||
preparedOperator.Mult(combinedVariation, preparedCombinedAction);
|
||||
|
||||
mfem::Vector referenceResidual;
|
||||
mfem::Vector referenceDensityAction;
|
||||
mfem::Vector referenceEnthalpyAction;
|
||||
mfem::Vector referenceDisplacementAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
displacement, referenceResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
|
||||
referenceDensityAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseEnthalpy, enthalpyVariation,
|
||||
displacement, referenceEnthalpyAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
displacement, displacementVariation, referenceDisplacementAction
|
||||
);
|
||||
|
||||
mfem::Vector referenceCombinedAction(referenceDensityAction);
|
||||
referenceCombinedAction += referenceEnthalpyAction;
|
||||
referenceCombinedAction += referenceDisplacementAction;
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double residualError = gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, communicator
|
||||
);
|
||||
|
||||
const double densityError = gravity_prepared_test_utils::relative_error(
|
||||
preparedDensityAction, referenceDensityAction, communicator
|
||||
);
|
||||
|
||||
const double enthalpyError = gravity_prepared_test_utils::relative_error(
|
||||
preparedEnthalpyAction, referenceEnthalpyAction, communicator
|
||||
);
|
||||
|
||||
const double splitError = gravity_prepared_test_utils::relative_error(
|
||||
preparedSplitAction, referenceCombinedAction, communicator
|
||||
);
|
||||
|
||||
const double combinedError = gravity_prepared_test_utils::relative_error(
|
||||
preparedCombinedAction, referenceCombinedAction, communicator
|
||||
);
|
||||
|
||||
INFO("Prepared residual error = " << residualError);
|
||||
INFO("Prepared density-action error = " << densityError);
|
||||
INFO("Prepared enthalpy-action error = " << enthalpyError);
|
||||
INFO("Prepared split-action error = " << splitError);
|
||||
INFO("Prepared combined-action error = " << combinedError);
|
||||
|
||||
CHECK(preparedOperator.IsPrepared());
|
||||
CHECK(preparedOperator.GetPreparationCount() == 1);
|
||||
CHECK(residualError < 2.0e-12);
|
||||
CHECK(densityError < 2.0e-12);
|
||||
CHECK(enthalpyError < 2.0e-12);
|
||||
CHECK(splitError < 2.0e-12);
|
||||
CHECK(combinedError < 2.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Barotropic Closure Jacobian Matches Centered Difference",
|
||||
tags::hydro &tags::jacobian &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mfem::Vector baseDensity = make_base_density(f);
|
||||
|
||||
mfem::Vector baseEnthalpy = make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.71
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.63);
|
||||
|
||||
const mfem::Vector enthalpyVariation = make_enthalpy_variation(f);
|
||||
|
||||
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement);
|
||||
|
||||
mfem::Vector analyticAction;
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
analyticAction
|
||||
);
|
||||
|
||||
constexpr double differenceStep = 1.0e-6;
|
||||
|
||||
const mfem::Vector plusDensity =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
baseDensity, 1.0, densityVariation, differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector minusDensity =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
baseDensity, 1.0, densityVariation, -differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector plusEnthalpy =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
baseEnthalpy, 1.0, enthalpyVariation, differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector minusEnthalpy =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
baseEnthalpy, 1.0, enthalpyVariation, -differenceStep
|
||||
);
|
||||
|
||||
mfem::Vector plusResidual;
|
||||
mfem::Vector minusResidual;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy,
|
||||
displacement, plusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy,
|
||||
displacement, minusResidual
|
||||
);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference *= 1.0 / (2.0 * differenceStep);
|
||||
|
||||
const double relativeError = gravity_prepared_test_utils::relative_error(
|
||||
analyticAction, finiteDifference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Prepared EOS centered-difference error = " << relativeError);
|
||||
|
||||
CHECK(relativeError < 2.0e-8);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Barotropic Closure Reuses Frozen Data",
|
||||
tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||
CHECK(preparedOperator.GetPreparationCount() == 0);
|
||||
|
||||
mfem::Vector baseDensity = make_base_density(f);
|
||||
|
||||
mfem::Vector baseEnthalpy = make_base_enthalpy(f);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.63);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.31
|
||||
);
|
||||
|
||||
const mfem::Vector enthalpyVariation = make_enthalpy_variation(f);
|
||||
|
||||
preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement);
|
||||
|
||||
REQUIRE(preparedOperator.IsPrepared());
|
||||
REQUIRE(preparedOperator.GetPreparationCount() == 1);
|
||||
|
||||
mfem::Vector firstResidual;
|
||||
mfem::Vector firstAction;
|
||||
|
||||
preparedOperator.BuildResidual(firstResidual);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation, firstAction
|
||||
);
|
||||
|
||||
baseDensity = 7.0;
|
||||
baseEnthalpy = 3.0;
|
||||
displacement *= -4.0;
|
||||
|
||||
const std::uint64_t preparationCount =
|
||||
preparedOperator.GetPreparationCount();
|
||||
|
||||
mfem::Vector repeatedResidual;
|
||||
mfem::Vector repeatedAction;
|
||||
|
||||
preparedOperator.BuildResidual(repeatedResidual);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
repeatedAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double residualReuseError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
repeatedResidual, firstResidual, communicator
|
||||
);
|
||||
|
||||
const double actionReuseError = gravity_prepared_test_utils::relative_error(
|
||||
repeatedAction, firstAction, communicator
|
||||
);
|
||||
|
||||
INFO("Frozen residual reuse error = " << residualReuseError);
|
||||
INFO("Frozen action reuse error = " << actionReuseError);
|
||||
|
||||
CHECK(residualReuseError < 2.0e-14);
|
||||
CHECK(actionReuseError < 2.0e-14);
|
||||
CHECK(preparedOperator.GetPreparationCount() == preparationCount);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Barotropic Closure Reprepares For New Geometry",
|
||||
tags::hydro &tags::mapping &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector baseDensity = make_base_density(f);
|
||||
|
||||
const mfem::Vector baseEnthalpy = make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.59
|
||||
);
|
||||
|
||||
mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
|
||||
zeroEnthalpy = 0.0;
|
||||
|
||||
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
mfem::Vector identityAction;
|
||||
mfem::Vector deformedAction;
|
||||
|
||||
for (const double deformationScale : {0.0, 1.0}) {
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, deformationScale);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.63);
|
||||
|
||||
preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector preparedAction;
|
||||
mfem::Vector referenceResidual;
|
||||
mfem::Vector referenceAction;
|
||||
|
||||
preparedOperator.BuildResidual(preparedResidual);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, zeroEnthalpy, displacementVariation,
|
||||
preparedAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
displacement, referenceResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation,
|
||||
displacement, referenceAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double residualError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, communicator
|
||||
);
|
||||
|
||||
const double actionError = gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, referenceAction, communicator
|
||||
);
|
||||
|
||||
INFO("Deformation scale = " << deformationScale);
|
||||
INFO("Reprepared residual error = " << residualError);
|
||||
INFO("Reprepared action error = " << actionError);
|
||||
|
||||
CHECK(residualError < 2.0e-12);
|
||||
CHECK(actionError < 2.0e-12);
|
||||
|
||||
if (deformationScale == 0.0) {
|
||||
identityAction = preparedAction;
|
||||
} else {
|
||||
deformedAction = preparedAction;
|
||||
}
|
||||
}
|
||||
|
||||
const double geometryChange = gravity_prepared_test_utils::relative_error(
|
||||
deformedAction, identityAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Prepared closure geometry change = " << geometryChange);
|
||||
|
||||
CHECK(preparedOperator.GetPreparationCount() == 2);
|
||||
CHECK(geometryChange > 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Complete Barotropic Closure Matches Blocks And Centered Differences "
|
||||
"Across Conditions",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::integration
|
||||
&tags::jacobian &tags::mapping &tags::physics &tags::prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
constexpr double differenceStep = 1.0e-5;
|
||||
|
||||
for (std::size_t conditionIndex = 0; conditionIndex <
|
||||
|
||||
conditions.size();
|
||||
++conditionIndex) {
|
||||
const auto &condition =
|
||||
|
||||
conditions[conditionIndex];
|
||||
|
||||
DYNAMIC_SECTION(condition.name) {
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(
|
||||
condition.polytropicIndex, condition.polytropicConstant
|
||||
);
|
||||
|
||||
const mfem::Vector baseDensity =
|
||||
|
||||
make_density(f, barotrope, condition);
|
||||
|
||||
const mfem::Vector baseEnthalpy =
|
||||
|
||||
make_enthalpy(f, condition);
|
||||
|
||||
const mfem::Vector baseDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(
|
||||
f, condition.deformationScale
|
||||
);
|
||||
|
||||
mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), condition.directionPhase
|
||||
);
|
||||
|
||||
mfem::Vector enthalpyVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(),
|
||||
condition.directionPhase + 0.27
|
||||
);
|
||||
|
||||
mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(
|
||||
f, condition.directionPhase
|
||||
);
|
||||
|
||||
mfem::Vector densityAction;
|
||||
mfem::Vector enthalpyAction;
|
||||
mfem::Vector displacementAction;
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation,
|
||||
baseDisplacement, densityAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_enthalpy_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseEnthalpy,
|
||||
enthalpyVariation, baseDisplacement, enthalpyAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, baseDisplacement, displacementVariation,
|
||||
displacementAction
|
||||
);
|
||||
|
||||
double densityActionNorm = gravity_prepared_test_utils::global_norm(
|
||||
densityAction, communicator
|
||||
);
|
||||
|
||||
double enthalpyActionNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
enthalpyAction, communicator
|
||||
);
|
||||
|
||||
double displacementActionNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
displacementAction, communicator
|
||||
);
|
||||
|
||||
REQUIRE(densityActionNorm > 1.0e-12);
|
||||
REQUIRE(enthalpyActionNorm > 1.0e-12);
|
||||
REQUIRE(displacementActionNorm > 1.0e-12);
|
||||
|
||||
const double targetActionNorm = std::min(
|
||||
{densityActionNorm, enthalpyActionNorm, displacementActionNorm}
|
||||
);
|
||||
|
||||
const double densityScale = targetActionNorm / densityActionNorm;
|
||||
|
||||
const double enthalpyScale = targetActionNorm / enthalpyActionNorm;
|
||||
|
||||
const double displacementScale =
|
||||
targetActionNorm / displacementActionNorm;
|
||||
|
||||
densityVariation *= densityScale;
|
||||
densityAction *= densityScale;
|
||||
|
||||
enthalpyVariation *= enthalpyScale;
|
||||
enthalpyAction *= enthalpyScale;
|
||||
|
||||
displacementVariation *= displacementScale;
|
||||
displacementAction *= displacementScale;
|
||||
|
||||
densityActionNorm = gravity_prepared_test_utils::global_norm(
|
||||
densityAction, communicator
|
||||
);
|
||||
|
||||
enthalpyActionNorm = gravity_prepared_test_utils::global_norm(
|
||||
enthalpyAction, communicator
|
||||
);
|
||||
|
||||
displacementActionNorm = gravity_prepared_test_utils::global_norm(
|
||||
displacementAction, communicator
|
||||
);
|
||||
|
||||
mean_field::operators::context::barotropic::
|
||||
BarotropicClosureLinearizationContext context(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
const std::uint64_t revisionBase =
|
||||
100 + static_cast<std::uint64_t>(10 * conditionIndex);
|
||||
|
||||
const mean_field::operators::context::barotropic::
|
||||
BarotropicClosureRevisions revisions{
|
||||
.density = revisionBase + 1,
|
||||
.enthalpy = revisionBase + 2,
|
||||
.displacement = revisionBase + 3
|
||||
};
|
||||
|
||||
context.Prepare(
|
||||
baseDensity, baseEnthalpy, baseDisplacement, revisions
|
||||
);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector referenceResidual;
|
||||
|
||||
context.BuildResidual(preparedResidual);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, baseDisplacement, referenceResidual
|
||||
);
|
||||
|
||||
const double residualEvaluationError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, communicator
|
||||
);
|
||||
|
||||
mfem::Vector preparedAction;
|
||||
|
||||
context.GetOperator().Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
preparedAction
|
||||
);
|
||||
|
||||
mfem::Vector blockSum(densityAction);
|
||||
|
||||
blockSum += enthalpyAction;
|
||||
blockSum += displacementAction;
|
||||
|
||||
const double blockAssemblyError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, blockSum, communicator
|
||||
);
|
||||
|
||||
mfem::Vector plusDensity(baseDensity);
|
||||
|
||||
mfem::Vector minusDensity(baseDensity);
|
||||
|
||||
mfem::Vector plusEnthalpy(baseEnthalpy);
|
||||
|
||||
mfem::Vector minusEnthalpy(baseEnthalpy);
|
||||
|
||||
mfem::Vector plusDisplacement(baseDisplacement);
|
||||
|
||||
mfem::Vector minusDisplacement(baseDisplacement);
|
||||
|
||||
plusDensity.Add(differenceStep, densityVariation);
|
||||
|
||||
minusDensity.Add(-differenceStep, densityVariation);
|
||||
|
||||
plusEnthalpy.Add(differenceStep, enthalpyVariation);
|
||||
|
||||
minusEnthalpy.Add(-differenceStep, enthalpyVariation);
|
||||
|
||||
plusDisplacement.Add(differenceStep, displacementVariation);
|
||||
|
||||
minusDisplacement.Add(-differenceStep, displacementVariation);
|
||||
|
||||
mfem::Vector plusResidual;
|
||||
mfem::Vector minusResidual;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, plusDensity,
|
||||
plusEnthalpy, plusDisplacement, plusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, minusDensity,
|
||||
minusEnthalpy, minusDisplacement, minusResidual
|
||||
);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
|
||||
finiteDifference -= minusResidual;
|
||||
|
||||
finiteDifference *= 1.0 / (2.0 * differenceStep);
|
||||
|
||||
mfem::Vector finiteDifferenceError(preparedAction);
|
||||
|
||||
finiteDifferenceError -= finiteDifference;
|
||||
|
||||
const double finiteDifferenceErrorNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
finiteDifferenceError, communicator
|
||||
);
|
||||
|
||||
const double blockNormSum =
|
||||
densityActionNorm + enthalpyActionNorm + displacementActionNorm;
|
||||
|
||||
const double blockScaledDifferenceError =
|
||||
finiteDifferenceErrorNorm / blockNormSum;
|
||||
|
||||
const double completeRelativeError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, finiteDifference, communicator
|
||||
);
|
||||
|
||||
INFO("Condition = " << condition.name);
|
||||
|
||||
INFO("Polytropic index = " << condition.polytropicIndex);
|
||||
|
||||
INFO("Deformation scale = " << condition.deformationScale);
|
||||
|
||||
INFO("Prepared residual error = " << residualEvaluationError);
|
||||
|
||||
INFO("Complete block-assembly error = " << blockAssemblyError);
|
||||
|
||||
INFO("Density-action norm = " << densityActionNorm);
|
||||
|
||||
INFO("Enthalpy-action norm = " << enthalpyActionNorm);
|
||||
|
||||
INFO("Displacement-action norm = " << displacementActionNorm);
|
||||
|
||||
INFO(
|
||||
"Complete centered-difference relative error = "
|
||||
<< completeRelativeError
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Block-scaled centered-difference error = "
|
||||
<< blockScaledDifferenceError
|
||||
);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
CHECK(residualEvaluationError < 5.0e-12);
|
||||
CHECK(blockAssemblyError < 5.0e-12);
|
||||
CHECK(blockScaledDifferenceError < 2.0e-7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Exact Constant Barotropic Closure Remains Zero Under Deformation",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::integration
|
||||
&tags::jacobian &tags::mapping &tags::physics
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
constexpr double enthalpyValue = 1.20;
|
||||
|
||||
const double equilibriumDensityValue =
|
||||
barotrope.density_from_enthalpy(enthalpyValue);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
make_constant_field(*f.enthalpyFes, enthalpyValue);
|
||||
|
||||
const mfem::Vector equilibriumDensity =
|
||||
make_constant_field(*f.densityFes, equilibriumDensityValue);
|
||||
|
||||
const mfem::Vector referenceDensity =
|
||||
make_constant_field(*f.densityFes, equilibriumDensityValue + 1.0);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.67);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
for (const double deformationScale : {0.0, 0.5, 1.0}) {
|
||||
DYNAMIC_SECTION("Deformation scale = " << deformationScale) {
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(
|
||||
f, deformationScale
|
||||
);
|
||||
|
||||
mfem::Vector exactResidual;
|
||||
mfem::Vector referenceResidual;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, equilibriumDensity,
|
||||
enthalpy, displacement, exactResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, referenceDensity,
|
||||
enthalpy, displacement, referenceResidual
|
||||
);
|
||||
|
||||
mfem::Vector exactGeometryAction;
|
||||
mfem::Vector referenceGeometryAction;
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, equilibriumDensity,
|
||||
enthalpy, displacement, displacementVariation,
|
||||
exactGeometryAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, referenceDensity,
|
||||
enthalpy, displacement, displacementVariation,
|
||||
referenceGeometryAction
|
||||
);
|
||||
|
||||
const double exactResidualNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
exactResidual, communicator
|
||||
);
|
||||
|
||||
const double referenceResidualNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
referenceResidual, communicator
|
||||
);
|
||||
|
||||
const double exactGeometryNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
exactGeometryAction, communicator
|
||||
);
|
||||
|
||||
const double referenceGeometryNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
referenceGeometryAction, communicator
|
||||
);
|
||||
|
||||
INFO("Deformation scale = " << deformationScale);
|
||||
|
||||
INFO("Exact-closure residual norm = " << exactResidualNorm);
|
||||
|
||||
INFO("Reference residual norm = " << referenceResidualNorm);
|
||||
|
||||
INFO("Exact-closure geometry-action norm = " << exactGeometryNorm);
|
||||
|
||||
INFO("Reference geometry-action norm = " << referenceGeometryNorm);
|
||||
|
||||
REQUIRE(referenceResidualNorm > 1.0e-12);
|
||||
REQUIRE(referenceGeometryNorm > 1.0e-14);
|
||||
|
||||
CHECK(exactResidualNorm <= 5.0e-12 * referenceResidualNorm);
|
||||
|
||||
CHECK(exactGeometryNorm <= 5.0e-12 * referenceGeometryNorm);
|
||||
}
|
||||
}
|
||||
}
|
||||
157
tests/operators/prepared_gravity_source.cpp
Normal file
157
tests/operators/prepared_gravity_source.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
namespace prepared_test = gravity_prepared_test_utils;
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Gravity Source Matches Stateless Kernel",
|
||||
tags::integration &tags::mfem_operators &tags::prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedGravitySourceOperator prepared_operator(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
REQUIRE(prepared_operator.Width() == f.densityFes->GetTrueVSize());
|
||||
REQUIRE(
|
||||
prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize()
|
||||
);
|
||||
|
||||
const mfem::Vector density = prepared_test::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.41
|
||||
);
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
mfem::Vector identity_action;
|
||||
mfem::Vector deformed_action;
|
||||
|
||||
for (const double deformation_scale : {0.0, 1.0}) {
|
||||
const mfem::Vector displacement =
|
||||
prepared_test::make_displacement(f, deformation_scale);
|
||||
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
mfem::Vector prepared_action;
|
||||
mfem::Vector reference_action;
|
||||
|
||||
prepared_operator.Mult(density, prepared_action);
|
||||
operators::kernels::apply_mapped_source(
|
||||
f, *f.domainMapperStateless, density, displacement, reference_action
|
||||
);
|
||||
|
||||
const double relative_error = prepared_test::relative_error(
|
||||
prepared_action, reference_action, communicator
|
||||
);
|
||||
|
||||
INFO("Deformation scale = " << deformation_scale);
|
||||
INFO(
|
||||
"Prepared source norm = "
|
||||
<< prepared_test::global_norm(prepared_action, communicator)
|
||||
);
|
||||
INFO(
|
||||
"Reference source norm = "
|
||||
<< prepared_test::global_norm(reference_action, communicator)
|
||||
);
|
||||
INFO("Relative prepared-source error = " << relative_error);
|
||||
|
||||
REQUIRE(prepared_operator.IsPrepared());
|
||||
CHECK_THAT(relative_error, WithinAbs(0.0, 2.0e-11));
|
||||
|
||||
if (deformation_scale == 0.0) {
|
||||
identity_action = prepared_action;
|
||||
} else {
|
||||
deformed_action = prepared_action;
|
||||
}
|
||||
}
|
||||
|
||||
const double geometry_change = prepared_test::relative_error(
|
||||
deformed_action, identity_action, communicator
|
||||
);
|
||||
|
||||
INFO("Relative source change under deformation = " << geometry_change);
|
||||
|
||||
CHECK(prepared_operator.GetPreparationCount() == 2);
|
||||
CHECK(geometry_change > 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Gravity Source Preserves Linearity And Excludes Vacuum",
|
||||
tags::integration &tags::gravity &tags::prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedGravitySourceOperator prepared_operator(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
REQUIRE(prepared_operator.Width() == f.densityFes->GetTrueVSize());
|
||||
REQUIRE(
|
||||
prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize()
|
||||
);
|
||||
const mfem::Vector displacement = prepared_test::make_displacement(f, 1.0);
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
const mfem::Vector first = prepared_test::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.27
|
||||
);
|
||||
const mfem::Vector second = prepared_test::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.79
|
||||
);
|
||||
const mfem::Vector combination =
|
||||
prepared_test::linear_combination(first, 1.3, second, -0.6);
|
||||
const mfem::Vector stellar_density =
|
||||
prepared_test::make_domain_supported_density(f, true);
|
||||
const mfem::Vector vacuum_density =
|
||||
prepared_test::make_domain_supported_density(f, false);
|
||||
|
||||
mfem::Vector first_action;
|
||||
mfem::Vector second_action;
|
||||
mfem::Vector combination_action;
|
||||
mfem::Vector stellar_action;
|
||||
mfem::Vector vacuum_action;
|
||||
|
||||
prepared_operator.Mult(first, first_action);
|
||||
prepared_operator.Mult(second, second_action);
|
||||
prepared_operator.Mult(combination, combination_action);
|
||||
prepared_operator.Mult(stellar_density, stellar_action);
|
||||
prepared_operator.Mult(vacuum_density, vacuum_action);
|
||||
|
||||
const mfem::Vector expected_combination = prepared_test::linear_combination(
|
||||
first_action, 1.3, second_action, -0.6
|
||||
);
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double linearity_error = prepared_test::relative_error(
|
||||
combination_action, expected_combination, communicator
|
||||
);
|
||||
const double stellar_norm =
|
||||
prepared_test::global_norm(stellar_action, communicator);
|
||||
const double vacuum_norm =
|
||||
prepared_test::global_norm(vacuum_action, communicator);
|
||||
const std::uint64_t preparation_count =
|
||||
prepared_operator.GetPreparationCount();
|
||||
|
||||
mfem::Vector repeated_action;
|
||||
prepared_operator.Mult(first, repeated_action);
|
||||
|
||||
INFO("Relative source linearity error = " << linearity_error);
|
||||
INFO("Stellar source norm = " << stellar_norm);
|
||||
INFO("Vacuum-only source norm = " << vacuum_norm);
|
||||
|
||||
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
|
||||
CHECK(stellar_norm > 0.0);
|
||||
CHECK(vacuum_norm <= 1.0e-13 * stellar_norm);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
repeated_action, first_action, communicator
|
||||
) < 2.0e-14
|
||||
);
|
||||
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
|
||||
}
|
||||
164
tests/operators/prepared_hdiv_mass.cpp
Normal file
164
tests/operators/prepared_hdiv_mass.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
namespace prepared_test = gravity_prepared_test_utils;
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Hdiv Mass Matches Stateless Kernel",
|
||||
tags::integration &tags::mfem_operators &tags::prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedHDivMassOperator prepared_operator(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
|
||||
const mfem::Vector gravity_gradient =
|
||||
prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.21
|
||||
);
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
|
||||
mfem::Vector identity_action;
|
||||
mfem::Vector deformed_action;
|
||||
|
||||
for (const double deformation_scale : {0.0, 1.0}) {
|
||||
const mfem::Vector displacement =
|
||||
prepared_test::make_displacement(f, deformation_scale);
|
||||
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
mfem::Vector prepared_action;
|
||||
mfem::Vector reference_action;
|
||||
|
||||
prepared_operator.Mult(gravity_gradient, prepared_action);
|
||||
operators::kernels::apply_mapped_hdiv_mass(
|
||||
f, *f.domainMapperStateless, gravity_gradient, displacement,
|
||||
reference_action
|
||||
);
|
||||
|
||||
const double relative_error = prepared_test::relative_error(
|
||||
prepared_action, reference_action, communicator
|
||||
);
|
||||
|
||||
INFO("Deformation scale = " << deformation_scale);
|
||||
INFO(
|
||||
"Prepared action norm = "
|
||||
<< prepared_test::global_norm(prepared_action, communicator)
|
||||
);
|
||||
INFO(
|
||||
"Reference action norm = "
|
||||
<< prepared_test::global_norm(reference_action, communicator)
|
||||
);
|
||||
INFO("Relative prepared-operator error = " << relative_error);
|
||||
|
||||
REQUIRE(prepared_operator.IsPrepared());
|
||||
CHECK_THAT(relative_error, WithinAbs(0.0, 2.0e-11));
|
||||
|
||||
if (deformation_scale == 0.0) {
|
||||
identity_action = prepared_action;
|
||||
} else {
|
||||
deformed_action = prepared_action;
|
||||
}
|
||||
}
|
||||
|
||||
const double geometry_change = prepared_test::relative_error(
|
||||
deformed_action, identity_action, communicator
|
||||
);
|
||||
|
||||
INFO("Relative action change under deformation = " << geometry_change);
|
||||
|
||||
CHECK(prepared_operator.GetPreparationCount() == 2);
|
||||
CHECK(geometry_change > 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Hdiv Mass Preserves Operator Identities",
|
||||
tags::integration &tags::gravity &tags::prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
operators::PreparedMappedHDivMassOperator prepared_operator(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
const mfem::Vector displacement = prepared_test::make_displacement(f, 1.0);
|
||||
prepared_operator.Prepare(displacement);
|
||||
|
||||
const mfem::Vector first = prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.17
|
||||
);
|
||||
const mfem::Vector second = prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.83
|
||||
);
|
||||
const mfem::Vector combination =
|
||||
prepared_test::linear_combination(first, 1.7, second, -0.4);
|
||||
|
||||
mfem::Vector first_action;
|
||||
mfem::Vector second_action;
|
||||
mfem::Vector combination_action;
|
||||
mfem::Vector zero_action;
|
||||
|
||||
prepared_operator.Mult(first, first_action);
|
||||
prepared_operator.Mult(second, second_action);
|
||||
prepared_operator.Mult(combination, combination_action);
|
||||
|
||||
mfem::Vector expected_combination = prepared_test::linear_combination(
|
||||
first_action, 1.7, second_action, -0.4
|
||||
);
|
||||
|
||||
mfem::Vector zero(first.Size());
|
||||
zero = 0.0;
|
||||
prepared_operator.Mult(zero, zero_action);
|
||||
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
|
||||
const double first_second_product =
|
||||
prepared_test::global_dot(first, second_action, communicator);
|
||||
const double second_first_product =
|
||||
prepared_test::global_dot(second, first_action, communicator);
|
||||
const double symmetry_error = prepared_test::relative_scalar_error(
|
||||
first_second_product, second_first_product
|
||||
);
|
||||
const double linearity_error = prepared_test::relative_error(
|
||||
combination_action, expected_combination, communicator
|
||||
);
|
||||
const double first_energy =
|
||||
prepared_test::global_dot(first, first_action, communicator);
|
||||
const double second_energy =
|
||||
prepared_test::global_dot(second, second_action, communicator);
|
||||
const std::uint64_t preparation_count =
|
||||
prepared_operator.GetPreparationCount();
|
||||
|
||||
mfem::Vector repeated_action;
|
||||
prepared_operator.Mult(first, repeated_action);
|
||||
|
||||
INFO("u^T M v = " << first_second_product);
|
||||
INFO("v^T M u = " << second_first_product);
|
||||
INFO("Relative symmetry error = " << symmetry_error);
|
||||
INFO("Relative linearity error = " << linearity_error);
|
||||
INFO("u^T M u = " << first_energy);
|
||||
INFO("v^T M v = " << second_energy);
|
||||
|
||||
CHECK_THAT(symmetry_error, WithinAbs(0.0, 2.0e-12));
|
||||
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
|
||||
CHECK_THAT(
|
||||
prepared_test::global_norm(zero_action, communicator),
|
||||
WithinAbs(0.0, 1.0e-14)
|
||||
);
|
||||
CHECK(first_energy > 0.0);
|
||||
CHECK(second_energy > 0.0);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
repeated_action, first_action, communicator
|
||||
) < 2.0e-14
|
||||
);
|
||||
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
|
||||
}
|
||||
334
tests/operators/prepared_hydrostatic_equilibrium.cpp
Normal file
334
tests/operators/prepared_hydrostatic_equilibrium.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace prepared_hydrostatic_test_utils {
|
||||
static mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies
|
||||
make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 211, .revision = 2},
|
||||
.enthalpy = {.identity = 223, .revision = 3},
|
||||
.gravityPotential = {.identity = 227, .revision = 5},
|
||||
.displacement = {.identity = 229, .revision = 7},
|
||||
.rotation = {.identity = 233, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 239, .revision = 13}
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
|
||||
make_state(
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement,
|
||||
const double bernoulliConstant
|
||||
) {
|
||||
return {
|
||||
.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant
|
||||
};
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double phase = 0.19
|
||||
) {
|
||||
return gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), phase
|
||||
);
|
||||
}
|
||||
|
||||
mfem::Vector make_gravity_potential(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double phase = 0.37
|
||||
) {
|
||||
return gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), phase
|
||||
);
|
||||
}
|
||||
|
||||
mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
|
||||
mfem::Vector angularVelocity(3);
|
||||
|
||||
angularVelocity(0) = 0.17 * scale;
|
||||
angularVelocity(1) = -0.11 * scale;
|
||||
angularVelocity(2) = 0.43 * scale;
|
||||
|
||||
mfem::Vector center(3);
|
||||
|
||||
center(0) = 0.037;
|
||||
center(1) = -0.029;
|
||||
center(2) = 0.021;
|
||||
|
||||
return mean_field::physics::RigidRotation(angularVelocity, center);
|
||||
}
|
||||
} // namespace prepared_hydrostatic_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Residual Matches Stateless Kernel",
|
||||
tags::barotrope &tags::hydro &tags::prepared &tags::residuals &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
prepared_hydrostatic_test_utils::make_enthalpy(f);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
prepared_hydrostatic_test_utils::make_gravity_potential(f);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.73);
|
||||
|
||||
constexpr double bernoulliConstant = 0.41;
|
||||
|
||||
const mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_test_utils::make_rotation();
|
||||
|
||||
const auto dependencies =
|
||||
prepared_hydrostatic_test_utils::make_dependencies();
|
||||
|
||||
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||
CHECK(preparedOperator.GetResidualPreparationCount() == 0);
|
||||
CHECK(preparedOperator.GetResidualApplicationCount() == 0);
|
||||
|
||||
const auto report = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector referenceResidual;
|
||||
|
||||
preparedOperator.BuildResidual(preparedResidual);
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential,
|
||||
displacement, bernoulliConstant, referenceResidual
|
||||
);
|
||||
|
||||
const double relativeError = gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Prepared hydrostatic residual relative error = " << relativeError);
|
||||
|
||||
const auto &statistics = preparedOperator.GetContextPreparationStatistics();
|
||||
|
||||
CHECK(preparedOperator.IsPrepared());
|
||||
CHECK(report.contextReport.preparedStaticDependencies);
|
||||
CHECK(report.contextReport.preparedGeometryState);
|
||||
CHECK(report.contextReport.preparedRotationDependencies);
|
||||
CHECK(report.contextReport.preparedBaseState);
|
||||
CHECK(report.updatedRotation);
|
||||
CHECK(report.preparedResidual);
|
||||
CHECK(report.DidAnyWork());
|
||||
|
||||
CHECK(preparedOperator.GetStellarElementCount() > 0);
|
||||
CHECK(statistics.staticPreparations == 1);
|
||||
CHECK(statistics.geometryPreparations == 1);
|
||||
CHECK(statistics.rotationPreparations == 1);
|
||||
CHECK(statistics.baseStatePreparations == 1);
|
||||
CHECK(preparedOperator.GetResidualPreparationCount() == 1);
|
||||
CHECK(preparedOperator.GetResidualApplicationCount() == 1);
|
||||
|
||||
CHECK(relativeError < 2.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Residual Reuses And Selectively Rebuilds Data",
|
||||
tags::barotrope &tags::hydro &tags::prepared &tags::residuals &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f);
|
||||
|
||||
mfem::Vector gravityPotential =
|
||||
prepared_hydrostatic_test_utils::make_gravity_potential(f);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.42);
|
||||
|
||||
double bernoulliConstant = 0.36;
|
||||
|
||||
const mfem::Vector initialEnthalpy(enthalpy);
|
||||
const mfem::Vector initialGravityPotential(gravityPotential);
|
||||
const mfem::Vector initialDisplacement(displacement);
|
||||
const double initialBernoulliConstant = bernoulliConstant;
|
||||
|
||||
const mean_field::physics::RigidRotation initialRotation =
|
||||
prepared_hydrostatic_test_utils::make_rotation(0.80);
|
||||
|
||||
const mean_field::physics::RigidRotation changedRotation =
|
||||
prepared_hydrostatic_test_utils::make_rotation(1.25);
|
||||
|
||||
auto dependencies = prepared_hydrostatic_test_utils::make_dependencies();
|
||||
|
||||
preparedOperator.Prepare(
|
||||
prepared_hydrostatic_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, initialRotation
|
||||
);
|
||||
|
||||
mfem::Vector initialResidual;
|
||||
preparedOperator.BuildResidual(initialResidual);
|
||||
|
||||
enthalpy(0) += 0.29;
|
||||
gravityPotential(0) -= 0.17;
|
||||
displacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
|
||||
bernoulliConstant += 0.23;
|
||||
|
||||
const auto unchangedReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, changedRotation
|
||||
);
|
||||
|
||||
mfem::Vector unchangedResidual;
|
||||
preparedOperator.BuildResidual(unchangedResidual);
|
||||
|
||||
CHECK_FALSE(unchangedReport.DidAnyWork());
|
||||
CHECK_FALSE(unchangedReport.updatedRotation);
|
||||
CHECK_FALSE(unchangedReport.preparedResidual);
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
unchangedResidual, initialResidual, f.mesh->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(preparedOperator.GetResidualPreparationCount() == 1);
|
||||
|
||||
// Only the enthalpy stamp changes. The altered potential,
|
||||
// displacement, constant, and rotation remain intentionally frozen.
|
||||
++dependencies.enthalpy.revision;
|
||||
|
||||
const auto enthalpyReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, changedRotation
|
||||
);
|
||||
|
||||
mfem::Vector enthalpyResidual;
|
||||
mfem::Vector enthalpyReference;
|
||||
|
||||
preparedOperator.BuildResidual(enthalpyResidual);
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, initialRotation, enthalpy,
|
||||
initialGravityPotential, initialDisplacement, initialBernoulliConstant,
|
||||
enthalpyReference
|
||||
);
|
||||
|
||||
CHECK_FALSE(enthalpyReport.contextReport.preparedStaticDependencies);
|
||||
CHECK_FALSE(enthalpyReport.contextReport.preparedGeometryState);
|
||||
CHECK_FALSE(enthalpyReport.contextReport.preparedRotationDependencies);
|
||||
CHECK(enthalpyReport.contextReport.preparedBaseState);
|
||||
CHECK_FALSE(enthalpyReport.updatedRotation);
|
||||
CHECK(enthalpyReport.preparedResidual);
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
enthalpyResidual, enthalpyReference, f.mesh->GetComm()
|
||||
) < 2.0e-12
|
||||
);
|
||||
|
||||
++dependencies.rotation.revision;
|
||||
|
||||
const auto rotationReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, changedRotation
|
||||
);
|
||||
|
||||
mfem::Vector rotationResidual;
|
||||
mfem::Vector rotationReference;
|
||||
|
||||
preparedOperator.BuildResidual(rotationResidual);
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, changedRotation, enthalpy,
|
||||
initialGravityPotential, initialDisplacement, initialBernoulliConstant,
|
||||
rotationReference
|
||||
);
|
||||
|
||||
CHECK_FALSE(rotationReport.contextReport.preparedStaticDependencies);
|
||||
CHECK_FALSE(rotationReport.contextReport.preparedGeometryState);
|
||||
CHECK(rotationReport.contextReport.preparedRotationDependencies);
|
||||
CHECK(rotationReport.contextReport.preparedBaseState);
|
||||
CHECK(rotationReport.updatedRotation);
|
||||
CHECK(rotationReport.preparedResidual);
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
rotationResidual, rotationReference, f.mesh->GetComm()
|
||||
) < 2.0e-12
|
||||
);
|
||||
|
||||
++dependencies.displacement.revision;
|
||||
|
||||
const auto displacementReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, changedRotation
|
||||
);
|
||||
|
||||
mfem::Vector displacementResidual;
|
||||
mfem::Vector displacementReference;
|
||||
|
||||
preparedOperator.BuildResidual(displacementResidual);
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, changedRotation, enthalpy,
|
||||
initialGravityPotential, displacement, initialBernoulliConstant,
|
||||
displacementReference
|
||||
);
|
||||
|
||||
CHECK_FALSE(displacementReport.contextReport.preparedStaticDependencies);
|
||||
CHECK(displacementReport.contextReport.preparedGeometryState);
|
||||
CHECK(displacementReport.contextReport.preparedRotationDependencies);
|
||||
CHECK(displacementReport.contextReport.preparedBaseState);
|
||||
CHECK_FALSE(displacementReport.updatedRotation);
|
||||
CHECK(displacementReport.preparedResidual);
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
displacementResidual, displacementReference, f.mesh->GetComm()
|
||||
) < 2.0e-12
|
||||
);
|
||||
|
||||
const auto &statistics = preparedOperator.GetContextPreparationStatistics();
|
||||
|
||||
CHECK(statistics.staticPreparations == 1);
|
||||
CHECK(statistics.geometryPreparations == 2);
|
||||
CHECK(statistics.rotationPreparations == 3);
|
||||
CHECK(statistics.baseStatePreparations == 4);
|
||||
CHECK(preparedOperator.GetResidualPreparationCount() == 4);
|
||||
CHECK(preparedOperator.GetResidualApplicationCount() == 5);
|
||||
|
||||
const double displacementEffect =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
displacementResidual, rotationResidual, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Residual change after displacement update = " << displacementEffect);
|
||||
|
||||
CHECK(displacementEffect > 1.0e-8);
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace prepared_hydrostatic_analytic_solve_test_utils {
|
||||
constexpr double bernoulliConstant = 0.83;
|
||||
constexpr double enthalpyAmplitude = 0.61;
|
||||
|
||||
struct AnalyticCase {
|
||||
const char *name;
|
||||
|
||||
std::array<double, 3> deformationScale;
|
||||
std::array<double, 3> angularVelocity;
|
||||
std::array<double, 3> rotationCenter;
|
||||
};
|
||||
|
||||
class EnthalpyJacobianOperator final : public mfem::Operator {
|
||||
public:
|
||||
EnthalpyJacobianOperator(
|
||||
const int enthalpySize,
|
||||
const mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
&preparedOperator
|
||||
)
|
||||
: mfem::Operator(enthalpySize),
|
||||
m_preparedOperator(preparedOperator) {
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
m_preparedOperator.ApplyEnthalpyJacobianAction(direction, action);
|
||||
}
|
||||
|
||||
private:
|
||||
const mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
&m_preparedOperator;
|
||||
};
|
||||
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies
|
||||
make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 701, .revision = 2},
|
||||
.enthalpy = {.identity = 709, .revision = 3},
|
||||
.gravityPotential = {.identity = 719, .revision = 5},
|
||||
.displacement = {.identity = 727, .revision = 7},
|
||||
.rotation = {.identity = 733, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 739, .revision = 13}
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
|
||||
make_state(
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement
|
||||
) {
|
||||
return {
|
||||
.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant
|
||||
};
|
||||
}
|
||||
|
||||
mfem::Vector make_vector(
|
||||
const std::array<
|
||||
double,
|
||||
3> &values
|
||||
) {
|
||||
mfem::Vector vector(3);
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
vector(component) = values[static_cast<std::size_t>(component)];
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
mean_field::physics::RigidRotation
|
||||
make_rotation(const AnalyticCase &analyticCase) {
|
||||
return mean_field::physics::RigidRotation(
|
||||
make_vector(analyticCase.angularVelocity),
|
||||
make_vector(analyticCase.rotationCenter)
|
||||
);
|
||||
}
|
||||
|
||||
void map_to_physical(
|
||||
const mfem::Vector &referencePosition,
|
||||
const AnalyticCase &analyticCase,
|
||||
mfem::Vector &physicalPosition
|
||||
) {
|
||||
physicalPosition.SetSize(3);
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
physicalPosition(component) =
|
||||
analyticCase
|
||||
.deformationScale[static_cast<std::size_t>(component)] *
|
||||
referencePosition(component);
|
||||
}
|
||||
}
|
||||
|
||||
double exact_enthalpy_value(const mfem::Vector &referencePosition) {
|
||||
double normalizedRadiusSquared = 0.0;
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const double normalizedCoordinate =
|
||||
referencePosition(component) / mean_field::utils::RADIUS;
|
||||
|
||||
normalizedRadiusSquared +=
|
||||
normalizedCoordinate * normalizedCoordinate;
|
||||
}
|
||||
|
||||
return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared);
|
||||
}
|
||||
|
||||
double exact_potential_value(
|
||||
const mfem::Vector &referencePosition,
|
||||
const AnalyticCase &analyticCase,
|
||||
const mean_field::physics::RigidRotation &rotation
|
||||
) {
|
||||
mfem::Vector physicalPosition;
|
||||
|
||||
map_to_physical(referencePosition, analyticCase, physicalPosition);
|
||||
|
||||
/*
|
||||
* Construct Phi so that
|
||||
*
|
||||
* h + Phi - Psi_rotation - C = 0
|
||||
*
|
||||
* analytically.
|
||||
*/
|
||||
return bernoulliConstant + rotation.potential(physicalPosition) -
|
||||
exact_enthalpy_value(referencePosition);
|
||||
}
|
||||
|
||||
mfem::Array<int>
|
||||
make_stellar_element_marker(const mean_field::fem::FEM &f) {
|
||||
mfem::Array<int> stellarElementMarker(f.mesh->GetNE());
|
||||
|
||||
const int vacuumAttribute =
|
||||
f.domainMapperStateless->GetVacuumElementAttribute();
|
||||
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
stellarElementMarker[elementId] =
|
||||
f.mesh->GetAttribute(elementId) != vacuumAttribute;
|
||||
}
|
||||
|
||||
return stellarElementMarker;
|
||||
}
|
||||
} // namespace prepared_hydrostatic_analytic_solve_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
|
||||
tags::barotrope &tags::hydro &tags::prepared &tags::integration
|
||||
&tags::solver &tags::convergence &tags::accuracy
|
||||
&tags::analytic_comparison
|
||||
) {
|
||||
using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase;
|
||||
|
||||
constexpr double deformationX = 1.08;
|
||||
constexpr double deformationY = 0.96;
|
||||
|
||||
/*
|
||||
* The third scale makes the affine deformation
|
||||
* volume-preserving:
|
||||
*
|
||||
* det(F) = sx * sy * sz = 1.
|
||||
*/
|
||||
constexpr double deformationZ = 1.0 / (deformationX * deformationY);
|
||||
|
||||
const std::array<AnalyticCase, 3> analyticCases{
|
||||
{{.name = "spherical nonrotating equilibrium",
|
||||
.deformationScale = {1.0, 1.0, 1.0},
|
||||
.angularVelocity = {0.0, 0.0, 0.0},
|
||||
.rotationCenter = {0.0, 0.0, 0.0}},
|
||||
{.name = "spherical rotating equilibrium",
|
||||
.deformationScale = {1.0, 1.0, 1.0},
|
||||
.angularVelocity = {0.13, -0.09, 0.31},
|
||||
.rotationCenter = {0.04, -0.03, 0.02}},
|
||||
{.name = "volume-preserving deformed rotating equilibrium",
|
||||
.deformationScale = {deformationX, deformationY, deformationZ},
|
||||
.angularVelocity = {0.17, -0.12, 0.43},
|
||||
.rotationCenter = {0.031, -0.024, 0.018}}}
|
||||
};
|
||||
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const mfem::Array<int> stellarElementMarker =
|
||||
prepared_hydrostatic_analytic_solve_test_utils::
|
||||
make_stellar_element_marker(f);
|
||||
|
||||
for (const AnalyticCase &analyticCase : analyticCases) {
|
||||
DYNAMIC_SECTION(analyticCase.name) {
|
||||
const double deformationDeterminant =
|
||||
analyticCase.deformationScale[0] *
|
||||
analyticCase.deformationScale[1] *
|
||||
analyticCase.deformationScale[2];
|
||||
|
||||
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
|
||||
|
||||
const mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(
|
||||
analyticCase
|
||||
);
|
||||
|
||||
auto displacementFunction = [&analyticCase](
|
||||
const mfem::Vector
|
||||
&referencePosition,
|
||||
mfem::Vector &displacementValue
|
||||
) {
|
||||
mfem::Vector physicalPosition;
|
||||
|
||||
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
|
||||
referencePosition, analyticCase, physicalPosition
|
||||
);
|
||||
|
||||
displacementValue.SetSize(3);
|
||||
displacementValue = physicalPosition;
|
||||
displacementValue -= referencePosition;
|
||||
};
|
||||
|
||||
auto potentialFunction = [&analyticCase, &rotation](
|
||||
const mfem::Vector &referencePosition
|
||||
) {
|
||||
return prepared_hydrostatic_analytic_solve_test_utils::
|
||||
exact_potential_value(
|
||||
referencePosition, analyticCase, rotation
|
||||
);
|
||||
};
|
||||
|
||||
auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
|
||||
return prepared_hydrostatic_analytic_solve_test_utils::
|
||||
exact_enthalpy_value(referencePosition);
|
||||
};
|
||||
|
||||
mfem::VectorFunctionCoefficient displacementCoefficient(
|
||||
f.mesh->Dimension(), displacementFunction
|
||||
);
|
||||
|
||||
mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
|
||||
|
||||
mfem::FunctionCoefficient exactEnthalpyCoefficient(
|
||||
enthalpyFunction
|
||||
);
|
||||
|
||||
/*
|
||||
* Project the prescribed geometry and potential.
|
||||
*/
|
||||
mfem::ParGridFunction displacementField(f.displacementFes.get());
|
||||
|
||||
mfem::ParGridFunction potentialField(f.gravityPotentialFes.get());
|
||||
|
||||
displacementField.ProjectCoefficient(displacementCoefficient);
|
||||
|
||||
potentialField.ProjectCoefficient(potentialCoefficient);
|
||||
|
||||
mfem::Vector displacement;
|
||||
mfem::Vector gravityPotential;
|
||||
|
||||
displacementField.GetTrueDofs(displacement);
|
||||
potentialField.GetTrueDofs(gravityPotential);
|
||||
|
||||
/*
|
||||
* This projection is not used as the solution. It gives
|
||||
* the best directly available representation baseline
|
||||
* against which the solved field can be compared.
|
||||
*/
|
||||
mfem::ParGridFunction projectedEnthalpyField(f.enthalpyFes.get());
|
||||
|
||||
projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient);
|
||||
|
||||
mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get());
|
||||
|
||||
zeroEnthalpyField = 0.0;
|
||||
|
||||
const double exactEnthalpyNorm = zeroEnthalpyField.ComputeL2Error(
|
||||
exactEnthalpyCoefficient, nullptr, &stellarElementMarker
|
||||
);
|
||||
|
||||
const double projectionError =
|
||||
projectedEnthalpyField.ComputeL2Error(
|
||||
exactEnthalpyCoefficient, nullptr, &stellarElementMarker
|
||||
);
|
||||
|
||||
REQUIRE(exactEnthalpyNorm > 0.0);
|
||||
|
||||
const double relativeProjectionError =
|
||||
projectionError / exactEnthalpyNorm;
|
||||
|
||||
/*
|
||||
* Begin deliberately far from equilibrium.
|
||||
*/
|
||||
mfem::Vector enthalpy(f.enthalpyFes->GetTrueVSize());
|
||||
|
||||
enthalpy = 0.0;
|
||||
|
||||
auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::
|
||||
make_dependencies();
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const auto initialReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
REQUIRE(initialReport.preparedResidual);
|
||||
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
|
||||
|
||||
mfem::Vector initialResidual;
|
||||
|
||||
preparedOperator.BuildResidual(initialResidual);
|
||||
|
||||
const double initialResidualNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
initialResidual, communicator
|
||||
);
|
||||
|
||||
REQUIRE(initialResidualNorm > 1.0e-12);
|
||||
|
||||
/*
|
||||
* One discrete Newton step:
|
||||
*
|
||||
* M_h delta_h = -R_h.
|
||||
*
|
||||
* The full four-block Bernoulli Jacobian is rectangular
|
||||
* and underdetermined in isolation. Freezing Phi, C,
|
||||
* rotation, and displacement makes this a well-defined
|
||||
* enthalpy solve.
|
||||
*/
|
||||
prepared_hydrostatic_analytic_solve_test_utils::
|
||||
EnthalpyJacobianOperator enthalpyJacobian(
|
||||
f.enthalpyFes->GetTrueVSize(), preparedOperator
|
||||
);
|
||||
|
||||
mfem::Vector rightHandSide(initialResidual);
|
||||
rightHandSide *= -1.0;
|
||||
|
||||
mfem::Vector enthalpyCorrection(f.enthalpyFes->GetTrueVSize());
|
||||
|
||||
enthalpyCorrection = 0.0;
|
||||
|
||||
/*
|
||||
* The operator is positive definite on stellar-supported
|
||||
* enthalpy DOFs and semidefinite on exterior-only DOFs.
|
||||
* The RHS is in its range, so MINRES is appropriate for
|
||||
* the compatible system.
|
||||
*/
|
||||
mfem::MINRESSolver linearSolver(communicator);
|
||||
|
||||
linearSolver.SetOperator(enthalpyJacobian);
|
||||
|
||||
linearSolver.SetRelTol(1.0e-13);
|
||||
linearSolver.SetAbsTol(1.0e-14);
|
||||
linearSolver.SetMaxIter(2000);
|
||||
linearSolver.SetPrintLevel(0);
|
||||
|
||||
linearSolver.Mult(rightHandSide, enthalpyCorrection);
|
||||
|
||||
INFO("Linear solver converged = " << linearSolver.GetConverged());
|
||||
|
||||
INFO(
|
||||
"Linear solver iterations = " << linearSolver.GetNumIterations()
|
||||
);
|
||||
|
||||
INFO("Linear solver final norm = " << linearSolver.GetFinalNorm());
|
||||
|
||||
REQUIRE(linearSolver.GetConverged());
|
||||
|
||||
enthalpy += enthalpyCorrection;
|
||||
|
||||
/*
|
||||
* Only the enthalpy state changed. Geometry, rotation,
|
||||
* and algebraic Jacobian data must remain reusable.
|
||||
*/
|
||||
++dependencies.enthalpy.revision;
|
||||
|
||||
const auto solvedReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_analytic_solve_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
CHECK(solvedReport.contextReport.updatedEnthalpy);
|
||||
|
||||
CHECK(solvedReport.contextReport.preparedBaseState);
|
||||
|
||||
CHECK_FALSE(solvedReport.contextReport.preparedGeometryState);
|
||||
|
||||
CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks);
|
||||
|
||||
mfem::Vector solvedResidual;
|
||||
|
||||
preparedOperator.BuildResidual(solvedResidual);
|
||||
|
||||
const double solvedResidualNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
solvedResidual, communicator
|
||||
);
|
||||
|
||||
const double residualReduction =
|
||||
solvedResidualNorm / initialResidualNorm;
|
||||
|
||||
/*
|
||||
* Compare the solved field with the continuum analytic
|
||||
* enthalpy over stellar elements only.
|
||||
*
|
||||
* All three mappings have determinant one, so this
|
||||
* normalized L2 error is also unchanged by the physical
|
||||
* volume transformation.
|
||||
*/
|
||||
mfem::ParGridFunction solvedEnthalpyField(f.enthalpyFes.get());
|
||||
|
||||
solvedEnthalpyField.SetFromTrueDofs(enthalpy);
|
||||
|
||||
const double solvedAnalyticError =
|
||||
solvedEnthalpyField.ComputeL2Error(
|
||||
exactEnthalpyCoefficient, nullptr, &stellarElementMarker
|
||||
);
|
||||
|
||||
const double relativeSolvedAnalyticError =
|
||||
solvedAnalyticError / exactEnthalpyNorm;
|
||||
|
||||
INFO("Deformation determinant = " << deformationDeterminant);
|
||||
|
||||
INFO("Initial weak residual norm = " << initialResidualNorm);
|
||||
|
||||
INFO("Solved weak residual norm = " << solvedResidualNorm);
|
||||
|
||||
INFO("Weak residual reduction = " << residualReduction);
|
||||
|
||||
INFO(
|
||||
"Relative analytic projection floor = "
|
||||
<< relativeProjectionError
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Relative solved analytic L2 error = "
|
||||
<< relativeSolvedAnalyticError
|
||||
);
|
||||
|
||||
/*
|
||||
* The discrete Bernoulli equation must be solved essentially
|
||||
* to the linear-solver floor.
|
||||
*/
|
||||
CHECK(residualReduction < 1.0e-10);
|
||||
|
||||
/*
|
||||
* The directly projected analytic enthalpy provides a lower
|
||||
* representation bound, but it is not the expected solution
|
||||
* of the cross-space discrete Bernoulli equation. The latter
|
||||
* also contains potential-projection and mapped-space
|
||||
* compatibility errors.
|
||||
*/
|
||||
CHECK(
|
||||
relativeSolvedAnalyticError <
|
||||
std::max(5.0 * relativeProjectionError, 1.25e-4)
|
||||
);
|
||||
|
||||
/*
|
||||
* Record that the analytic error remains within one order of
|
||||
* magnitude of the direct enthalpy projection floor.
|
||||
*/
|
||||
CHECK(relativeSolvedAnalyticError / relativeProjectionError < 5.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace prepared_hydrostatic_complete_test_utils {
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies
|
||||
make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 503, .revision = 2},
|
||||
.enthalpy = {.identity = 509, .revision = 3},
|
||||
.gravityPotential = {.identity = 521, .revision = 5},
|
||||
.displacement = {.identity = 523, .revision = 7},
|
||||
.rotation = {.identity = 541, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 547, .revision = 13}
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
|
||||
make_state(
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement,
|
||||
const double bernoulliConstant
|
||||
) {
|
||||
return {
|
||||
.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::physics::RigidRotation make_rotation() {
|
||||
mfem::Vector angularVelocity(3);
|
||||
|
||||
angularVelocity(0) = 0.18;
|
||||
angularVelocity(1) = -0.13;
|
||||
angularVelocity(2) = 0.49;
|
||||
|
||||
mfem::Vector center(3);
|
||||
|
||||
center(0) = 0.031;
|
||||
center(1) = -0.024;
|
||||
center(2) = 0.017;
|
||||
|
||||
return mean_field::physics::RigidRotation(angularVelocity, center);
|
||||
}
|
||||
|
||||
mfem::Vector make_displacement_direction(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double firstPhase,
|
||||
const double secondPhase
|
||||
) {
|
||||
mfem::Vector direction =
|
||||
gravity_prepared_test_utils::make_displacement(f, firstPhase);
|
||||
|
||||
const mfem::Vector secondField =
|
||||
gravity_prepared_test_utils::make_displacement(f, secondPhase);
|
||||
|
||||
direction -= secondField;
|
||||
return direction;
|
||||
}
|
||||
|
||||
void centered_complete_difference(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::RigidRotation &rotation,
|
||||
const mfem::Vector &baseEnthalpy,
|
||||
const mfem::Vector &baseGravityPotential,
|
||||
const mfem::Vector &baseDisplacement,
|
||||
const double baseBernoulliConstant,
|
||||
const mfem::Vector &enthalpyVariation,
|
||||
const mfem::Vector &gravityPotentialVariation,
|
||||
const mfem::Vector &displacementVariation,
|
||||
const double bernoulliConstantVariation,
|
||||
const double step,
|
||||
mfem::Vector &difference
|
||||
) {
|
||||
mfem::Vector enthalpyPlus(baseEnthalpy);
|
||||
mfem::Vector enthalpyMinus(baseEnthalpy);
|
||||
mfem::Vector gravityPotentialPlus(baseGravityPotential);
|
||||
mfem::Vector gravityPotentialMinus(baseGravityPotential);
|
||||
mfem::Vector displacementPlus(baseDisplacement);
|
||||
mfem::Vector displacementMinus(baseDisplacement);
|
||||
|
||||
enthalpyPlus.Add(step, enthalpyVariation);
|
||||
enthalpyMinus.Add(-step, enthalpyVariation);
|
||||
|
||||
gravityPotentialPlus.Add(step, gravityPotentialVariation);
|
||||
|
||||
gravityPotentialMinus.Add(-step, gravityPotentialVariation);
|
||||
|
||||
displacementPlus.Add(step, displacementVariation);
|
||||
displacementMinus.Add(-step, displacementVariation);
|
||||
|
||||
const double bernoulliConstantPlus =
|
||||
baseBernoulliConstant + step * bernoulliConstantVariation;
|
||||
|
||||
const double bernoulliConstantMinus =
|
||||
baseBernoulliConstant - step * bernoulliConstantVariation;
|
||||
|
||||
mfem::Vector residualPlus;
|
||||
mfem::Vector residualMinus;
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpyPlus,
|
||||
gravityPotentialPlus, displacementPlus, bernoulliConstantPlus,
|
||||
residualPlus
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpyMinus,
|
||||
gravityPotentialMinus, displacementMinus, bernoulliConstantMinus,
|
||||
residualMinus
|
||||
);
|
||||
|
||||
difference = residualPlus;
|
||||
difference -= residualMinus;
|
||||
difference /= 2.0 * step;
|
||||
}
|
||||
|
||||
void set_block(
|
||||
mfem::Vector &packedDirection,
|
||||
const mean_field::operators::HydrostaticJacobianBlockLayout &layout,
|
||||
const mean_field::operators::HydrostaticJacobianInputBlock block,
|
||||
const mfem::Vector &values
|
||||
) {
|
||||
REQUIRE(layout.Size(block) == values.Size());
|
||||
|
||||
const int offset = layout.Offset(block);
|
||||
|
||||
for (int entry = 0; entry < values.Size(); ++entry) {
|
||||
packedDirection(offset + entry) = values(entry);
|
||||
}
|
||||
}
|
||||
} // namespace prepared_hydrostatic_complete_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Complete Jacobian Matches Sum And Centered "
|
||||
"Differences",
|
||||
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
|
||||
&tags::prepared &tags::self_consistency
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 0.34
|
||||
);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.57
|
||||
);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.68);
|
||||
|
||||
constexpr double bernoulliConstant = 0.43;
|
||||
|
||||
const mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_complete_test_utils::make_rotation();
|
||||
|
||||
preparedOperator.Prepare(
|
||||
prepared_hydrostatic_complete_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
prepared_hydrostatic_complete_test_utils::make_dependencies(), rotation
|
||||
);
|
||||
|
||||
const mfem::Vector enthalpyVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 1.07
|
||||
);
|
||||
|
||||
const mfem::Vector gravityPotentialVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 1.31
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
prepared_hydrostatic_complete_test_utils::make_displacement_direction(
|
||||
f, 1.19, 0.38
|
||||
);
|
||||
|
||||
constexpr double bernoulliConstantVariation = -0.37;
|
||||
|
||||
mfem::Vector enthalpyAction;
|
||||
mfem::Vector gravityPotentialAction;
|
||||
mfem::Vector bernoulliConstantAction;
|
||||
mfem::Vector displacementAction;
|
||||
mfem::Vector completeAction;
|
||||
|
||||
preparedOperator.ApplyEnthalpyJacobianAction(
|
||||
enthalpyVariation, enthalpyAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyGravityPotentialJacobianAction(
|
||||
gravityPotentialVariation, gravityPotentialAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyBernoulliConstantJacobianAction(
|
||||
bernoulliConstantVariation, bernoulliConstantAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(
|
||||
displacementVariation, displacementAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyCompleteJacobianAction(
|
||||
enthalpyVariation, gravityPotentialVariation,
|
||||
bernoulliConstantVariation, displacementVariation, completeAction
|
||||
);
|
||||
|
||||
mfem::Vector summedAction(enthalpyAction);
|
||||
summedAction += gravityPotentialAction;
|
||||
summedAction += bernoulliConstantAction;
|
||||
summedAction += displacementAction;
|
||||
|
||||
constexpr double finiteDifferenceStep = 1.0e-5;
|
||||
|
||||
mfem::Vector centeredDifference;
|
||||
|
||||
prepared_hydrostatic_complete_test_utils::centered_complete_difference(
|
||||
f, rotation, enthalpy, gravityPotential, displacement,
|
||||
bernoulliConstant, enthalpyVariation, gravityPotentialVariation,
|
||||
displacementVariation, bernoulliConstantVariation, finiteDifferenceStep,
|
||||
centeredDifference
|
||||
);
|
||||
|
||||
const double summationError = gravity_prepared_test_utils::relative_error(
|
||||
completeAction, summedAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double centeredDifferenceError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
completeAction, centeredDifference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Complete-action summation error = " << summationError);
|
||||
|
||||
INFO(
|
||||
"Complete-action centered-difference error = "
|
||||
<< centeredDifferenceError
|
||||
);
|
||||
|
||||
CHECK(preparedOperator.GetCompleteJacobianStatistics().applications == 1);
|
||||
|
||||
CHECK(summationError < 5.0e-12);
|
||||
CHECK(centeredDifferenceError < 2.0e-7);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic MFEM Adapter Uses Four Block Layout And Reuses "
|
||||
"Preparation",
|
||||
tags::barotrope &tags::hydro &tags::integration &tags::jacobian
|
||||
&tags::mfem_operators &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 0.41
|
||||
);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.63
|
||||
);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.74);
|
||||
|
||||
constexpr double bernoulliConstant = 0.38;
|
||||
|
||||
preparedOperator.Prepare(
|
||||
prepared_hydrostatic_complete_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
prepared_hydrostatic_complete_test_utils::make_dependencies(),
|
||||
prepared_hydrostatic_complete_test_utils::make_rotation()
|
||||
);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumJacobianOperator
|
||||
adapter(f, preparedOperator);
|
||||
|
||||
const auto &layout = adapter.GetLayout();
|
||||
|
||||
CHECK(
|
||||
layout.Offset(
|
||||
mean_field::operators::HydrostaticJacobianInputBlock::enthalpy
|
||||
) == 0
|
||||
);
|
||||
|
||||
CHECK(
|
||||
layout.Offset(
|
||||
mean_field::operators::HydrostaticJacobianInputBlock::
|
||||
gravityPotential
|
||||
) == f.enthalpyFes->GetTrueVSize()
|
||||
);
|
||||
|
||||
CHECK(
|
||||
layout.Size(
|
||||
mean_field::operators::HydrostaticJacobianInputBlock::
|
||||
bernoulliConstant
|
||||
) == 1
|
||||
);
|
||||
|
||||
CHECK(adapter.Width() == layout.GetTotalSize());
|
||||
CHECK(adapter.Height() == layout.GetResidualSize());
|
||||
CHECK(adapter.Height() == f.enthalpyFes->GetTrueVSize());
|
||||
|
||||
const mfem::Vector enthalpyVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 1.12
|
||||
);
|
||||
|
||||
const mfem::Vector gravityPotentialVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 1.39
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
prepared_hydrostatic_complete_test_utils::make_displacement_direction(
|
||||
f, 1.28, 0.49
|
||||
);
|
||||
|
||||
constexpr double bernoulliConstantVariation = 0.29;
|
||||
|
||||
mfem::Vector packedDirection(adapter.Width());
|
||||
packedDirection = 0.0;
|
||||
|
||||
prepared_hydrostatic_complete_test_utils::set_block(
|
||||
packedDirection, layout,
|
||||
mean_field::operators::HydrostaticJacobianInputBlock::enthalpy,
|
||||
enthalpyVariation
|
||||
);
|
||||
|
||||
prepared_hydrostatic_complete_test_utils::set_block(
|
||||
packedDirection, layout,
|
||||
mean_field::operators::HydrostaticJacobianInputBlock::gravityPotential,
|
||||
gravityPotentialVariation
|
||||
);
|
||||
|
||||
prepared_hydrostatic_complete_test_utils::set_block(
|
||||
packedDirection, layout,
|
||||
mean_field::operators::HydrostaticJacobianInputBlock::displacement,
|
||||
displacementVariation
|
||||
);
|
||||
|
||||
packedDirection(layout.Offset(
|
||||
mean_field::operators::HydrostaticJacobianInputBlock::bernoulliConstant
|
||||
)) = bernoulliConstantVariation;
|
||||
|
||||
mfem::Vector directAction;
|
||||
mfem::Vector adapterAction;
|
||||
|
||||
preparedOperator.ApplyCompleteJacobianAction(
|
||||
enthalpyVariation, gravityPotentialVariation,
|
||||
bernoulliConstantVariation, displacementVariation, directAction
|
||||
);
|
||||
|
||||
adapter.Mult(packedDirection, adapterAction);
|
||||
|
||||
const double adapterError = gravity_prepared_test_utils::relative_error(
|
||||
adapterAction, directAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const auto contextStatisticsBefore =
|
||||
preparedOperator.GetContextPreparationStatistics();
|
||||
|
||||
const auto algebraicStatisticsBefore =
|
||||
preparedOperator.GetAlgebraicJacobianStatistics();
|
||||
|
||||
const auto displacementStatisticsBefore =
|
||||
preparedOperator.GetDisplacementJacobianStatistics();
|
||||
|
||||
mfem::Vector secondPackedDirection(packedDirection);
|
||||
secondPackedDirection *= -0.61;
|
||||
|
||||
mfem::Vector secondAdapterAction;
|
||||
adapter.Mult(secondPackedDirection, secondAdapterAction);
|
||||
|
||||
mfem::Vector expectedSecondAction(adapterAction);
|
||||
expectedSecondAction *= -0.61;
|
||||
|
||||
const double adapterLinearityError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
secondAdapterAction, expectedSecondAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const auto &contextStatisticsAfter =
|
||||
preparedOperator.GetContextPreparationStatistics();
|
||||
|
||||
const auto &algebraicStatisticsAfter =
|
||||
preparedOperator.GetAlgebraicJacobianStatistics();
|
||||
|
||||
const auto &displacementStatisticsAfter =
|
||||
preparedOperator.GetDisplacementJacobianStatistics();
|
||||
|
||||
INFO("MFEM adapter/direct-action error = " << adapterError);
|
||||
|
||||
INFO("MFEM adapter linearity error = " << adapterLinearityError);
|
||||
|
||||
CHECK(adapterError < 5.0e-12);
|
||||
CHECK(adapterLinearityError < 5.0e-12);
|
||||
CHECK(secondAdapterAction.Size() == adapter.Height());
|
||||
|
||||
CHECK(contextStatisticsAfter == contextStatisticsBefore);
|
||||
|
||||
CHECK(
|
||||
algebraicStatisticsAfter.preparations ==
|
||||
algebraicStatisticsBefore.preparations
|
||||
);
|
||||
|
||||
CHECK(
|
||||
displacementStatisticsAfter.preparations ==
|
||||
displacementStatisticsBefore.preparations
|
||||
);
|
||||
|
||||
CHECK(preparedOperator.GetCompleteJacobianStatistics().applications == 3);
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace prepared_hydrostatic_displacement_test_utils {
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies
|
||||
make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 401, .revision = 2},
|
||||
.enthalpy = {.identity = 409, .revision = 3},
|
||||
.gravityPotential = {.identity = 419, .revision = 5},
|
||||
.displacement = {.identity = 421, .revision = 7},
|
||||
.rotation = {.identity = 431, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 433, .revision = 13}
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
|
||||
make_state(
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement,
|
||||
const double bernoulliConstant
|
||||
) {
|
||||
return {
|
||||
.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant
|
||||
};
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double phase = 0.29
|
||||
) {
|
||||
return gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), phase
|
||||
);
|
||||
}
|
||||
|
||||
mfem::Vector make_gravity_potential(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double phase = 0.47
|
||||
) {
|
||||
return gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), phase
|
||||
);
|
||||
}
|
||||
|
||||
mfem::Vector make_displacement_direction(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double firstPhase,
|
||||
const double secondPhase
|
||||
) {
|
||||
mfem::Vector direction =
|
||||
gravity_prepared_test_utils::make_displacement(f, firstPhase);
|
||||
|
||||
const mfem::Vector secondField =
|
||||
gravity_prepared_test_utils::make_displacement(f, secondPhase);
|
||||
|
||||
direction -= secondField;
|
||||
return direction;
|
||||
}
|
||||
|
||||
mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
|
||||
mfem::Vector angularVelocity(3);
|
||||
|
||||
angularVelocity(0) = 0.16 * scale;
|
||||
angularVelocity(1) = -0.14 * scale;
|
||||
angularVelocity(2) = 0.46 * scale;
|
||||
|
||||
mfem::Vector center(3);
|
||||
|
||||
center(0) = 0.034;
|
||||
center(1) = -0.026;
|
||||
center(2) = 0.019;
|
||||
|
||||
return mean_field::physics::RigidRotation(angularVelocity, center);
|
||||
}
|
||||
|
||||
void centered_displacement_difference(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::RigidRotation &rotation,
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &baseDisplacement,
|
||||
const mfem::Vector &displacementVariation,
|
||||
const double bernoulliConstant,
|
||||
const double step,
|
||||
mfem::Vector &difference
|
||||
) {
|
||||
mfem::Vector displacementPlus(baseDisplacement);
|
||||
mfem::Vector displacementMinus(baseDisplacement);
|
||||
|
||||
displacementPlus.Add(step, displacementVariation);
|
||||
displacementMinus.Add(-step, displacementVariation);
|
||||
|
||||
mfem::Vector residualPlus;
|
||||
mfem::Vector residualMinus;
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential,
|
||||
displacementPlus, bernoulliConstant, residualPlus
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential,
|
||||
displacementMinus, bernoulliConstant, residualMinus
|
||||
);
|
||||
|
||||
difference = residualPlus;
|
||||
difference -= residualMinus;
|
||||
difference /= 2.0 * step;
|
||||
}
|
||||
|
||||
double relative_error(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
return gravity_prepared_test_utils::relative_error(
|
||||
actual, expected, communicator
|
||||
);
|
||||
}
|
||||
} // namespace prepared_hydrostatic_displacement_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Displacement Jacobian Matches Centered Differences",
|
||||
tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
prepared_hydrostatic_displacement_test_utils::make_enthalpy(f);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
prepared_hydrostatic_displacement_test_utils::make_gravity_potential(f);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.73);
|
||||
|
||||
constexpr double bernoulliConstant = 0.39;
|
||||
|
||||
const mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_displacement_test_utils::make_rotation(0.9);
|
||||
|
||||
const auto dependencies =
|
||||
prepared_hydrostatic_displacement_test_utils::make_dependencies();
|
||||
|
||||
const auto report = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_displacement_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
const mfem::Vector firstVariation =
|
||||
prepared_hydrostatic_displacement_test_utils::
|
||||
make_displacement_direction(f, 1.17, 0.31);
|
||||
|
||||
const mfem::Vector secondVariation =
|
||||
prepared_hydrostatic_displacement_test_utils::
|
||||
make_displacement_direction(f, 1.43, 0.58);
|
||||
|
||||
mfem::Vector combinedVariation(firstVariation);
|
||||
combinedVariation += secondVariation;
|
||||
|
||||
mfem::Vector firstAction;
|
||||
mfem::Vector secondAction;
|
||||
mfem::Vector combinedAction;
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(
|
||||
firstVariation, firstAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(
|
||||
secondVariation, secondAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(
|
||||
combinedVariation, combinedAction
|
||||
);
|
||||
|
||||
constexpr double finiteDifferenceStep = 1.0e-5;
|
||||
|
||||
mfem::Vector centeredDifference;
|
||||
|
||||
prepared_hydrostatic_displacement_test_utils::
|
||||
centered_displacement_difference(
|
||||
f, rotation, enthalpy, gravityPotential, displacement,
|
||||
firstVariation, bernoulliConstant, finiteDifferenceStep,
|
||||
centeredDifference
|
||||
);
|
||||
|
||||
mfem::Vector sumOfActions(firstAction);
|
||||
sumOfActions += secondAction;
|
||||
|
||||
const double centeredDifferenceError =
|
||||
prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
firstAction, centeredDifference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double linearityError =
|
||||
prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
combinedAction, sumOfActions, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Prepared displacement centered-difference error = "
|
||||
<< centeredDifferenceError
|
||||
);
|
||||
|
||||
INFO("Prepared displacement linearity error = " << linearityError);
|
||||
|
||||
const auto &statistics =
|
||||
preparedOperator.GetDisplacementJacobianStatistics();
|
||||
|
||||
CHECK(report.preparedDisplacementJacobianData);
|
||||
CHECK(statistics.preparations == 1);
|
||||
CHECK(statistics.applications == 3);
|
||||
CHECK(centeredDifferenceError < 2.0e-7);
|
||||
CHECK(linearityError < 5.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Displacement Jacobian Reuses And Refreshes Frozen "
|
||||
"Data",
|
||||
tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
prepared_hydrostatic_displacement_test_utils::make_enthalpy(f, 0.37);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
prepared_hydrostatic_displacement_test_utils::make_gravity_potential(
|
||||
f, 0.53
|
||||
);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.42);
|
||||
|
||||
constexpr double bernoulliConstant = 0.36;
|
||||
|
||||
mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_displacement_test_utils::make_rotation(0.75);
|
||||
|
||||
auto dependencies =
|
||||
prepared_hydrostatic_displacement_test_utils::make_dependencies();
|
||||
|
||||
preparedOperator.Prepare(
|
||||
prepared_hydrostatic_displacement_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
prepared_hydrostatic_displacement_test_utils::
|
||||
make_displacement_direction(f, 1.09, 0.27);
|
||||
|
||||
const mfem::Vector secondVariation =
|
||||
prepared_hydrostatic_displacement_test_utils::
|
||||
make_displacement_direction(f, 1.36, 0.64);
|
||||
|
||||
mfem::Vector initialAction;
|
||||
mfem::Vector secondDirectionAction;
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(
|
||||
displacementVariation, initialAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(
|
||||
secondVariation, secondDirectionAction
|
||||
);
|
||||
|
||||
CHECK(
|
||||
preparedOperator.GetDisplacementJacobianStatistics().preparations == 1
|
||||
);
|
||||
|
||||
rotation =
|
||||
prepared_hydrostatic_displacement_test_utils::make_rotation(1.45);
|
||||
|
||||
++dependencies.rotation.revision;
|
||||
|
||||
const auto rotationReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_displacement_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
mfem::Vector rotationUpdatedAction;
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(
|
||||
displacementVariation, rotationUpdatedAction
|
||||
);
|
||||
|
||||
constexpr double finiteDifferenceStep = 1.0e-5;
|
||||
|
||||
mfem::Vector rotationReference;
|
||||
|
||||
prepared_hydrostatic_displacement_test_utils::
|
||||
centered_displacement_difference(
|
||||
f, rotation, enthalpy, gravityPotential, displacement,
|
||||
displacementVariation, bernoulliConstant, finiteDifferenceStep,
|
||||
rotationReference
|
||||
);
|
||||
|
||||
const double rotationReferenceError =
|
||||
prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
rotationUpdatedAction, rotationReference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double rotationEffect =
|
||||
prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
rotationUpdatedAction, initialAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
CHECK_FALSE(rotationReport.contextReport.preparedGeometryState);
|
||||
CHECK(rotationReport.contextReport.preparedRotationDependencies);
|
||||
CHECK(rotationReport.contextReport.preparedBaseState);
|
||||
CHECK(rotationReport.updatedRotation);
|
||||
CHECK(rotationReport.preparedDisplacementJacobianData);
|
||||
CHECK_FALSE(rotationReport.preparedAlgebraicJacobianBlocks);
|
||||
CHECK(rotationReferenceError < 2.0e-7);
|
||||
CHECK(rotationEffect > 1.0e-8);
|
||||
|
||||
displacement = gravity_prepared_test_utils::make_displacement(f, 0.91);
|
||||
|
||||
++dependencies.displacement.revision;
|
||||
|
||||
const auto geometryReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_displacement_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
mfem::Vector geometryUpdatedAction;
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(
|
||||
displacementVariation, geometryUpdatedAction
|
||||
);
|
||||
|
||||
mfem::Vector geometryReference;
|
||||
|
||||
prepared_hydrostatic_displacement_test_utils::
|
||||
centered_displacement_difference(
|
||||
f, rotation, enthalpy, gravityPotential, displacement,
|
||||
displacementVariation, bernoulliConstant, finiteDifferenceStep,
|
||||
geometryReference
|
||||
);
|
||||
|
||||
const double geometryReferenceError =
|
||||
prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
geometryUpdatedAction, geometryReference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double geometryEffect =
|
||||
prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
geometryUpdatedAction, rotationUpdatedAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Rotation-updated displacement Jacobian error = "
|
||||
<< rotationReferenceError
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Displacement Jacobian change after rotation update = "
|
||||
<< rotationEffect
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Geometry-updated displacement Jacobian error = "
|
||||
<< geometryReferenceError
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Displacement Jacobian change after geometry update = "
|
||||
<< geometryEffect
|
||||
);
|
||||
|
||||
const auto &contextStatistics =
|
||||
preparedOperator.GetContextPreparationStatistics();
|
||||
|
||||
const auto &displacementStatistics =
|
||||
preparedOperator.GetDisplacementJacobianStatistics();
|
||||
|
||||
CHECK(geometryReport.contextReport.preparedGeometryState);
|
||||
CHECK(geometryReport.contextReport.preparedRotationDependencies);
|
||||
CHECK(geometryReport.contextReport.preparedBaseState);
|
||||
CHECK_FALSE(geometryReport.updatedRotation);
|
||||
CHECK(geometryReport.preparedAlgebraicJacobianBlocks);
|
||||
CHECK(geometryReport.preparedDisplacementJacobianData);
|
||||
|
||||
CHECK(contextStatistics.staticPreparations == 1);
|
||||
CHECK(contextStatistics.geometryPreparations == 2);
|
||||
CHECK(contextStatistics.rotationPreparations == 3);
|
||||
CHECK(contextStatistics.baseStatePreparations == 3);
|
||||
|
||||
CHECK(displacementStatistics.preparations == 3);
|
||||
CHECK(displacementStatistics.applications == 4);
|
||||
CHECK(geometryReferenceError < 2.0e-7);
|
||||
CHECK(geometryEffect > 1.0e-8);
|
||||
}
|
||||
472
tests/operators/prepared_hydrostatic_equilibrium_jacobian.cpp
Normal file
472
tests/operators/prepared_hydrostatic_equilibrium_jacobian.cpp
Normal file
@@ -0,0 +1,472 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace prepared_hydrostatic_jacobian_test_utils {
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies
|
||||
make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 307, .revision = 2},
|
||||
.enthalpy = {.identity = 311, .revision = 3},
|
||||
.gravityPotential = {.identity = 313, .revision = 5},
|
||||
.displacement = {.identity = 317, .revision = 7},
|
||||
.rotation = {.identity = 331, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 337, .revision = 13}
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
|
||||
make_state(
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement,
|
||||
const double bernoulliConstant
|
||||
) {
|
||||
return {
|
||||
.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant
|
||||
};
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double phase = 0.23
|
||||
) {
|
||||
return gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), phase
|
||||
);
|
||||
}
|
||||
|
||||
mfem::Vector make_gravity_potential(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double phase = 0.41
|
||||
) {
|
||||
return gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), phase
|
||||
);
|
||||
}
|
||||
|
||||
mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
|
||||
mfem::Vector angularVelocity(3);
|
||||
|
||||
angularVelocity(0) = 0.13 * scale;
|
||||
angularVelocity(1) = -0.19 * scale;
|
||||
angularVelocity(2) = 0.47 * scale;
|
||||
|
||||
mfem::Vector center(3);
|
||||
|
||||
center(0) = 0.031;
|
||||
center(1) = -0.023;
|
||||
center(2) = 0.017;
|
||||
|
||||
return mean_field::physics::RigidRotation(angularVelocity, center);
|
||||
}
|
||||
|
||||
void centered_residual_difference(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::RigidRotation &rotation,
|
||||
const mfem::Vector &enthalpyPlus,
|
||||
const mfem::Vector &enthalpyMinus,
|
||||
const mfem::Vector &gravityPotentialPlus,
|
||||
const mfem::Vector &gravityPotentialMinus,
|
||||
const mfem::Vector &displacement,
|
||||
const double bernoulliConstantPlus,
|
||||
const double bernoulliConstantMinus,
|
||||
mfem::Vector &difference
|
||||
) {
|
||||
mfem::Vector residualPlus;
|
||||
mfem::Vector residualMinus;
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpyPlus,
|
||||
gravityPotentialPlus, displacement, bernoulliConstantPlus,
|
||||
residualPlus
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_hydrostatic_equilibrium(
|
||||
f, *f.domainMapperStateless, rotation, enthalpyMinus,
|
||||
gravityPotentialMinus, displacement, bernoulliConstantMinus,
|
||||
residualMinus
|
||||
);
|
||||
|
||||
// The two states are separated by one complete variation:
|
||||
// x_+ = x + 0.5 dx and x_- = x - 0.5 dx.
|
||||
difference = residualPlus;
|
||||
difference -= residualMinus;
|
||||
}
|
||||
|
||||
double relative_error(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
return gravity_prepared_test_utils::relative_error(
|
||||
actual, expected, communicator
|
||||
);
|
||||
}
|
||||
} // namespace prepared_hydrostatic_jacobian_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Algebraic Jacobian Matches Centered Residual "
|
||||
"Differences",
|
||||
tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.73);
|
||||
|
||||
constexpr double bernoulliConstant = 0.39;
|
||||
|
||||
const mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_rotation();
|
||||
|
||||
const auto report = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_jacobian_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
prepared_hydrostatic_jacobian_test_utils::make_dependencies(), rotation
|
||||
);
|
||||
|
||||
const mfem::Vector enthalpyVariation =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.71);
|
||||
|
||||
const mfem::Vector gravityPotentialVariation =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(
|
||||
f, 0.83
|
||||
);
|
||||
|
||||
constexpr double bernoulliConstantVariation = -0.31;
|
||||
|
||||
mfem::Vector enthalpyAction;
|
||||
mfem::Vector gravityPotentialAction;
|
||||
mfem::Vector bernoulliConstantAction;
|
||||
mfem::Vector combinedAction;
|
||||
|
||||
preparedOperator.ApplyEnthalpyJacobianAction(
|
||||
enthalpyVariation, enthalpyAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyGravityPotentialJacobianAction(
|
||||
gravityPotentialVariation, gravityPotentialAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyBernoulliConstantJacobianAction(
|
||||
bernoulliConstantVariation, bernoulliConstantAction
|
||||
);
|
||||
|
||||
preparedOperator.ApplyAlgebraicJacobianAction(
|
||||
enthalpyVariation, gravityPotentialVariation,
|
||||
bernoulliConstantVariation, combinedAction
|
||||
);
|
||||
|
||||
mfem::Vector enthalpyPlus(enthalpy);
|
||||
mfem::Vector enthalpyMinus(enthalpy);
|
||||
mfem::Vector gravityPotentialPlus(gravityPotential);
|
||||
mfem::Vector gravityPotentialMinus(gravityPotential);
|
||||
|
||||
enthalpyPlus.Add(0.5, enthalpyVariation);
|
||||
enthalpyMinus.Add(-0.5, enthalpyVariation);
|
||||
|
||||
mfem::Vector enthalpyReference;
|
||||
|
||||
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
|
||||
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotential,
|
||||
gravityPotential, displacement, bernoulliConstant, bernoulliConstant,
|
||||
enthalpyReference
|
||||
);
|
||||
|
||||
enthalpyPlus = enthalpy;
|
||||
enthalpyMinus = enthalpy;
|
||||
|
||||
gravityPotentialPlus.Add(0.5, gravityPotentialVariation);
|
||||
|
||||
gravityPotentialMinus.Add(-0.5, gravityPotentialVariation);
|
||||
|
||||
mfem::Vector gravityPotentialReference;
|
||||
|
||||
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
|
||||
f, rotation, enthalpy, enthalpy, gravityPotentialPlus,
|
||||
gravityPotentialMinus, displacement, bernoulliConstant,
|
||||
bernoulliConstant, gravityPotentialReference
|
||||
);
|
||||
|
||||
gravityPotentialPlus = gravityPotential;
|
||||
gravityPotentialMinus = gravityPotential;
|
||||
|
||||
mfem::Vector bernoulliConstantReference;
|
||||
|
||||
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
|
||||
f, rotation, enthalpy, enthalpy, gravityPotential, gravityPotential,
|
||||
displacement, bernoulliConstant + 0.5 * bernoulliConstantVariation,
|
||||
bernoulliConstant - 0.5 * bernoulliConstantVariation,
|
||||
bernoulliConstantReference
|
||||
);
|
||||
|
||||
enthalpyPlus.Add(0.5, enthalpyVariation);
|
||||
enthalpyMinus.Add(-0.5, enthalpyVariation);
|
||||
|
||||
gravityPotentialPlus.Add(0.5, gravityPotentialVariation);
|
||||
|
||||
gravityPotentialMinus.Add(-0.5, gravityPotentialVariation);
|
||||
|
||||
mfem::Vector combinedReference;
|
||||
|
||||
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
|
||||
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus,
|
||||
gravityPotentialMinus, displacement,
|
||||
bernoulliConstant + 0.5 * bernoulliConstantVariation,
|
||||
bernoulliConstant - 0.5 * bernoulliConstantVariation, combinedReference
|
||||
);
|
||||
|
||||
mfem::Vector sumOfBlocks(enthalpyAction);
|
||||
sumOfBlocks += gravityPotentialAction;
|
||||
sumOfBlocks += bernoulliConstantAction;
|
||||
|
||||
const double enthalpyError =
|
||||
prepared_hydrostatic_jacobian_test_utils::relative_error(
|
||||
enthalpyAction, enthalpyReference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double gravityPotentialError =
|
||||
prepared_hydrostatic_jacobian_test_utils::relative_error(
|
||||
gravityPotentialAction, gravityPotentialReference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double bernoulliConstantError =
|
||||
prepared_hydrostatic_jacobian_test_utils::relative_error(
|
||||
bernoulliConstantAction, bernoulliConstantReference,
|
||||
f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double combinedError =
|
||||
prepared_hydrostatic_jacobian_test_utils::relative_error(
|
||||
combinedAction, combinedReference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double blockSumError =
|
||||
prepared_hydrostatic_jacobian_test_utils::relative_error(
|
||||
combinedAction, sumOfBlocks, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Enthalpy block error = " << enthalpyError);
|
||||
INFO("Gravity-potential block error = " << gravityPotentialError);
|
||||
INFO("Bernoulli-constant block error = " << bernoulliConstantError);
|
||||
INFO("Combined algebraic action error = " << combinedError);
|
||||
INFO("Combined-versus-summed-block error = " << blockSumError);
|
||||
|
||||
const auto &statistics = preparedOperator.GetAlgebraicJacobianStatistics();
|
||||
|
||||
CHECK(report.preparedAlgebraicJacobianBlocks);
|
||||
CHECK(statistics.preparations == 1);
|
||||
CHECK(statistics.enthalpyApplications == 1);
|
||||
CHECK(statistics.gravityPotentialApplications == 1);
|
||||
CHECK(statistics.bernoulliConstantApplications == 1);
|
||||
CHECK(statistics.combinedApplications == 1);
|
||||
|
||||
CHECK(enthalpyError < 5.0e-12);
|
||||
CHECK(gravityPotentialError < 5.0e-12);
|
||||
CHECK(bernoulliConstantError < 5.0e-12);
|
||||
CHECK(combinedError < 5.0e-12);
|
||||
CHECK(blockSumError < 5.0e-13);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hydrostatic Algebraic Jacobian Reuses And Rebuilds Only With "
|
||||
"Geometry",
|
||||
tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator
|
||||
preparedOperator(f, *f.domainMapperStateless);
|
||||
|
||||
mfem::Vector enthalpy =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f);
|
||||
|
||||
mfem::Vector gravityPotential =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.42);
|
||||
|
||||
double bernoulliConstant = 0.37;
|
||||
|
||||
mean_field::physics::RigidRotation rotation =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_rotation(0.8);
|
||||
|
||||
auto dependencies =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_dependencies();
|
||||
|
||||
preparedOperator.Prepare(
|
||||
prepared_hydrostatic_jacobian_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
const mfem::Vector enthalpyVariation =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.67);
|
||||
|
||||
const mfem::Vector gravityPotentialVariation =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(
|
||||
f, 0.79
|
||||
);
|
||||
|
||||
constexpr double bernoulliConstantVariation = 0.28;
|
||||
|
||||
mfem::Vector initialAction;
|
||||
|
||||
preparedOperator.ApplyAlgebraicJacobianAction(
|
||||
enthalpyVariation, gravityPotentialVariation,
|
||||
bernoulliConstantVariation, initialAction
|
||||
);
|
||||
|
||||
enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.13);
|
||||
|
||||
gravityPotential =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(
|
||||
f, 1.31
|
||||
);
|
||||
|
||||
bernoulliConstant = 0.62;
|
||||
rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation(1.4);
|
||||
|
||||
++dependencies.enthalpy.revision;
|
||||
++dependencies.gravityPotential.revision;
|
||||
++dependencies.bernoulliConstant.revision;
|
||||
++dependencies.rotation.revision;
|
||||
|
||||
const auto baseStateReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_jacobian_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
mfem::Vector baseStateChangedAction;
|
||||
|
||||
preparedOperator.ApplyAlgebraicJacobianAction(
|
||||
enthalpyVariation, gravityPotentialVariation,
|
||||
bernoulliConstantVariation, baseStateChangedAction
|
||||
);
|
||||
|
||||
CHECK_FALSE(baseStateReport.contextReport.preparedGeometryState);
|
||||
CHECK(baseStateReport.contextReport.preparedRotationDependencies);
|
||||
CHECK(baseStateReport.contextReport.preparedBaseState);
|
||||
CHECK_FALSE(baseStateReport.preparedAlgebraicJacobianBlocks);
|
||||
CHECK(preparedOperator.GetAlgebraicJacobianStatistics().preparations == 1);
|
||||
CHECK(
|
||||
prepared_hydrostatic_jacobian_test_utils::relative_error(
|
||||
baseStateChangedAction, initialAction, f.mesh->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
const mfem::Vector secondEnthalpyVariation =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.57);
|
||||
|
||||
const mfem::Vector secondGravityPotentialVariation =
|
||||
prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(
|
||||
f, 1.73
|
||||
);
|
||||
|
||||
mfem::Vector secondDirectionAction;
|
||||
|
||||
preparedOperator.ApplyAlgebraicJacobianAction(
|
||||
secondEnthalpyVariation, secondGravityPotentialVariation, -0.19,
|
||||
secondDirectionAction
|
||||
);
|
||||
|
||||
CHECK(preparedOperator.GetAlgebraicJacobianStatistics().preparations == 1);
|
||||
|
||||
displacement = gravity_prepared_test_utils::make_displacement(f, 0.73);
|
||||
|
||||
++dependencies.displacement.revision;
|
||||
|
||||
const auto geometryReport = preparedOperator.Prepare(
|
||||
prepared_hydrostatic_jacobian_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies, rotation
|
||||
);
|
||||
|
||||
mfem::Vector geometryChangedAction;
|
||||
|
||||
preparedOperator.ApplyAlgebraicJacobianAction(
|
||||
enthalpyVariation, gravityPotentialVariation,
|
||||
bernoulliConstantVariation, geometryChangedAction
|
||||
);
|
||||
|
||||
mfem::Vector enthalpyPlus(enthalpy);
|
||||
mfem::Vector enthalpyMinus(enthalpy);
|
||||
mfem::Vector gravityPotentialPlus(gravityPotential);
|
||||
mfem::Vector gravityPotentialMinus(gravityPotential);
|
||||
|
||||
enthalpyPlus.Add(0.5, enthalpyVariation);
|
||||
enthalpyMinus.Add(-0.5, enthalpyVariation);
|
||||
|
||||
gravityPotentialPlus.Add(0.5, gravityPotentialVariation);
|
||||
|
||||
gravityPotentialMinus.Add(-0.5, gravityPotentialVariation);
|
||||
|
||||
mfem::Vector geometryReference;
|
||||
|
||||
prepared_hydrostatic_jacobian_test_utils::centered_residual_difference(
|
||||
f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus,
|
||||
gravityPotentialMinus, displacement,
|
||||
bernoulliConstant + 0.5 * bernoulliConstantVariation,
|
||||
bernoulliConstant - 0.5 * bernoulliConstantVariation, geometryReference
|
||||
);
|
||||
|
||||
const double geometryReferenceError =
|
||||
prepared_hydrostatic_jacobian_test_utils::relative_error(
|
||||
geometryChangedAction, geometryReference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double geometryEffect =
|
||||
prepared_hydrostatic_jacobian_test_utils::relative_error(
|
||||
geometryChangedAction, initialAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Geometry-updated algebraic Jacobian error = " << geometryReferenceError
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Algebraic Jacobian change after deformation update = "
|
||||
<< geometryEffect
|
||||
);
|
||||
|
||||
const auto &statistics = preparedOperator.GetAlgebraicJacobianStatistics();
|
||||
|
||||
CHECK(geometryReport.contextReport.preparedGeometryState);
|
||||
CHECK(geometryReport.preparedAlgebraicJacobianBlocks);
|
||||
CHECK(statistics.preparations == 2);
|
||||
CHECK(statistics.enthalpyApplications == 0);
|
||||
CHECK(statistics.gravityPotentialApplications == 0);
|
||||
CHECK(statistics.bernoulliConstantApplications == 0);
|
||||
CHECK(statistics.combinedApplications == 4);
|
||||
|
||||
CHECK(geometryReferenceError < 5.0e-12);
|
||||
CHECK(geometryEffect > 1.0e-8);
|
||||
}
|
||||
144
tests/physics/barotrope.cpp
Normal file
144
tests/physics/barotrope.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Satisfies Its Analytic Identities",
|
||||
tags::hydro &tags::unit &tags::barotrope
|
||||
) {
|
||||
constexpr double polytropic_index = 3.0;
|
||||
constexpr double polytropic_constant = 1.5;
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(
|
||||
polytropic_index, polytropic_constant
|
||||
);
|
||||
|
||||
const std::array<double, 5> densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0};
|
||||
|
||||
for (const double density : densities) {
|
||||
const double pressure = barotrope.pressure_from_density(density);
|
||||
|
||||
const double enthalpy = barotrope.enthalpy_from_density(density);
|
||||
|
||||
const double reconstructed_density =
|
||||
barotrope.density_from_enthalpy(enthalpy);
|
||||
|
||||
const double reconstructed_pressure =
|
||||
barotrope.pressure_from_enthalpy(enthalpy);
|
||||
|
||||
CHECK_THAT(
|
||||
reconstructed_density, Catch::Matchers::WithinRel(density, 2.0e-14)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
reconstructed_pressure,
|
||||
Catch::Matchers::WithinRel(pressure, 2.0e-14)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
pressure, Catch::Matchers::WithinRel(
|
||||
density * enthalpy / (polytropic_index + 1.0), 2.0e-14
|
||||
)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
barotrope.pressure_derivative_from_enthalpy(enthalpy),
|
||||
Catch::Matchers::WithinRel(density, 2.0e-14)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
barotrope.pressure_derivative_from_density(density),
|
||||
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Derivatives Match Centered Differences",
|
||||
tags::hydro &tags::jacobian &tags::unit &tags::barotrope
|
||||
) {
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const std::array<double, 4> enthalpies{0.05, 0.2, 0.7, 1.4};
|
||||
|
||||
for (const double enthalpy : enthalpies) {
|
||||
const double step = 1.0e-6 * std::max(1.0, enthalpy);
|
||||
|
||||
const double density_difference =
|
||||
(barotrope.density_from_enthalpy(enthalpy + step) -
|
||||
barotrope.density_from_enthalpy(enthalpy - step)) /
|
||||
(2.0 * step);
|
||||
|
||||
const double pressure_difference =
|
||||
(barotrope.pressure_from_enthalpy(enthalpy + step) -
|
||||
barotrope.pressure_from_enthalpy(enthalpy - step)) /
|
||||
(2.0 * step);
|
||||
|
||||
CHECK_THAT(
|
||||
density_difference,
|
||||
Catch::Matchers::WithinRel(
|
||||
barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10
|
||||
)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
pressure_difference,
|
||||
Catch::Matchers::WithinRel(
|
||||
barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Has An Exact Zero Density Surface",
|
||||
tags::hydro &tags::unit &tags::barotrope
|
||||
) {
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
CHECK(barotrope.density_from_enthalpy(-1.0) == 0.0);
|
||||
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.pressure_from_enthalpy(-1.0) == 0.0);
|
||||
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.density_derivative_from_enthalpy(-1.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Rejects Invalid Material Parameters",
|
||||
tags::hydro &tags::unit
|
||||
) {
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::physics::PolytropicBarotrope(0.5, 1.0),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::physics::PolytropicBarotrope(3.0, 0.0),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::physics::PolytropicBarotrope(
|
||||
std::numeric_limits<double>::infinity(), 1.0
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.0);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.pressure_from_density(-1.0), std::domain_error);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error);
|
||||
}
|
||||
699
tests/physics/barotrope_pressure.cpp
Normal file
699
tests/physics/barotrope_pressure.cpp
Normal file
@@ -0,0 +1,699 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace polytropic_barotrope_test_utils {
|
||||
template <typename Function>
|
||||
double centered_derivative(
|
||||
Function &&function,
|
||||
const double position,
|
||||
const double step
|
||||
) {
|
||||
return (function(position + step) - function(position - step)) /
|
||||
(2.0 * step);
|
||||
}
|
||||
|
||||
template <typename Integrand>
|
||||
double integrate_cube(
|
||||
const mfem::IntegrationRule &integrationRule,
|
||||
Integrand &&integrand
|
||||
) {
|
||||
double integral = 0.0;
|
||||
|
||||
for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints();
|
||||
++pointIndex) {
|
||||
const mfem::IntegrationPoint &integrationPoint =
|
||||
integrationRule.IntPoint(pointIndex);
|
||||
|
||||
integral += integrationPoint.weight * integrand(integrationPoint);
|
||||
}
|
||||
|
||||
return integral;
|
||||
}
|
||||
} // namespace polytropic_barotrope_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Satisfies Its Thermodynamic Identities",
|
||||
tags::barotrope &tags::physics &tags::unit
|
||||
) {
|
||||
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
|
||||
|
||||
constexpr std::array<double, 4> densities{1.0e-4, 0.02, 0.37, 2.4};
|
||||
|
||||
constexpr double polytropicConstant = 0.73;
|
||||
|
||||
for (const double polytropicIndex : polytropicIndices) {
|
||||
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(
|
||||
polytropicIndex, polytropicConstant
|
||||
);
|
||||
|
||||
const double expectedEnthalpyScale =
|
||||
(polytropicIndex + 1.0) * polytropicConstant;
|
||||
|
||||
CHECK(barotrope.polytropic_index() == polytropicIndex);
|
||||
|
||||
CHECK(barotrope.polytropic_constant() == polytropicConstant);
|
||||
|
||||
CHECK(barotrope.enthalpy_scale() == expectedEnthalpyScale);
|
||||
|
||||
for (const double density : densities) {
|
||||
CAPTURE(polytropicIndex, polytropicConstant, density);
|
||||
|
||||
const double expectedPressure =
|
||||
polytropicConstant *
|
||||
std::pow(density, 1.0 + 1.0 / polytropicIndex);
|
||||
|
||||
const double expectedEnthalpy =
|
||||
expectedEnthalpyScale *
|
||||
std::pow(density, 1.0 / polytropicIndex);
|
||||
|
||||
const double pressureFromDensity =
|
||||
barotrope.pressure_from_density(density);
|
||||
|
||||
const double enthalpyFromDensity =
|
||||
barotrope.enthalpy_from_density(density);
|
||||
|
||||
const double recoveredDensity =
|
||||
barotrope.density_from_enthalpy(enthalpyFromDensity);
|
||||
|
||||
const double pressureFromEnthalpy =
|
||||
barotrope.pressure_from_enthalpy(enthalpyFromDensity);
|
||||
|
||||
CHECK_THAT(
|
||||
pressureFromDensity,
|
||||
Catch::Matchers::WithinRel(expectedPressure, 2.0e-13)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
enthalpyFromDensity,
|
||||
Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
recoveredDensity,
|
||||
Catch::Matchers::WithinRel(density, 5.0e-13)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
pressureFromEnthalpy,
|
||||
Catch::Matchers::WithinRel(expectedPressure, 5.0e-13)
|
||||
);
|
||||
|
||||
/*
|
||||
* Polytropic identity:
|
||||
*
|
||||
* P = rho h / (n + 1).
|
||||
*/
|
||||
CHECK_THAT(
|
||||
pressureFromEnthalpy,
|
||||
Catch::Matchers::WithinRel(
|
||||
density * enthalpyFromDensity / (polytropicIndex + 1.0),
|
||||
5.0e-13
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* Polytropic identity:
|
||||
*
|
||||
* dP / dh = rho.
|
||||
*
|
||||
* The implementation should return the same
|
||||
* value as density_from_enthalpy().
|
||||
*/
|
||||
CHECK(
|
||||
barotrope.pressure_derivative_from_enthalpy(
|
||||
enthalpyFromDensity
|
||||
) == barotrope.density_from_enthalpy(enthalpyFromDensity)
|
||||
);
|
||||
|
||||
/*
|
||||
* Since
|
||||
*
|
||||
* h = (n + 1) K rho^(1/n),
|
||||
*
|
||||
* it follows that
|
||||
*
|
||||
* dP / d rho = h / n.
|
||||
*/
|
||||
CHECK_THAT(
|
||||
barotrope.pressure_derivative_from_density(density),
|
||||
Catch::Matchers::WithinRel(
|
||||
enthalpyFromDensity / polytropicIndex, 5.0e-13
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Pressure Derivatives Match Centered Differences",
|
||||
tags::barotrope &tags::physics &tags::unit &tags::jacobian &tags::pressure
|
||||
) {
|
||||
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
|
||||
|
||||
constexpr std::array<double, 3> positiveValues{0.2, 0.73, 1.8};
|
||||
|
||||
constexpr double polytropicConstant = 0.61;
|
||||
|
||||
for (const double polytropicIndex : polytropicIndices) {
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(
|
||||
polytropicIndex, polytropicConstant
|
||||
);
|
||||
|
||||
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
|
||||
for (const double enthalpy : positiveValues) {
|
||||
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
|
||||
|
||||
const double numericalDerivative =
|
||||
polytropic_barotrope_test_utils::centered_derivative(
|
||||
[&barotrope](const double perturbedEnthalpy) {
|
||||
return barotrope.pressure_from_enthalpy(
|
||||
perturbedEnthalpy
|
||||
);
|
||||
},
|
||||
enthalpy, step
|
||||
);
|
||||
|
||||
const double analyticDerivative =
|
||||
barotrope.pressure_derivative_from_enthalpy(enthalpy);
|
||||
|
||||
CAPTURE(
|
||||
polytropicIndex, enthalpy, step, numericalDerivative,
|
||||
analyticDerivative
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
numericalDerivative,
|
||||
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)
|
||||
);
|
||||
}
|
||||
|
||||
for (const double density : positiveValues) {
|
||||
const double step = 2.0e-6 * std::max(1.0, std::abs(density));
|
||||
|
||||
const double numericalDerivative =
|
||||
polytropic_barotrope_test_utils::centered_derivative(
|
||||
[&barotrope](const double perturbedDensity) {
|
||||
return barotrope.pressure_from_density(
|
||||
perturbedDensity
|
||||
);
|
||||
},
|
||||
density, step
|
||||
);
|
||||
|
||||
const double analyticDerivative =
|
||||
barotrope.pressure_derivative_from_density(density);
|
||||
|
||||
CAPTURE(
|
||||
polytropicIndex, density, step, numericalDerivative,
|
||||
analyticDerivative
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
numericalDerivative,
|
||||
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Density Derivative Matches Centered Differences",
|
||||
tags::barotrope &tags::physics &tags::unit &tags::jacobian &tags::pressure
|
||||
) {
|
||||
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
|
||||
|
||||
constexpr std::array<double, 3> enthalpies{0.2, 0.73, 1.8};
|
||||
|
||||
constexpr double polytropicConstant = 0.61;
|
||||
|
||||
for (const double polytropicIndex : polytropicIndices) {
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(
|
||||
polytropicIndex, polytropicConstant
|
||||
);
|
||||
|
||||
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
|
||||
for (const double enthalpy : enthalpies) {
|
||||
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
|
||||
|
||||
const double numericalDerivative =
|
||||
polytropic_barotrope_test_utils::centered_derivative(
|
||||
[&barotrope](const double perturbedEnthalpy) {
|
||||
return barotrope.density_from_enthalpy(
|
||||
perturbedEnthalpy
|
||||
);
|
||||
},
|
||||
enthalpy, step
|
||||
);
|
||||
|
||||
const double analyticDerivative =
|
||||
barotrope.density_derivative_from_enthalpy(enthalpy);
|
||||
|
||||
CAPTURE(
|
||||
polytropicIndex, enthalpy, step, numericalDerivative,
|
||||
analyticDerivative
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
numericalDerivative,
|
||||
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Defines Consistent Surface And Exterior Behavior",
|
||||
tags::barotrope &tags::physics &tags::unit &tags::pressure
|
||||
) {
|
||||
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
|
||||
|
||||
constexpr double polytropicConstant = 0.47;
|
||||
constexpr double exteriorEnthalpy = -0.3;
|
||||
|
||||
for (const double polytropicIndex : polytropicIndices) {
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(
|
||||
polytropicIndex, polytropicConstant
|
||||
);
|
||||
|
||||
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
|
||||
/*
|
||||
* Exact surface values.
|
||||
*/
|
||||
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.pressure_from_density(0.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.enthalpy_from_density(0.0) == 0.0);
|
||||
|
||||
CHECK(barotrope.pressure_derivative_from_density(0.0) == 0.0);
|
||||
|
||||
/*
|
||||
* Positive-part extension into h < 0.
|
||||
*/
|
||||
CHECK(barotrope.density_from_enthalpy(exteriorEnthalpy) == 0.0);
|
||||
|
||||
CHECK(barotrope.pressure_from_enthalpy(exteriorEnthalpy) == 0.0);
|
||||
|
||||
CHECK(
|
||||
barotrope.density_derivative_from_enthalpy(exteriorEnthalpy) ==
|
||||
0.0
|
||||
);
|
||||
|
||||
CHECK(
|
||||
barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) ==
|
||||
0.0
|
||||
);
|
||||
|
||||
/*
|
||||
* At h = 0, rho(h) has a nonzero right
|
||||
* derivative only for n = 1.
|
||||
*/
|
||||
const double expectedSurfaceDensityDerivative =
|
||||
polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0;
|
||||
|
||||
CHECK(
|
||||
barotrope.density_derivative_from_enthalpy(0.0) ==
|
||||
expectedSurfaceDensityDerivative
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic Barotrope Rejects Invalid Physical Inputs",
|
||||
tags::barotrope &tags::physics &tags::unit &tags::pressure
|
||||
) {
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::physics::PolytropicBarotrope(0.999, 1.0),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::physics::PolytropicBarotrope(
|
||||
std::numeric_limits<double>::infinity(), 1.0
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::physics::PolytropicBarotrope(3.0, 0.0),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::physics::PolytropicBarotrope(3.0, -1.0),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.75);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.pressure_from_density(-0.1), std::domain_error);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-0.1), std::domain_error);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
barotrope.pressure_derivative_from_density(-0.1), std::domain_error
|
||||
);
|
||||
|
||||
constexpr std::array<double, 3> nonfiniteValues{
|
||||
std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::quiet_NaN()
|
||||
};
|
||||
|
||||
for (const double nonfiniteValue : nonfiniteValues) {
|
||||
CAPTURE(nonfiniteValue);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
barotrope.density_from_enthalpy(nonfiniteValue), std::domain_error
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
barotrope.pressure_from_enthalpy(nonfiniteValue), std::domain_error
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
barotrope.density_derivative_from_enthalpy(nonfiniteValue),
|
||||
std::domain_error
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
barotrope.pressure_derivative_from_enthalpy(nonfiniteValue),
|
||||
std::domain_error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force And Pressure Integral Have Distinct Registered Forms",
|
||||
tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature
|
||||
&tags::unit
|
||||
) {
|
||||
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
|
||||
|
||||
/*
|
||||
* For the registered H1 order p = 3 and n = 3:
|
||||
*
|
||||
* h has degree p,
|
||||
* P(h) has degree 4p,
|
||||
*
|
||||
* so the nonlinear EOS contributes an additional
|
||||
*
|
||||
* 4p - p = 3p = 9
|
||||
*
|
||||
* beyond the registered enthalpy operand.
|
||||
*/
|
||||
constexpr int enthalpyOrder =
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder;
|
||||
|
||||
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
|
||||
|
||||
constexpr int geometryWeightOrder = 2;
|
||||
|
||||
constexpr mean_field::quadrature::Query pressureIntegralQuery =
|
||||
EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::PressureIntegral>(
|
||||
mean_field::quadrature::QuadratureRole::diagnostic,
|
||||
geometryWeightOrder, std::array<int, 1>{pressureExtraOrder},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
constexpr mean_field::quadrature::Query pressureForceQuery =
|
||||
EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::PressureForce>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
geometryWeightOrder, std::array<int, 1>{pressureExtraOrder},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
STATIC_CHECK(
|
||||
mean_field::field::Enthalpy::Form::PressureIntegral::
|
||||
dynamicOrderCount == 1
|
||||
);
|
||||
|
||||
STATIC_CHECK(
|
||||
mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1
|
||||
);
|
||||
|
||||
STATIC_CHECK(
|
||||
mean_field::field::Enthalpy::Form::PressureIntegral::policyKey !=
|
||||
mean_field::field::Enthalpy::Form::PressureForce::policyKey
|
||||
);
|
||||
|
||||
REQUIRE(pressureIntegralQuery.base_order.has_value());
|
||||
|
||||
REQUIRE(pressureForceQuery.base_order.has_value());
|
||||
|
||||
/*
|
||||
* Pressure integral:
|
||||
*
|
||||
* degree(P) + degree(J)
|
||||
* = 12 + 2
|
||||
* = 14.
|
||||
*/
|
||||
CHECK(*pressureIntegralQuery.base_order == 14);
|
||||
|
||||
/*
|
||||
* Pressure force:
|
||||
*
|
||||
* degree(P)
|
||||
* + degree(grad w)
|
||||
* + degree(J)
|
||||
*
|
||||
* = 12 + 2 + 2
|
||||
* = 16.
|
||||
*/
|
||||
CHECK(*pressureForceQuery.base_order == 16);
|
||||
|
||||
CHECK(
|
||||
pressureIntegralQuery.term ==
|
||||
mean_field::quadrature::Term::pressure_integral
|
||||
);
|
||||
|
||||
CHECK(
|
||||
pressureForceQuery.term == mean_field::quadrature::Term::pressure_force
|
||||
);
|
||||
|
||||
CHECK(
|
||||
pressureIntegralQuery.role ==
|
||||
mean_field::quadrature::QuadratureRole::diagnostic
|
||||
);
|
||||
|
||||
CHECK(
|
||||
pressureForceQuery.role ==
|
||||
mean_field::quadrature::QuadratureRole::discretization
|
||||
);
|
||||
|
||||
CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR);
|
||||
|
||||
CHECK(pressureForceQuery.domain == mean_field::utils::DOMAINS::STELLAR);
|
||||
|
||||
/*
|
||||
* Verify that the two terms route to independent policy
|
||||
* controls.
|
||||
*/
|
||||
mean_field::quadrature::RuleSet ruleSet =
|
||||
mean_field::quadrature::make_rule_set(
|
||||
mean_field::quadrature::Mode::production
|
||||
);
|
||||
|
||||
ruleSet.pressure_integral.boost = 3;
|
||||
ruleSet.pressure_force.boost = 5;
|
||||
|
||||
const mean_field::quadrature::Policy policy(std::move(ruleSet));
|
||||
|
||||
const mean_field::quadrature::Resolution pressureIntegralResolution =
|
||||
policy.resolve(pressureIntegralQuery);
|
||||
|
||||
const mean_field::quadrature::Resolution pressureForceResolution =
|
||||
policy.resolve(pressureForceQuery);
|
||||
|
||||
CHECK(pressureIntegralResolution.base_order == 14);
|
||||
|
||||
CHECK(pressureIntegralResolution.boost == 3);
|
||||
|
||||
CHECK(pressureIntegralResolution.order == 17);
|
||||
|
||||
CHECK(pressureForceResolution.base_order == 16);
|
||||
|
||||
CHECK(pressureForceResolution.boost == 5);
|
||||
|
||||
CHECK(pressureForceResolution.order == 21);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stage Four Pressure Quadrature Exactly Integrates An N Three Polynomial",
|
||||
tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature
|
||||
&tags::accuracy
|
||||
) {
|
||||
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
|
||||
|
||||
constexpr int enthalpyOrder =
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder;
|
||||
|
||||
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
|
||||
|
||||
/*
|
||||
* K = 1/4 and n = 3 give
|
||||
*
|
||||
* (n + 1) K = 1,
|
||||
* rho(h) = h^3,
|
||||
* P(h) = h^4 / 4.
|
||||
*/
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
constexpr mean_field::quadrature::Query pressureIntegralQuery =
|
||||
EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::PressureIntegral>(
|
||||
mean_field::quadrature::QuadratureRole::diagnostic, 0,
|
||||
std::array<int, 1>{pressureExtraOrder},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::affine
|
||||
);
|
||||
|
||||
constexpr mean_field::quadrature::Query pressureForceQuery =
|
||||
EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::PressureForce>(
|
||||
mean_field::quadrature::QuadratureRole::discretization, 0,
|
||||
std::array<int, 1>{pressureExtraOrder},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::affine
|
||||
);
|
||||
|
||||
const mean_field::quadrature::RuleFactory ruleFactory{
|
||||
mean_field::quadrature::Policy(
|
||||
mean_field::quadrature::make_rule_set(
|
||||
mean_field::quadrature::Mode::production
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
const mean_field::quadrature::MfemRule pressureIntegralRule =
|
||||
ruleFactory.get(pressureIntegralQuery, mfem::Geometry::CUBE);
|
||||
|
||||
const mean_field::quadrature::MfemRule pressureForceRule =
|
||||
ruleFactory.get(pressureForceQuery, mfem::Geometry::CUBE);
|
||||
|
||||
/*
|
||||
* On the reference cube [0,1]^3 choose
|
||||
*
|
||||
* h = x^3 y^3 z^3.
|
||||
*
|
||||
* This is representable by the order-three H1 space.
|
||||
* Then
|
||||
*
|
||||
* P = x^12 y^12 z^12 / 4.
|
||||
*/
|
||||
const double numericalPressureIntegral =
|
||||
polytropic_barotrope_test_utils::integrate_cube(
|
||||
*pressureIntegralRule.integration_rule,
|
||||
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
|
||||
const double coordinateProduct = integrationPoint.x *
|
||||
integrationPoint.y *
|
||||
integrationPoint.z;
|
||||
|
||||
const double enthalpy = std::pow(coordinateProduct, 3.0);
|
||||
|
||||
return barotrope.pressure_from_enthalpy(enthalpy);
|
||||
}
|
||||
);
|
||||
|
||||
const double analyticPressureIntegral = 0.25 / std::pow(13.0, 3.0);
|
||||
|
||||
/*
|
||||
* Choose a representable vector test function whose
|
||||
* divergence is
|
||||
*
|
||||
* div(w) = x^2 y^2 z^2.
|
||||
*
|
||||
* Therefore
|
||||
*
|
||||
* -P div(w)
|
||||
* = -x^14 y^14 z^14 / 4.
|
||||
*/
|
||||
const double numericalPressureForceIntegral =
|
||||
polytropic_barotrope_test_utils::integrate_cube(
|
||||
*pressureForceRule.integration_rule,
|
||||
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
|
||||
const double coordinateProduct = integrationPoint.x *
|
||||
integrationPoint.y *
|
||||
integrationPoint.z;
|
||||
|
||||
const double enthalpy = std::pow(coordinateProduct, 3.0);
|
||||
|
||||
const double pressure =
|
||||
barotrope.pressure_from_enthalpy(enthalpy);
|
||||
|
||||
const double testDivergence =
|
||||
integrationPoint.x * integrationPoint.x *
|
||||
integrationPoint.y * integrationPoint.y *
|
||||
integrationPoint.z * integrationPoint.z;
|
||||
|
||||
return -pressure * testDivergence;
|
||||
}
|
||||
);
|
||||
|
||||
const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0);
|
||||
|
||||
INFO(
|
||||
"Pressure-integral quadrature order = "
|
||||
<< pressureIntegralRule.resolution.order
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Pressure-force quadrature order = "
|
||||
<< pressureForceRule.resolution.order
|
||||
);
|
||||
|
||||
INFO("Numerical pressure integral = " << numericalPressureIntegral);
|
||||
|
||||
INFO("Analytic pressure integral = " << analyticPressureIntegral);
|
||||
|
||||
INFO(
|
||||
"Numerical pressure-force integral = " << numericalPressureForceIntegral
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Analytic pressure-force integral = " << analyticPressureForceIntegral
|
||||
);
|
||||
|
||||
CHECK(pressureIntegralRule.resolution.base_order == 12);
|
||||
|
||||
CHECK(pressureIntegralRule.resolution.order == 12);
|
||||
|
||||
CHECK(pressureForceRule.resolution.base_order == 14);
|
||||
|
||||
CHECK(pressureForceRule.resolution.order == 14);
|
||||
|
||||
CHECK_THAT(
|
||||
numericalPressureIntegral,
|
||||
Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
numericalPressureForceIntegral,
|
||||
Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14)
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1502
tests/physics/gravity_monopole_accuracy.cpp
Normal file
1502
tests/physics/gravity_monopole_accuracy.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,11 @@
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
@@ -15,51 +18,104 @@ import test_helpers;
|
||||
using namespace mean_field;
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
concept HasRuntimeSpaceOrders = requires(T value) { value.space_orders; };
|
||||
|
||||
static_assert(mean_field::field::FieldTag<field::Gravity>);
|
||||
static_assert(mean_field::field::FieldTag<field::Displacement>);
|
||||
static_assert(mean_field::field::FieldTag<field::Density>);
|
||||
static_assert(mean_field::field::FieldTag<field::Enthalpy>);
|
||||
static_assert(mean_field::field::FieldTag<field::BarotropicConstant>);
|
||||
static_assert(field::Gravity::constraintsAreValid);
|
||||
static_assert(std::same_as<
|
||||
field::RelationTargetT<field::Gravity::Flux>,
|
||||
field::Gravity::Potential>);
|
||||
static_assert(!HasRuntimeSpaceOrders<utils::Args>);
|
||||
|
||||
std::string_view get_term_name(const quadrature::Term term) {
|
||||
switch (term) {
|
||||
case quadrature::Term::gravity_hdiv_mass: return "gravity_hdiv_mass";
|
||||
case quadrature::Term::gravity_divergence: return "gravity_divergence";
|
||||
case quadrature::Term::gravity_source: return "gravity_source";
|
||||
case quadrature::Term::gravity_boundary: return "gravity_boundary";
|
||||
case quadrature::Term::density_projection: return "density_projection";
|
||||
case quadrature::Term::mass_conservation: return "mass_conservation";
|
||||
case quadrature::Term::center_of_mass: return "center_of_mass";
|
||||
case quadrature::Term::quadrupole: return "quadrupole";
|
||||
case quadrature::Term::gravitational_energy: return "gravitational_energy";
|
||||
case quadrature::Term::virial: return "virial";
|
||||
case quadrature::Term::error_norm: return "error_norm";
|
||||
case quadrature::Term::gravity_hdiv_mass:
|
||||
return "gravity_hdiv_mass";
|
||||
case quadrature::Term::gravity_divergence:
|
||||
return "gravity_divergence";
|
||||
case quadrature::Term::gravity_source:
|
||||
return "gravity_source";
|
||||
case quadrature::Term::gravity_boundary:
|
||||
return "gravity_boundary";
|
||||
case quadrature::Term::centrifugal:
|
||||
return "centrifugal";
|
||||
case quadrature::Term::density_projection:
|
||||
return "density_projection";
|
||||
case quadrature::Term::eos_closure:
|
||||
return "eos_closure";
|
||||
case quadrature::Term::hydrostatic_equilibrium:
|
||||
return "hydrostatic_equilibrium";
|
||||
case quadrature::Term::isobaric_surface:
|
||||
return "isobaric_surface";
|
||||
case quadrature::Term::mesh_extension:
|
||||
return "mesh_extension";
|
||||
case quadrature::Term::mass_conservation:
|
||||
return "mass_conservation";
|
||||
case quadrature::Term::mass_normalization:
|
||||
return "mass_normalization";
|
||||
case quadrature::Term::center_of_mass:
|
||||
return "center_of_mass";
|
||||
case quadrature::Term::quadrupole:
|
||||
return "quadrupole";
|
||||
case quadrature::Term::gravitational_energy:
|
||||
return "gravitational_energy";
|
||||
case quadrature::Term::pressure_integral:
|
||||
return "pressure_integral";
|
||||
case quadrature::Term::virial:
|
||||
return "virial";
|
||||
case quadrature::Term::error_norm:
|
||||
return "error_norm";
|
||||
case quadrature::Term::pressure_force:
|
||||
return "pressure_force";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
quadrature::Query make_query(const quadrature::Term term, const int base_order) {
|
||||
quadrature::Query make_query(
|
||||
const quadrature::Term term,
|
||||
const int base_order
|
||||
) {
|
||||
return {.term = term, .base_order = base_order};
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Quadrature Policy Computes Base Orders", tags::unit & tags::quadrature) {
|
||||
const quadrature::Policy policy(quadrature::make_rule_set(quadrature::Mode::production));
|
||||
template <typename FieldT, typename QuantityT>
|
||||
concept CanMakeFec = requires { FieldT::template make_fec<QuantityT>(3); };
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Quadrature Policy Computes Base Orders",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
const quadrature::Policy policy(
|
||||
quadrature::make_rule_set(quadrature::Mode::production)
|
||||
);
|
||||
|
||||
quadrature::Query generic_query = {
|
||||
.term = quadrature::Term::gravitational_energy,
|
||||
.trial_order = 3,
|
||||
.test_order = 4,
|
||||
.coefficient_order = 2,
|
||||
.term = quadrature::Term::gravitational_energy,
|
||||
.trial_order = 3,
|
||||
.test_order = 4,
|
||||
.coefficient_order = 2,
|
||||
.geometry_weight_order = 5
|
||||
};
|
||||
|
||||
const quadrature::Resolution generic_resolution = policy.resolve(generic_query);
|
||||
const quadrature::Resolution generic_resolution =
|
||||
policy.resolve(generic_query);
|
||||
CHECK(generic_resolution.base_order == 14);
|
||||
CHECK(generic_resolution.boost == 0);
|
||||
CHECK(generic_resolution.order == 14);
|
||||
CHECK_FALSE(generic_resolution.used_fixed_order);
|
||||
|
||||
quadrature::Query divergence_query = {
|
||||
.term = quadrature::Term::gravity_divergence,
|
||||
.trial_order = 3,
|
||||
.test_order = 2,
|
||||
.coefficient_order = 1,
|
||||
.term = quadrature::Term::gravity_divergence,
|
||||
.trial_order = 3,
|
||||
.test_order = 2,
|
||||
.coefficient_order = 1,
|
||||
.geometry_weight_order = 4
|
||||
};
|
||||
|
||||
@@ -69,147 +125,218 @@ TEST_CASE("Quadrature Policy Computes Base Orders", tags::unit & tags::quadratur
|
||||
CHECK(policy.resolve(divergence_query).base_order == 7);
|
||||
|
||||
quadrature::Query explicit_query = {
|
||||
.term = quadrature::Term::gravity_hdiv_mass,
|
||||
.trial_order = 20,
|
||||
.test_order = 20,
|
||||
.coefficient_order = 20,
|
||||
.term = quadrature::Term::gravity_hdiv_mass,
|
||||
.trial_order = 20,
|
||||
.test_order = 20,
|
||||
.coefficient_order = 20,
|
||||
.geometry_weight_order = 20,
|
||||
.base_order = 11
|
||||
.base_order = 11
|
||||
};
|
||||
|
||||
CHECK(policy.resolve(explicit_query).base_order == 11);
|
||||
CHECK(policy.resolve(explicit_query).order == 11);
|
||||
}
|
||||
|
||||
TEST_CASE("Quadrature Policy Composes Global and Term Boosts", tags::unit & tags::quadrature) {
|
||||
quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 3);
|
||||
TEST_CASE(
|
||||
"Quadrature Policy Composes Global and Term Boosts",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
quadrature::RuleSet rule_set =
|
||||
quadrature::make_rule_set(quadrature::Mode::production, 3);
|
||||
rule_set.gravity_hdiv_mass.boost = 5;
|
||||
rule_set.error_norm.boost = 2;
|
||||
rule_set.error_norm.boost = 2;
|
||||
const quadrature::Policy policy(rule_set);
|
||||
|
||||
const quadrature::Resolution mass_resolution = policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 7));
|
||||
const quadrature::Resolution mass_resolution =
|
||||
policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 7));
|
||||
CHECK(mass_resolution.base_order == 7);
|
||||
CHECK(mass_resolution.boost == 8);
|
||||
CHECK(mass_resolution.order == 15);
|
||||
CHECK_FALSE(mass_resolution.used_fixed_order);
|
||||
|
||||
const quadrature::Resolution error_resolution = policy.resolve(make_query(quadrature::Term::error_norm, 7));
|
||||
const quadrature::Resolution error_resolution =
|
||||
policy.resolve(make_query(quadrature::Term::error_norm, 7));
|
||||
CHECK(error_resolution.boost == 5);
|
||||
CHECK(error_resolution.order == 12);
|
||||
|
||||
const quadrature::Resolution source_resolution = policy.resolve(make_query(quadrature::Term::gravity_source, 7));
|
||||
const quadrature::Resolution source_resolution =
|
||||
policy.resolve(make_query(quadrature::Term::gravity_source, 7));
|
||||
CHECK(source_resolution.boost == 3);
|
||||
CHECK(source_resolution.order == 10);
|
||||
}
|
||||
|
||||
TEST_CASE("Quadrature Fixed Orders Have Defined Precedence", tags::unit & tags::quadrature) {
|
||||
quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 4);
|
||||
rule_set.fallback.fixed_order = 17;
|
||||
TEST_CASE(
|
||||
"Quadrature Fixed Orders Have Defined Precedence",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
quadrature::RuleSet rule_set =
|
||||
quadrature::make_rule_set(quadrature::Mode::production, 4);
|
||||
rule_set.fallback.fixed_order = 17;
|
||||
rule_set.gravity_hdiv_mass.fixed_order = 23;
|
||||
rule_set.gravity_hdiv_mass.boost = 100;
|
||||
rule_set.gravity_source.boost = 100;
|
||||
rule_set.gravity_hdiv_mass.boost = 100;
|
||||
rule_set.gravity_source.boost = 100;
|
||||
const quadrature::Policy policy(rule_set);
|
||||
|
||||
const quadrature::Resolution term_resolution = policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 8));
|
||||
const quadrature::Resolution term_resolution =
|
||||
policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 8));
|
||||
CHECK(term_resolution.base_order == 8);
|
||||
CHECK(term_resolution.boost == 0);
|
||||
CHECK(term_resolution.order == 23);
|
||||
CHECK(term_resolution.used_fixed_order);
|
||||
|
||||
const quadrature::Resolution fallback_resolution = policy.resolve(make_query(quadrature::Term::gravity_source, 8));
|
||||
const quadrature::Resolution fallback_resolution =
|
||||
policy.resolve(make_query(quadrature::Term::gravity_source, 8));
|
||||
CHECK(fallback_resolution.base_order == 8);
|
||||
CHECK(fallback_resolution.boost == 0);
|
||||
CHECK(fallback_resolution.order == 17);
|
||||
CHECK(fallback_resolution.used_fixed_order);
|
||||
}
|
||||
|
||||
TEST_CASE("Quadrature Modes Apply Their Expected Baseline Boosts", tags::unit & tags::quadrature) {
|
||||
constexpr int base_order = 6;
|
||||
TEST_CASE(
|
||||
"Quadrature Modes Apply Their Expected Baseline Boosts",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
constexpr int base_order = 6;
|
||||
constexpr int global_boost = 3;
|
||||
|
||||
for (const quadrature::Mode mode : {quadrature::Mode::fast, quadrature::Mode::production, quadrature::Mode::convergence}) {
|
||||
const quadrature::Policy policy(quadrature::make_rule_set(mode, global_boost));
|
||||
const quadrature::Resolution resolution = policy.resolve(make_query(quadrature::Term::error_norm, base_order));
|
||||
for (const quadrature::Mode mode :
|
||||
{quadrature::Mode::fast, quadrature::Mode::production,
|
||||
quadrature::Mode::convergence}) {
|
||||
const quadrature::Policy policy(
|
||||
quadrature::make_rule_set(mode, global_boost)
|
||||
);
|
||||
const quadrature::Resolution resolution = policy.resolve(
|
||||
make_query(quadrature::Term::error_norm, base_order)
|
||||
);
|
||||
CHECK(resolution.boost == global_boost);
|
||||
CHECK(resolution.order == base_order + global_boost);
|
||||
}
|
||||
|
||||
const quadrature::Policy reference_policy(quadrature::make_rule_set(quadrature::Mode::reference, global_boost));
|
||||
const quadrature::Resolution reference_resolution = reference_policy.resolve(make_query(quadrature::Term::error_norm, base_order));
|
||||
const quadrature::Policy reference_policy(
|
||||
quadrature::make_rule_set(quadrature::Mode::reference, global_boost)
|
||||
);
|
||||
const quadrature::Resolution reference_resolution =
|
||||
reference_policy.resolve(
|
||||
make_query(quadrature::Term::error_norm, base_order)
|
||||
);
|
||||
CHECK(reference_resolution.boost == global_boost + 8);
|
||||
CHECK(reference_resolution.order == base_order + global_boost + 8);
|
||||
}
|
||||
|
||||
TEST_CASE("Quadrature Policy Routes Every Term to Its Control", tags::unit & tags::quadrature) {
|
||||
TEST_CASE(
|
||||
"Quadrature Policy Routes Every Term to Its Control",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
quadrature::RuleSet rule_set;
|
||||
rule_set.gravity_hdiv_mass.boost = 1;
|
||||
rule_set.gravity_divergence.boost = 2;
|
||||
rule_set.gravity_source.boost = 3;
|
||||
rule_set.gravity_boundary.boost = 4;
|
||||
rule_set.density_projection.boost = 5;
|
||||
rule_set.mass_conservation.boost = 6;
|
||||
rule_set.center_of_mass.boost = 7;
|
||||
rule_set.quadrupole.boost = 8;
|
||||
rule_set.gravitational_energy.boost = 9;
|
||||
rule_set.virial.boost = 10;
|
||||
rule_set.error_norm.boost = 11;
|
||||
rule_set.gravity_hdiv_mass.boost = 1;
|
||||
rule_set.gravity_divergence.boost = 2;
|
||||
rule_set.gravity_source.boost = 3;
|
||||
rule_set.gravity_boundary.boost = 4;
|
||||
rule_set.centrifugal.boost = 18;
|
||||
rule_set.density_projection.boost = 5;
|
||||
rule_set.eos_closure.boost = 6;
|
||||
rule_set.hydrostatic_equilibrium.boost = 7;
|
||||
rule_set.isobaric_surface.boost = 8;
|
||||
rule_set.mesh_extension.boost = 9;
|
||||
rule_set.mass_conservation.boost = 10;
|
||||
rule_set.mass_normalization.boost = 11;
|
||||
rule_set.center_of_mass.boost = 12;
|
||||
rule_set.quadrupole.boost = 13;
|
||||
rule_set.gravitational_energy.boost = 14;
|
||||
rule_set.pressure_integral.boost = 15;
|
||||
rule_set.virial.boost = 16;
|
||||
rule_set.error_norm.boost = 17;
|
||||
const quadrature::Policy policy(rule_set);
|
||||
|
||||
const std::array<std::pair<quadrature::Term, int>, 11> cases = {{
|
||||
{quadrature::Term::gravity_hdiv_mass, 1},
|
||||
{quadrature::Term::gravity_divergence, 2},
|
||||
{quadrature::Term::gravity_source, 3},
|
||||
{quadrature::Term::gravity_boundary, 4},
|
||||
{quadrature::Term::density_projection, 5},
|
||||
{quadrature::Term::mass_conservation, 6},
|
||||
{quadrature::Term::center_of_mass, 7},
|
||||
{quadrature::Term::quadrupole, 8},
|
||||
{quadrature::Term::gravitational_energy, 9},
|
||||
{quadrature::Term::virial, 10},
|
||||
{quadrature::Term::error_norm, 11}
|
||||
}};
|
||||
const std::array<std::pair<quadrature::Term, int>, 18> cases = {
|
||||
{{quadrature::Term::gravity_hdiv_mass, 1},
|
||||
{quadrature::Term::gravity_divergence, 2},
|
||||
{quadrature::Term::gravity_source, 3},
|
||||
{quadrature::Term::gravity_boundary, 4},
|
||||
{quadrature::Term::centrifugal, 18},
|
||||
{quadrature::Term::density_projection, 5},
|
||||
{quadrature::Term::eos_closure, 6},
|
||||
{quadrature::Term::hydrostatic_equilibrium, 7},
|
||||
{quadrature::Term::isobaric_surface, 8},
|
||||
{quadrature::Term::mesh_extension, 9},
|
||||
{quadrature::Term::mass_conservation, 10},
|
||||
{quadrature::Term::mass_normalization, 11},
|
||||
{quadrature::Term::center_of_mass, 12},
|
||||
{quadrature::Term::quadrupole, 13},
|
||||
{quadrature::Term::gravitational_energy, 14},
|
||||
{quadrature::Term::pressure_integral, 15},
|
||||
{quadrature::Term::virial, 16},
|
||||
{quadrature::Term::error_norm, 17}}
|
||||
};
|
||||
|
||||
for (const auto& [term, expected_boost] : cases) {
|
||||
for (const auto &[term, expected_boost] : cases) {
|
||||
DYNAMIC_SECTION(get_term_name(term)) {
|
||||
const quadrature::Resolution resolution = policy.resolve(make_query(term, 20));
|
||||
const quadrature::Resolution resolution =
|
||||
policy.resolve(make_query(term, 20));
|
||||
CHECK(resolution.boost == expected_boost);
|
||||
CHECK(resolution.order == 20 + expected_boost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Quadrature Policy Rejects Invalid Orders", tags::unit & tags::quadrature) {
|
||||
const quadrature::Policy policy(quadrature::make_rule_set(quadrature::Mode::production));
|
||||
TEST_CASE(
|
||||
"Quadrature Policy Rejects Invalid Orders",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
const quadrature::Policy policy(
|
||||
quadrature::make_rule_set(quadrature::Mode::production)
|
||||
);
|
||||
|
||||
quadrature::Query negative_component_query = {
|
||||
.term = quadrature::Term::error_norm,
|
||||
.trial_order = -1
|
||||
.term = quadrature::Term::error_norm, .trial_order = -1
|
||||
};
|
||||
REQUIRE_THROWS_AS(policy.resolve(negative_component_query), std::invalid_argument);
|
||||
REQUIRE_THROWS_AS(
|
||||
policy.resolve(negative_component_query), std::invalid_argument
|
||||
);
|
||||
|
||||
quadrature::Query negative_base_query = {
|
||||
.term = quadrature::Term::error_norm,
|
||||
.base_order = -1
|
||||
.term = quadrature::Term::error_norm, .base_order = -1
|
||||
};
|
||||
REQUIRE_THROWS_AS(policy.resolve(negative_base_query), std::invalid_argument);
|
||||
REQUIRE_THROWS_AS(
|
||||
policy.resolve(negative_base_query), std::invalid_argument
|
||||
);
|
||||
|
||||
quadrature::RuleSet negative_fixed_rule_set;
|
||||
negative_fixed_rule_set.error_norm.fixed_order = -1;
|
||||
const quadrature::Policy negative_fixed_policy(negative_fixed_rule_set);
|
||||
REQUIRE_THROWS_AS(negative_fixed_policy.resolve(make_query(quadrature::Term::error_norm, 3)), std::invalid_argument);
|
||||
REQUIRE_THROWS_AS(
|
||||
negative_fixed_policy.resolve(
|
||||
make_query(quadrature::Term::error_norm, 3)
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
quadrature::RuleSet negative_resolved_rule_set;
|
||||
negative_resolved_rule_set.fallback.boost = -4;
|
||||
const quadrature::Policy negative_resolved_policy(negative_resolved_rule_set);
|
||||
REQUIRE_THROWS_AS(negative_resolved_policy.resolve(make_query(quadrature::Term::error_norm, 3)), std::invalid_argument);
|
||||
const quadrature::Policy negative_resolved_policy(
|
||||
negative_resolved_rule_set
|
||||
);
|
||||
REQUIRE_THROWS_AS(
|
||||
negative_resolved_policy.resolve(
|
||||
make_query(quadrature::Term::error_norm, 3)
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE("MFEM Rule Factory Returns the Resolved Rule", tags::unit & tags::quadrature) {
|
||||
quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 2);
|
||||
TEST_CASE(
|
||||
"MFEM Rule Factory Returns the Resolved Rule",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
quadrature::RuleSet rule_set =
|
||||
quadrature::make_rule_set(quadrature::Mode::production, 2);
|
||||
rule_set.error_norm.boost = 3;
|
||||
const quadrature::RuleFactory factory{quadrature::Policy(rule_set)};
|
||||
const quadrature::MfemRule selected_rule = factory.get(make_query(quadrature::Term::error_norm, 4), mfem::Geometry::CUBE);
|
||||
const mfem::IntegrationRule& expected_rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 9);
|
||||
const quadrature::MfemRule selected_rule = factory.get(
|
||||
make_query(quadrature::Term::error_norm, 4), mfem::Geometry::CUBE
|
||||
);
|
||||
const mfem::IntegrationRule &expected_rule =
|
||||
mfem::IntRules.Get(mfem::Geometry::CUBE, 9);
|
||||
|
||||
REQUIRE(selected_rule.integration_rule != nullptr);
|
||||
CHECK(selected_rule.resolution.base_order == 4);
|
||||
@@ -219,47 +346,85 @@ TEST_CASE("MFEM Rule Factory Returns the Resolved Rule", tags::unit & tags::quad
|
||||
CHECK(selected_rule.integration_rule->GetNPoints() > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("MFEM Quadrature Rule Integrates Tensor Polynomial Exactly", tags::unit & tags::quadrature) {
|
||||
TEST_CASE(
|
||||
"MFEM Quadrature Rule Integrates Tensor Polynomial Exactly",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
constexpr int polynomial_degree = 7;
|
||||
const quadrature::RuleFactory factory{quadrature::Policy(quadrature::make_rule_set(quadrature::Mode::production))};
|
||||
const quadrature::MfemRule selected_rule = factory.get(make_query(quadrature::Term::error_norm, polynomial_degree), mfem::Geometry::CUBE);
|
||||
const quadrature::RuleFactory factory{quadrature::Policy(
|
||||
quadrature::make_rule_set(quadrature::Mode::production)
|
||||
)};
|
||||
const quadrature::MfemRule selected_rule = factory.get(
|
||||
make_query(quadrature::Term::error_norm, polynomial_degree),
|
||||
mfem::Geometry::CUBE
|
||||
);
|
||||
double numerical_integral = 0.0;
|
||||
|
||||
for (int i = 0; i < selected_rule.integration_rule->GetNPoints(); ++i) {
|
||||
const mfem::IntegrationPoint& integration_point = selected_rule.integration_rule->IntPoint(i);
|
||||
numerical_integral += integration_point.weight * std::pow(integration_point.x, polynomial_degree) * std::pow(integration_point.y, polynomial_degree) * std::pow(integration_point.z, polynomial_degree);
|
||||
const mfem::IntegrationPoint &integration_point =
|
||||
selected_rule.integration_rule->IntPoint(i);
|
||||
numerical_integral += integration_point.weight *
|
||||
std::pow(integration_point.x, polynomial_degree) *
|
||||
std::pow(integration_point.y, polynomial_degree) *
|
||||
std::pow(integration_point.z, polynomial_degree);
|
||||
}
|
||||
|
||||
const double one_dimensional_integral = 1.0 / static_cast<double>(polynomial_degree + 1);
|
||||
const double analytic_integral = one_dimensional_integral * one_dimensional_integral * one_dimensional_integral;
|
||||
CHECK_THAT(numerical_integral, Catch::Matchers::WithinAbs(analytic_integral, 5.0e-14));
|
||||
const double one_dimensional_integral =
|
||||
1.0 / static_cast<double>(polynomial_degree + 1);
|
||||
const double analytic_integral = one_dimensional_integral *
|
||||
one_dimensional_integral *
|
||||
one_dimensional_integral;
|
||||
CHECK_THAT(
|
||||
numerical_integral,
|
||||
Catch::Matchers::WithinAbs(analytic_integral, 5.0e-14)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE("Policy Controlled Hdiv Mass Assembly Matches Overintegrated Reference", tags::quadrature & tags::solver & tags::integration) {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0);
|
||||
TEST_CASE(
|
||||
"Policy Controlled Hdiv Mass Assembly Matches Overintegrated Reference",
|
||||
tags::quadrature &tags::solver &tags::integration
|
||||
) {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(
|
||||
1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0
|
||||
);
|
||||
mfem::RT_FECollection rt_collection(2, 3);
|
||||
mfem::FiniteElementSpace rt_space(&mesh, &rt_collection);
|
||||
const mfem::FiniteElement* rt_element = rt_space.GetTypicalFE();
|
||||
mfem::ElementTransformation* transformation = mesh.GetElementTransformation(0);
|
||||
const int base_order = 2 * rt_element->GetOrder() + transformation->OrderW();
|
||||
const mfem::FiniteElement *rt_element = rt_space.GetTypicalFE();
|
||||
mfem::ElementTransformation *transformation =
|
||||
mesh.GetElementTransformation(0);
|
||||
const int base_order =
|
||||
2 * rt_element->GetOrder() + transformation->OrderW();
|
||||
|
||||
const quadrature::RuleFactory production_factory{quadrature::Policy(quadrature::make_rule_set(quadrature::Mode::production))};
|
||||
const quadrature::MfemRule production_rule = production_factory.get(make_query(quadrature::Term::gravity_hdiv_mass, base_order), rt_element->GetGeomType());
|
||||
const quadrature::RuleFactory production_factory{quadrature::Policy(
|
||||
quadrature::make_rule_set(quadrature::Mode::production)
|
||||
)};
|
||||
const quadrature::MfemRule production_rule = production_factory.get(
|
||||
make_query(quadrature::Term::gravity_hdiv_mass, base_order),
|
||||
rt_element->GetGeomType()
|
||||
);
|
||||
|
||||
quadrature::RuleSet reference_rule_set = quadrature::make_rule_set(quadrature::Mode::production);
|
||||
quadrature::RuleSet reference_rule_set =
|
||||
quadrature::make_rule_set(quadrature::Mode::production);
|
||||
reference_rule_set.gravity_hdiv_mass.boost = 8;
|
||||
const quadrature::RuleFactory reference_factory{quadrature::Policy(reference_rule_set)};
|
||||
const quadrature::MfemRule reference_rule = reference_factory.get(make_query(quadrature::Term::gravity_hdiv_mass, base_order), rt_element->GetGeomType());
|
||||
const quadrature::RuleFactory reference_factory{
|
||||
quadrature::Policy(reference_rule_set)
|
||||
};
|
||||
const quadrature::MfemRule reference_rule = reference_factory.get(
|
||||
make_query(quadrature::Term::gravity_hdiv_mass, base_order),
|
||||
rt_element->GetGeomType()
|
||||
);
|
||||
|
||||
mfem::BilinearForm production_mass(&rt_space);
|
||||
auto* production_integrator = new mfem::VectorFEMassIntegrator();
|
||||
production_integrator->SetIntegrationRule(*production_rule.integration_rule);
|
||||
auto *production_integrator = new mfem::VectorFEMassIntegrator();
|
||||
production_integrator->SetIntegrationRule(
|
||||
*production_rule.integration_rule
|
||||
);
|
||||
production_mass.AddDomainIntegrator(production_integrator);
|
||||
production_mass.Assemble();
|
||||
production_mass.Finalize();
|
||||
|
||||
mfem::BilinearForm reference_mass(&rt_space);
|
||||
auto* reference_integrator = new mfem::VectorFEMassIntegrator();
|
||||
auto *reference_integrator = new mfem::VectorFEMassIntegrator();
|
||||
reference_integrator->SetIntegrationRule(*reference_rule.integration_rule);
|
||||
reference_mass.AddDomainIntegrator(reference_integrator);
|
||||
reference_mass.Assemble();
|
||||
@@ -277,55 +442,417 @@ TEST_CASE("Policy Controlled Hdiv Mass Assembly Matches Overintegrated Reference
|
||||
|
||||
mfem::Vector difference(production_output);
|
||||
difference -= reference_output;
|
||||
const double relative_difference = difference.Norml2() / reference_output.Norml2();
|
||||
const double relative_difference =
|
||||
difference.Norml2() / reference_output.Norml2();
|
||||
INFO("Production quadrature order = " << production_rule.resolution.order);
|
||||
INFO("Reference quadrature order = " << reference_rule.resolution.order);
|
||||
INFO("Relative operator difference = " << relative_difference);
|
||||
CHECK_THAT(relative_difference, Catch::Matchers::WithinAbs(0.0, 1.0e-12));
|
||||
}
|
||||
|
||||
TEST_CASE("HDiv Mass Helper Resolves the MFEM Baseline", tags::unit & tags::quadrature & tags::solver) {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON);
|
||||
TEST_CASE(
|
||||
"HDiv Mass Helper Resolves the MFEM Baseline",
|
||||
tags::unit &tags::quadrature &tags::solver
|
||||
) {
|
||||
mfem::Mesh mesh =
|
||||
mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON);
|
||||
mfem::RT_FECollection rt_collection(2, 3);
|
||||
mfem::FiniteElementSpace rt_space(&mesh, &rt_collection);
|
||||
|
||||
quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production);
|
||||
quadrature::RuleSet rule_set =
|
||||
quadrature::make_rule_set(quadrature::Mode::production);
|
||||
rule_set.gravity_hdiv_mass.boost = 3;
|
||||
quadrature::RuleFactory factory{quadrature::Policy(std::move(rule_set))};
|
||||
|
||||
const mfem::FiniteElement& element = *rt_space.GetTypicalFE();
|
||||
const mfem::ElementTransformation& transformation = *mesh.GetElementTransformation(0);
|
||||
const mfem::FiniteElement &element = *rt_space.GetTypicalFE();
|
||||
const mfem::ElementTransformation &transformation =
|
||||
*mesh.GetElementTransformation(0);
|
||||
mfem::VectorFEMassIntegrator integrator;
|
||||
|
||||
const quadrature::Resolution resolution = factory.configure_gravity_hdiv_mass(integrator, quadrature::QuadratureRole::discretization, element, transformation);
|
||||
const int expected_base_order = 2 * element.GetOrder() + transformation.OrderW();
|
||||
const quadrature::Resolution resolution =
|
||||
factory.configure_gravity_hdiv_mass(
|
||||
integrator, quadrature::QuadratureRole::discretization, element,
|
||||
transformation
|
||||
);
|
||||
const int expected_base_order =
|
||||
2 * element.GetOrder() + transformation.OrderW();
|
||||
|
||||
CHECK(resolution.base_order == expected_base_order);
|
||||
CHECK(resolution.boost == 3);
|
||||
CHECK(resolution.order == expected_base_order + 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Gravity Divergence Helper Resolves Preconditioner Rule", tags::unit & tags::quadrature & tags::solver) {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON);
|
||||
TEST_CASE(
|
||||
"Gravity Divergence Helper Resolves Preconditioner Rule",
|
||||
tags::unit &tags::quadrature &tags::solver
|
||||
) {
|
||||
mfem::Mesh mesh =
|
||||
mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON);
|
||||
mfem::RT_FECollection rt_collection(2, 3);
|
||||
mfem::L2_FECollection l2_collection(2, 3);
|
||||
mfem::FiniteElementSpace rt_space(&mesh, &rt_collection);
|
||||
mfem::FiniteElementSpace l2_space(&mesh, &l2_collection);
|
||||
|
||||
quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production);
|
||||
rule_set.gravity_divergence.boost = 2;
|
||||
quadrature::RuleSet rule_set =
|
||||
quadrature::make_rule_set(quadrature::Mode::production);
|
||||
rule_set.gravity_divergence.boost = 2;
|
||||
rule_set.roles.preconditioner.boost = 3;
|
||||
quadrature::RuleFactory factory{quadrature::Policy(std::move(rule_set))};
|
||||
|
||||
const mfem::FiniteElement& trial_element = *rt_space.GetTypicalFE();
|
||||
const mfem::FiniteElement& test_element = *l2_space.GetTypicalFE();
|
||||
const mfem::ElementTransformation& transformation = *mesh.GetElementTransformation(0);
|
||||
const mfem::FiniteElement &trial_element = *rt_space.GetTypicalFE();
|
||||
const mfem::FiniteElement &test_element = *l2_space.GetTypicalFE();
|
||||
const mfem::ElementTransformation &transformation =
|
||||
*mesh.GetElementTransformation(0);
|
||||
mfem::VectorFEDivergenceIntegrator integrator;
|
||||
|
||||
const quadrature::Resolution resolution = factory.configure_gravity_divergence(integrator, quadrature::QuadratureRole::preconditioner, trial_element, test_element, transformation);
|
||||
const int expected_base_order = std::max(0, trial_element.GetOrder() - 1) + test_element.GetOrder() + transformation.OrderW();
|
||||
const quadrature::Resolution resolution =
|
||||
factory.configure_gravity_divergence(
|
||||
integrator, quadrature::QuadratureRole::preconditioner,
|
||||
trial_element, test_element, transformation
|
||||
);
|
||||
const int expected_base_order = std::max(0, trial_element.GetOrder() - 1) +
|
||||
test_element.GetOrder() +
|
||||
transformation.OrderW();
|
||||
|
||||
CHECK(resolution.base_order == expected_base_order);
|
||||
CHECK(resolution.boost == 5);
|
||||
CHECK(resolution.order == expected_base_order + 5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Field Registry Encodes Spaces Orders And Stable Pair Constraints",
|
||||
tags::unit &tags::quadrature &tags::initialization
|
||||
) {
|
||||
CHECK(mean_field::field::Density::Scalar::familyOrder == 2);
|
||||
CHECK(mean_field::field::Gravity::Potential::familyOrder == 2);
|
||||
CHECK(mean_field::field::Gravity::Flux::familyOrder == 2);
|
||||
CHECK(mean_field::field::Displacement::Vector::familyOrder == 3);
|
||||
CHECK(mean_field::field::Enthalpy::Scalar::familyOrder == 3);
|
||||
|
||||
CHECK((std::same_as<
|
||||
mean_field::field::Density::Scalar::Space, mean_field::field::L2>));
|
||||
CHECK((
|
||||
std::same_as<
|
||||
mean_field::field::Gravity::Potential::Space, mean_field::field::L2>
|
||||
));
|
||||
CHECK((std::same_as<
|
||||
mean_field::field::Gravity::Flux::Space, mean_field::field::RT>));
|
||||
CHECK((std::same_as<
|
||||
mean_field::field::Displacement::Vector::Space,
|
||||
mean_field::field::H1>));
|
||||
CHECK((std::same_as<
|
||||
mean_field::field::Enthalpy::Scalar::Space, mean_field::field::H1>));
|
||||
|
||||
CHECK(mean_field::field::Density::Scalar::rankValue == 0);
|
||||
CHECK(mean_field::field::Gravity::Potential::rankValue == 0);
|
||||
CHECK(mean_field::field::Gravity::Flux::rankValue == 1);
|
||||
CHECK(mean_field::field::Displacement::Vector::rankValue == 1);
|
||||
CHECK(mean_field::field::Enthalpy::Scalar::rankValue == 0);
|
||||
CHECK(
|
||||
mean_field::field::Gravity::Flux::familyOrder ==
|
||||
mean_field::field::Gravity::Potential::familyOrder
|
||||
);
|
||||
|
||||
CHECK(
|
||||
std::is_empty_v<mean_field::field::Field<mean_field::field::Gravity>>
|
||||
);
|
||||
CHECK(
|
||||
std::is_empty_v<
|
||||
mean_field::field::Field<mean_field::field::Displacement>>
|
||||
);
|
||||
CHECK(
|
||||
std::is_empty_v<mean_field::field::Field<mean_field::field::Density>>
|
||||
);
|
||||
CHECK(
|
||||
std::is_empty_v<mean_field::field::Field<mean_field::field::Enthalpy>>
|
||||
);
|
||||
|
||||
STATIC_CHECK(field::RegisteredQuantity<field::BarotropicConstant::Scalar>);
|
||||
STATIC_CHECK(
|
||||
field::GlobalScalarQuantity<field::BarotropicConstant::Scalar>
|
||||
);
|
||||
STATIC_CHECK_FALSE(field::FieldQuantity<field::BarotropicConstant::Scalar>);
|
||||
|
||||
STATIC_CHECK(
|
||||
field::BarotropicConstant::Scalar::storageKind ==
|
||||
field::StorageKind::global_scalar
|
||||
);
|
||||
STATIC_CHECK(field::BarotropicConstant::Scalar::staticBlockSize == 1);
|
||||
|
||||
STATIC_CHECK(
|
||||
field::Enthalpy::Scalar::storageKind ==
|
||||
field::StorageKind::finite_element
|
||||
);
|
||||
STATIC_CHECK(
|
||||
field::Enthalpy::Scalar::staticBlockSize == field::dynamicBlockSize
|
||||
);
|
||||
|
||||
STATIC_CHECK_FALSE(
|
||||
CanMakeFec<
|
||||
field::Field<field::BarotropicConstant>,
|
||||
field::BarotropicConstant::Scalar>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Field Forms Produce Typed Quadrature Queries",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
using GravityField = field::Field<field::Gravity>;
|
||||
using DensityField = field::Field<field::Density>;
|
||||
using EnthalpyField = field::Field<field::Enthalpy>;
|
||||
|
||||
constexpr quadrature::Query hdiv_query =
|
||||
GravityField::make_query<field::Gravity::Form::HDivMass>(
|
||||
quadrature::QuadratureRole::discretization, 2, {},
|
||||
utils::DOMAINS::ALL, quadrature::MappingKind::general
|
||||
);
|
||||
constexpr quadrature::Query divergence_query =
|
||||
GravityField::make_query<field::Gravity::Form::DivergenceCoupling>(
|
||||
quadrature::QuadratureRole::preconditioner, 2
|
||||
);
|
||||
constexpr quadrature::Query source_query =
|
||||
GravityField::make_query<field::Gravity::Form::SourceProjection>(
|
||||
quadrature::QuadratureRole::projection, 2, {},
|
||||
utils::DOMAINS::STELLAR
|
||||
);
|
||||
constexpr quadrature::Query center_of_mass_query =
|
||||
DensityField::make_query<field::Density::Form::CenterOfMass>(
|
||||
quadrature::QuadratureRole::diagnostic, 2, std::array<int, 1>{1},
|
||||
utils::DOMAINS::STELLAR
|
||||
);
|
||||
constexpr quadrature::Query eos_closure_query =
|
||||
EnthalpyField::make_query<field::Enthalpy::Form::EosClosureSource>(
|
||||
quadrature::QuadratureRole::discretization, 2,
|
||||
std::array<int, 1>{6}, utils::DOMAINS::STELLAR,
|
||||
quadrature::MappingKind::general
|
||||
);
|
||||
constexpr quadrature::Query equilibrium_gravity_query =
|
||||
EnthalpyField::make_query<field::Enthalpy::Form::EquilibriumGravity>(
|
||||
quadrature::QuadratureRole::discretization, 2, {},
|
||||
utils::DOMAINS::STELLAR, quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
constexpr quadrature::Query equilibrium_constant_query =
|
||||
EnthalpyField::make_query<field::Enthalpy::Form::EquilibriumConstant>(
|
||||
quadrature::QuadratureRole::discretization, 2, {},
|
||||
utils::DOMAINS::STELLAR, quadrature::MappingKind::general
|
||||
);
|
||||
constexpr quadrature::Query rotation_query =
|
||||
EnthalpyField::make_query<field::Enthalpy::Form::EquilibriumRotation>(
|
||||
quadrature::QuadratureRole::discretization, 2,
|
||||
std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
|
||||
quadrature::MappingKind::general
|
||||
);
|
||||
constexpr quadrature::Query isobaric_surface_query =
|
||||
EnthalpyField::make_query<field::Enthalpy::Form::IsobaricSurface>(
|
||||
quadrature::QuadratureRole::discretization, 2, {},
|
||||
utils::DOMAINS::STELLAR, quadrature::MappingKind::general
|
||||
);
|
||||
constexpr quadrature::Query mesh_extension_query =
|
||||
field::Field<field::Displacement>::make_query<
|
||||
field::Displacement::Form::MeshExtension>(
|
||||
quadrature::QuadratureRole::discretization, 2, {},
|
||||
utils::DOMAINS::ALL, quadrature::MappingKind::general
|
||||
);
|
||||
constexpr quadrature::Query mass_normalization_query =
|
||||
DensityField::make_query<field::Density::Form::MassNormalization>(
|
||||
quadrature::QuadratureRole::discretization, 2, {},
|
||||
utils::DOMAINS::STELLAR, quadrature::MappingKind::general
|
||||
);
|
||||
constexpr quadrature::Query pressure_integral_query =
|
||||
EnthalpyField::make_query<field::Enthalpy::Form::PressureIntegral>(
|
||||
quadrature::QuadratureRole::diagnostic, 2, std::array<int, 1>{9},
|
||||
utils::DOMAINS::STELLAR, quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
STATIC_CHECK(
|
||||
mean_field::field::Gravity::Form::SourceProjection::dynamicOrderCount ==
|
||||
0
|
||||
);
|
||||
STATIC_CHECK(hdiv_query.base_order.has_value());
|
||||
STATIC_CHECK(*hdiv_query.base_order == 8);
|
||||
STATIC_CHECK(*divergence_query.base_order == 6);
|
||||
STATIC_CHECK(*source_query.base_order == 6);
|
||||
STATIC_CHECK(*center_of_mass_query.base_order == 5);
|
||||
STATIC_CHECK(*eos_closure_query.base_order == 13);
|
||||
STATIC_CHECK(*equilibrium_gravity_query.base_order == 7);
|
||||
STATIC_CHECK(*rotation_query.base_order == 7);
|
||||
STATIC_CHECK(*isobaric_surface_query.base_order == 8);
|
||||
STATIC_CHECK(*mesh_extension_query.base_order == 6);
|
||||
STATIC_CHECK(*mass_normalization_query.base_order == 4);
|
||||
STATIC_CHECK(*pressure_integral_query.base_order == 14);
|
||||
STATIC_CHECK(*equilibrium_constant_query.base_order == 5);
|
||||
|
||||
CHECK(
|
||||
equilibrium_constant_query.term ==
|
||||
quadrature::Term::hydrostatic_equilibrium
|
||||
);
|
||||
CHECK(hdiv_query.term == mean_field::quadrature::Term::gravity_hdiv_mass);
|
||||
CHECK(
|
||||
hdiv_query.role ==
|
||||
mean_field::quadrature::QuadratureRole::discretization
|
||||
);
|
||||
CHECK(hdiv_query.domain == mean_field::utils::DOMAINS::ALL);
|
||||
CHECK(hdiv_query.mapping == mean_field::quadrature::MappingKind::general);
|
||||
CHECK(source_query.term == mean_field::quadrature::Term::gravity_source);
|
||||
CHECK(
|
||||
center_of_mass_query.term ==
|
||||
mean_field::quadrature::Term::center_of_mass
|
||||
);
|
||||
CHECK(eos_closure_query.term == mean_field::quadrature::Term::eos_closure);
|
||||
CHECK(
|
||||
equilibrium_gravity_query.term ==
|
||||
mean_field::quadrature::Term::hydrostatic_equilibrium
|
||||
);
|
||||
CHECK(
|
||||
rotation_query.term ==
|
||||
mean_field::quadrature::Term::hydrostatic_equilibrium
|
||||
);
|
||||
CHECK(
|
||||
isobaric_surface_query.term ==
|
||||
mean_field::quadrature::Term::isobaric_surface
|
||||
);
|
||||
CHECK(
|
||||
mesh_extension_query.term ==
|
||||
mean_field::quadrature::Term::mesh_extension
|
||||
);
|
||||
CHECK(
|
||||
mass_normalization_query.term ==
|
||||
mean_field::quadrature::Term::mass_normalization
|
||||
);
|
||||
CHECK(
|
||||
pressure_integral_query.term ==
|
||||
mean_field::quadrature::Term::pressure_integral
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
(GravityField::make_query<mean_field::field::Gravity::Form::HDivMass>(
|
||||
mean_field::quadrature::QuadratureRole::discretization, -1
|
||||
)),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(DensityField::make_query<
|
||||
mean_field::field::Density::Form::CenterOfMass>(
|
||||
mean_field::quadrature::QuadratureRole::diagnostic, 2,
|
||||
std::array<int, 1>{-1}
|
||||
)),
|
||||
std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Field Factories Construct The Registered MFEM Collections",
|
||||
tags::unit &tags::quadrature &tags::initialization
|
||||
) {
|
||||
using GravityField = field::Field<field::Gravity>;
|
||||
using DisplacementField = field::Field<field::Displacement>;
|
||||
using DensityField = field::Field<field::Density>;
|
||||
using EnthalpyField = field::Field<field::Enthalpy>;
|
||||
|
||||
std::unique_ptr<mfem::FiniteElementCollection> density_collection =
|
||||
DensityField::make_fec<field::Density::Scalar>(3);
|
||||
std::unique_ptr<mfem::FiniteElementCollection> potential_collection =
|
||||
GravityField::make_fec<field::Gravity::Potential>(3);
|
||||
std::unique_ptr<mfem::FiniteElementCollection> flux_collection =
|
||||
GravityField::make_fec<field::Gravity::Flux>(3);
|
||||
std::unique_ptr<mfem::FiniteElementCollection> displacement_collection =
|
||||
DisplacementField::make_fec<field::Displacement::Vector>(3);
|
||||
std::unique_ptr<mfem::FiniteElementCollection> enthalpy_collection =
|
||||
EnthalpyField::make_fec<field::Enthalpy::Scalar>(3);
|
||||
|
||||
CHECK(
|
||||
dynamic_cast<mfem::L2_FECollection *>(density_collection.get()) !=
|
||||
nullptr
|
||||
);
|
||||
CHECK(
|
||||
dynamic_cast<mfem::L2_FECollection *>(potential_collection.get()) !=
|
||||
nullptr
|
||||
);
|
||||
CHECK(
|
||||
dynamic_cast<mfem::RT_FECollection *>(flux_collection.get()) != nullptr
|
||||
);
|
||||
CHECK(
|
||||
dynamic_cast<mfem::H1_FECollection *>(displacement_collection.get()) !=
|
||||
nullptr
|
||||
);
|
||||
CHECK(
|
||||
dynamic_cast<mfem::H1_FECollection *>(enthalpy_collection.get()) !=
|
||||
nullptr
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
(DensityField::make_fec<mean_field::field::Density::Scalar>(0)),
|
||||
std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"FEM Setup Realizes Every Registered Field Independently",
|
||||
tags::integration &tags::initialization &tags::solver
|
||||
) {
|
||||
const utils::Args args = test_utils::setup_args();
|
||||
const fem::FEM fem = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(fem.okay());
|
||||
REQUIRE(fem.mesh != nullptr);
|
||||
REQUIRE(fem.densityFes != nullptr);
|
||||
REQUIRE(fem.gravityPotentialFes != nullptr);
|
||||
REQUIRE(fem.gravityFluxFes != nullptr);
|
||||
REQUIRE(fem.displacementFes != nullptr);
|
||||
REQUIRE(fem.enthalpyFes != nullptr);
|
||||
|
||||
CHECK(fem.densityFes.get() != fem.gravityPotentialFes.get());
|
||||
CHECK(fem.densityFec.get() != fem.gravityPotentialFec.get());
|
||||
|
||||
CHECK(
|
||||
fem.densityFes->GetMaxElementOrder() ==
|
||||
mean_field::field::Density::Scalar::familyOrder
|
||||
);
|
||||
CHECK(
|
||||
fem.gravityPotentialFes->GetMaxElementOrder() ==
|
||||
mean_field::field::Gravity::Potential::familyOrder
|
||||
);
|
||||
CHECK(
|
||||
fem.gravityFluxFes->GetMaxElementOrder() ==
|
||||
mean_field::field::Gravity::Flux::familyOrder + 1
|
||||
);
|
||||
CHECK(
|
||||
fem.displacementFes->GetMaxElementOrder() ==
|
||||
mean_field::field::Displacement::Vector::familyOrder
|
||||
);
|
||||
CHECK(
|
||||
fem.enthalpyFes->GetMaxElementOrder() ==
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder
|
||||
);
|
||||
|
||||
CHECK(fem.densityFes->GetVDim() == 1);
|
||||
CHECK(fem.gravityPotentialFes->GetVDim() == 1);
|
||||
CHECK(fem.gravityFluxFes->GetVDim() == 1);
|
||||
CHECK(fem.displacementFes->GetVDim() == fem.mesh->SpaceDimension());
|
||||
CHECK(fem.displacementFes->GetOrdering() == mfem::Ordering::byNODES);
|
||||
CHECK(fem.enthalpyFes->GetVDim() == 1);
|
||||
|
||||
REQUIRE(fem.blockTrueOffsets.Size() == 3);
|
||||
CHECK(fem.blockTrueOffsets[0] == 0);
|
||||
CHECK(fem.blockTrueOffsets[1] == fem.displacementFes->GetTrueVSize());
|
||||
CHECK(
|
||||
fem.blockTrueOffsets[2] ==
|
||||
fem.displacementFes->GetTrueVSize() + fem.densityFes->GetTrueVSize()
|
||||
);
|
||||
|
||||
REQUIRE(fem.gravityBlockTrueOffsets.Size() == 3);
|
||||
CHECK(fem.gravityBlockTrueOffsets[0] == 0);
|
||||
CHECK(fem.gravityBlockTrueOffsets[1] == fem.gravityFluxFes->GetTrueVSize());
|
||||
CHECK(
|
||||
fem.gravityBlockTrueOffsets[2] ==
|
||||
fem.gravityFluxFes->GetTrueVSize() +
|
||||
fem.gravityPotentialFes->GetTrueVSize()
|
||||
);
|
||||
|
||||
CHECK(
|
||||
fem.gravityContext.source_form->Height() ==
|
||||
fem.gravityPotentialFes->GetTrueVSize()
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,31 @@
|
||||
module;
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
#include <string>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
export module test_helpers;
|
||||
import mean_field;
|
||||
|
||||
template <std::size_t N>
|
||||
struct Tag {
|
||||
template <std::size_t N> struct Tag {
|
||||
std::array<char, N> chars{};
|
||||
|
||||
// ReSharper disable once CppNonExplicitConvertingConstructor
|
||||
consteval Tag(std::array<char, N> arr) : chars(arr) {}
|
||||
consteval Tag(
|
||||
std::array<
|
||||
char,
|
||||
N> arr
|
||||
)
|
||||
: chars(arr) {
|
||||
}
|
||||
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
constexpr operator const char*() const { return chars.data(); }
|
||||
constexpr operator const char *() const {
|
||||
return chars.data();
|
||||
}
|
||||
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
constexpr operator Catch::StringRef() const {
|
||||
@@ -25,7 +33,7 @@ struct Tag {
|
||||
}
|
||||
|
||||
template <std::size_t M>
|
||||
consteval Tag<N + M - 1> operator&(const Tag<M>& other) const {
|
||||
consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
|
||||
std::array<char, N + M - 1> res{};
|
||||
std::ranges::copy(chars.begin(), chars.end() - 1, res.begin());
|
||||
std::ranges::copy(other.chars, res.begin() + (N - 1));
|
||||
@@ -33,18 +41,22 @@ struct Tag {
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
consteval auto make_tag(const char (&str)[N]) {
|
||||
template <std::size_t N> consteval auto make_tag(const char (&str)[N]) {
|
||||
std::array<char, N + 2> res{};
|
||||
res[0] = '[';
|
||||
std::ranges::copy(str, str + N - 1, res.begin() + 1);
|
||||
res[N] = ']';
|
||||
res[N] = ']';
|
||||
res[N + 1] = '\0';
|
||||
return Tag<N + 2>{res};
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval auto sub_tag(const Tag<N>& parent, const char (&str)[M]) {
|
||||
template <
|
||||
std::size_t N,
|
||||
std::size_t M>
|
||||
consteval auto sub_tag(
|
||||
const Tag<N> &parent,
|
||||
const char (&str)[M]
|
||||
) {
|
||||
return parent & make_tag(str);
|
||||
}
|
||||
|
||||
@@ -54,11 +66,11 @@ namespace test_utils::detail {
|
||||
mean_field::utils::Args make_default_args() {
|
||||
mean_field::utils::Args args;
|
||||
args.mesh_file = "sandbox.smesh";
|
||||
args.p.rtol = 1.0e-12;
|
||||
args.p.atol = 1.0e-12;
|
||||
args.p.rtol = 1.0e-12;
|
||||
args.p.atol = 1.0e-12;
|
||||
return args;
|
||||
}
|
||||
}
|
||||
} // namespace test_utils::detail
|
||||
|
||||
export namespace test_utils {
|
||||
void set_args(mean_field::utils::Args args) {
|
||||
@@ -72,26 +84,216 @@ export namespace test_utils {
|
||||
|
||||
return detail::make_default_args();
|
||||
}
|
||||
}
|
||||
} // namespace test_utils
|
||||
|
||||
export namespace gravity_prepared_test_utils {
|
||||
inline mfem::Vector make_deterministic_vector(
|
||||
const int size,
|
||||
const double phase = 0.0
|
||||
) {
|
||||
mfem::Vector vector(size);
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const double index = static_cast<double>(i + 1);
|
||||
vector(i) = std::sin(0.37 * index + phase) +
|
||||
0.31 * std::cos(0.19 * index - 0.5 * phase);
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
inline mfem::Vector make_displacement(
|
||||
const mean_field::fem::FEM &f,
|
||||
const double scale
|
||||
) {
|
||||
mfem::ParGridFunction displacement(f.displacementFes.get());
|
||||
|
||||
auto displacement_function =
|
||||
[scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = scale * (0.04 * position(0) +
|
||||
0.01 * position(1) * position(2));
|
||||
value(1) = scale * (-0.03 * position(1) +
|
||||
0.008 * position(0) * position(2));
|
||||
value(2) = scale * (0.02 * position(2) -
|
||||
0.006 * position(0) * position(1));
|
||||
};
|
||||
|
||||
mfem::VectorFunctionCoefficient coefficient(
|
||||
f.mesh->Dimension(), displacement_function
|
||||
);
|
||||
displacement.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector displacement_true;
|
||||
displacement.GetTrueDofs(displacement_true);
|
||||
return displacement_true;
|
||||
}
|
||||
|
||||
inline mfem::Vector make_domain_supported_density(
|
||||
const mean_field::fem::FEM &f,
|
||||
const bool stellar
|
||||
) {
|
||||
mfem::Vector attribute_values(f.mesh->attributes.Max());
|
||||
attribute_values = 0.0;
|
||||
|
||||
const int vacuum_attribute =
|
||||
f.domainMapperStateless->GetVacuumElementAttribute();
|
||||
|
||||
for (int i = 0; i < f.mesh->attributes.Size(); ++i) {
|
||||
const int attribute = f.mesh->attributes[i];
|
||||
const bool is_stellar = attribute != vacuum_attribute;
|
||||
|
||||
if (is_stellar == stellar) {
|
||||
attribute_values(attribute - 1) = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
mfem::PWConstCoefficient coefficient(attribute_values);
|
||||
mfem::ParGridFunction density(f.densityFes.get());
|
||||
density.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector density_true;
|
||||
density.GetTrueDofs(density_true);
|
||||
return density_true;
|
||||
}
|
||||
|
||||
inline mfem::Vector linear_combination(
|
||||
const mfem::Vector &first,
|
||||
const double first_scale,
|
||||
const mfem::Vector &second,
|
||||
const double second_scale
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
first.Size() == second.Size(),
|
||||
"Cannot combine vectors with different sizes."
|
||||
);
|
||||
|
||||
mfem::Vector combination(first);
|
||||
combination *= first_scale;
|
||||
combination.Add(second_scale, second);
|
||||
return combination;
|
||||
}
|
||||
|
||||
inline double global_norm(
|
||||
const mfem::Vector &vector,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
const double local_norm_squared = vector * vector;
|
||||
double global_norm_squared = 0.0;
|
||||
MPI_Allreduce(
|
||||
&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM,
|
||||
communicator
|
||||
);
|
||||
return std::sqrt(global_norm_squared);
|
||||
}
|
||||
|
||||
inline double global_dot(
|
||||
const mfem::Vector &first,
|
||||
const mfem::Vector &second,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
first.Size() == second.Size(),
|
||||
"Cannot take the dot product of vectors with different sizes."
|
||||
);
|
||||
|
||||
const double local_dot = first * second;
|
||||
double global_dot = 0.0;
|
||||
MPI_Allreduce(
|
||||
&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator
|
||||
);
|
||||
return global_dot;
|
||||
}
|
||||
|
||||
inline double relative_error(
|
||||
const mfem::Vector &computed,
|
||||
const mfem::Vector &reference,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
computed.Size() == reference.Size(),
|
||||
"Cannot compare vectors with different sizes."
|
||||
);
|
||||
|
||||
mfem::Vector difference(computed);
|
||||
difference -= reference;
|
||||
|
||||
return global_norm(difference, communicator) /
|
||||
std::max(
|
||||
global_norm(reference, communicator),
|
||||
std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
}
|
||||
|
||||
inline double relative_scalar_error(
|
||||
const double computed,
|
||||
const double reference
|
||||
) {
|
||||
return std::abs(computed - reference) /
|
||||
std::max(
|
||||
std::abs(reference), std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
}
|
||||
} // namespace gravity_prepared_test_utils
|
||||
|
||||
export namespace tags {
|
||||
inline constexpr auto geometry = make_tag("geometry");
|
||||
inline constexpr auto physics = make_tag("physics");
|
||||
inline constexpr auto unit = make_tag("unit");
|
||||
inline constexpr auto mesh = make_tag("mesh");
|
||||
inline constexpr auto integration = make_tag("integration");
|
||||
inline constexpr auto solver = make_tag("solver");
|
||||
inline constexpr auto geometry = make_tag("geometry");
|
||||
inline constexpr auto physics = make_tag("physics");
|
||||
inline constexpr auto unit = make_tag("unit");
|
||||
inline constexpr auto mesh = make_tag("mesh");
|
||||
inline constexpr auto integration = make_tag("integration");
|
||||
inline constexpr auto solver = make_tag("solver");
|
||||
inline constexpr auto integrator = make_tag("integrator");
|
||||
inline constexpr auto mapping = make_tag("mapping");
|
||||
inline constexpr auto utils = make_tag("utils");
|
||||
inline constexpr auto mfem_operators = make_tag("operators");
|
||||
inline constexpr auto initialization = make_tag("initialization");
|
||||
inline constexpr auto accuracy = make_tag("accuracy");
|
||||
inline constexpr auto closure = make_tag("closure");
|
||||
inline constexpr auto kernels = make_tag("kernels");
|
||||
|
||||
inline constexpr auto gravity = sub_tag(physics, "gravity");
|
||||
inline constexpr auto hydro = sub_tag(physics, "hydro");
|
||||
inline constexpr auto jacobian = sub_tag(integration & physics , "jacobian");
|
||||
inline constexpr auto residuals = sub_tag(integration & physics , "residuals");
|
||||
inline constexpr auto h_refinement = sub_tag(mesh & solver , "h_refinement");
|
||||
inline constexpr auto volume = sub_tag(mesh & geometry , "volume");
|
||||
inline constexpr auto quadrature = sub_tag(mesh & geometry & solver , "quadrature");
|
||||
inline constexpr auto legacy_comparison = make_tag("legacy_comparison");
|
||||
inline constexpr auto pressure = sub_tag(physics, "pressure");
|
||||
|
||||
inline constexpr auto analytic_comparison = sub_tag(solver & physics & residuals , "analytic_comparison");
|
||||
inline constexpr auto self_consistency = sub_tag(solver & physics , "self_consistency");
|
||||
inline constexpr auto hydro = sub_tag(physics, "hydro");
|
||||
inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian");
|
||||
inline constexpr auto residuals =
|
||||
sub_tag(integration & physics, "residuals");
|
||||
inline constexpr auto volume = sub_tag(mesh & geometry, "volume");
|
||||
inline constexpr auto quadrature =
|
||||
sub_tag(mesh & geometry & solver, "quadrature");
|
||||
inline constexpr auto convergence = sub_tag(solver, "convergence");
|
||||
inline constexpr auto transformations =
|
||||
sub_tag(mesh & geometry, "transformations");
|
||||
|
||||
}
|
||||
inline constexpr auto h_refinement =
|
||||
sub_tag(mesh & convergence, "h_refinement");
|
||||
inline constexpr auto p_refinement =
|
||||
sub_tag(mesh & convergence, "p_refinement");
|
||||
|
||||
inline constexpr auto analytic_comparison =
|
||||
sub_tag(solver & physics & residuals, "analytic_comparison");
|
||||
inline constexpr auto self_consistency =
|
||||
sub_tag(solver & physics, "self_consistency");
|
||||
|
||||
inline constexpr auto centrifugal =
|
||||
sub_tag(solver & physics, "centrifugal");
|
||||
inline constexpr auto advection = sub_tag(solver & physics, "advection");
|
||||
inline constexpr auto coriolis = sub_tag(solver & physics, "coriolis");
|
||||
inline constexpr auto gravity = sub_tag(solver & physics, "gravity");
|
||||
inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy");
|
||||
inline constexpr auto barotrope = sub_tag(physics, "barotrope");
|
||||
inline constexpr auto mass_continuity =
|
||||
sub_tag(solver & physics, "mass_continuity");
|
||||
inline constexpr auto pressure_gradient =
|
||||
sub_tag(solver & physics, "pressure_gradient");
|
||||
inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity");
|
||||
|
||||
inline constexpr auto compactification =
|
||||
sub_tag(mesh & mapping, "compactification");
|
||||
inline constexpr auto kelvin = sub_tag(compactification, "kelvin");
|
||||
|
||||
inline constexpr auto prepared = sub_tag(solver & physics, "prepared");
|
||||
inline constexpr auto contexts = sub_tag(solver, "contexts");
|
||||
|
||||
} // namespace tags
|
||||
|
||||
@@ -1,127 +1,496 @@
|
||||
#include <algorithm>
|
||||
#include <catch2/catch_session.hpp>
|
||||
#include <catch2/reporters/catch_reporter_streaming_base.hpp>
|
||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
||||
#include <catch2/catch_test_case_info.hpp>
|
||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
||||
#include <catch2/reporters/catch_reporter_streaming_base.hpp>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <mfem.hpp>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <fourdst/config/config.h>
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <fourdst/config/config.h>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
std::string escapeHtml(const std::string &data) {
|
||||
std::string buffer;
|
||||
buffer.reserve(data.size());
|
||||
for (size_t pos = 0; pos != data.size(); ++pos) {
|
||||
switch (data[pos]) {
|
||||
case '&':
|
||||
buffer.append("&");
|
||||
break;
|
||||
case '\"':
|
||||
buffer.append(""");
|
||||
break;
|
||||
case '\'':
|
||||
buffer.append("'");
|
||||
break;
|
||||
case '<':
|
||||
buffer.append("<");
|
||||
break;
|
||||
case '>':
|
||||
buffer.append(">");
|
||||
break;
|
||||
default:
|
||||
buffer.append(&data[pos], 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string ansiToHtml(const std::string &text) {
|
||||
// Convert text to HTML-safe first
|
||||
std::string htmlEscaped = escapeHtml(text);
|
||||
|
||||
std::ostringstream oss;
|
||||
size_t i = 0;
|
||||
size_t len = htmlEscaped.length();
|
||||
int openSpans = 0;
|
||||
|
||||
auto closeSpans = [&oss, &openSpans]() {
|
||||
while (openSpans > 0) {
|
||||
oss << "</span>";
|
||||
--openSpans;
|
||||
}
|
||||
};
|
||||
|
||||
while (i < len) {
|
||||
// Look for ANSI CSI sequence '\033[' or '\x1b['
|
||||
if ((htmlEscaped[i] == '\033' || htmlEscaped[i] == '\x1b') &&
|
||||
i + 1 < len && htmlEscaped[i + 1] == '[') {
|
||||
size_t seqStart = i + 2;
|
||||
size_t seqEnd = htmlEscaped.find('m', seqStart);
|
||||
|
||||
if (seqEnd != std::string::npos) {
|
||||
std::string codeStr =
|
||||
htmlEscaped.substr(seqStart, seqEnd - seqStart);
|
||||
i = seqEnd + 1;
|
||||
|
||||
std::istringstream codeStream(codeStr);
|
||||
std::string codeVal;
|
||||
|
||||
// Defaults if sequence is just \033[m (Reset)
|
||||
if (codeStr.empty()) {
|
||||
closeSpans();
|
||||
continue;
|
||||
}
|
||||
|
||||
while (std::getline(codeStream, codeVal, ';')) {
|
||||
int code = 0;
|
||||
try {
|
||||
code = std::stoi(codeVal);
|
||||
} catch (...) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 0: // Reset
|
||||
closeSpans();
|
||||
break;
|
||||
case 1: // Bold
|
||||
oss << "<span style='font-weight:bold;'>";
|
||||
openSpans++;
|
||||
break;
|
||||
case 2: // Dim
|
||||
oss << "<span style='opacity:0.7;'>";
|
||||
openSpans++;
|
||||
break;
|
||||
// Standard Foreground Colors
|
||||
case 30:
|
||||
oss << "<span style='color:#2c3e50;'>";
|
||||
openSpans++;
|
||||
break; // Black
|
||||
case 31:
|
||||
oss << "<span style='color:#e74c3c;'>";
|
||||
openSpans++;
|
||||
break; // Red
|
||||
case 32:
|
||||
oss << "<span style='color:#27ae60;'>";
|
||||
openSpans++;
|
||||
break; // Green
|
||||
case 33:
|
||||
oss << "<span style='color:#f39c12;'>";
|
||||
openSpans++;
|
||||
break; // Yellow
|
||||
case 34:
|
||||
oss << "<span style='color:#2980b9;'>";
|
||||
openSpans++;
|
||||
break; // Blue
|
||||
case 35:
|
||||
oss << "<span style='color:#8e44ad;'>";
|
||||
openSpans++;
|
||||
break; // Magenta
|
||||
case 36:
|
||||
oss << "<span style='color:#16a085;'>";
|
||||
openSpans++;
|
||||
break; // Cyan
|
||||
case 37:
|
||||
oss << "<span style='color:#bdc3c7;'>";
|
||||
openSpans++;
|
||||
break; // Light Gray
|
||||
// Bright Foreground Colors
|
||||
case 90:
|
||||
oss << "<span style='color:#7f8c8d;'>";
|
||||
openSpans++;
|
||||
break; // Dark Gray
|
||||
case 91:
|
||||
oss << "<span style='color:#ff6b6b;'>";
|
||||
openSpans++;
|
||||
break; // Bright Red
|
||||
case 92:
|
||||
oss << "<span style='color:#51cf66;'>";
|
||||
openSpans++;
|
||||
break; // Bright Green
|
||||
case 93:
|
||||
oss << "<span style='color:#fcc419;'>";
|
||||
openSpans++;
|
||||
break; // Bright Yellow
|
||||
case 94:
|
||||
oss << "<span style='color:#339af0;'>";
|
||||
openSpans++;
|
||||
break; // Bright Blue
|
||||
case 95:
|
||||
oss << "<span style='color:#cc5de8;'>";
|
||||
openSpans++;
|
||||
break; // Bright Magenta
|
||||
case 96:
|
||||
oss << "<span style='color:#22b8cf;'>";
|
||||
openSpans++;
|
||||
break; // Bright Cyan
|
||||
case 97:
|
||||
oss << "<span style='color:#ffffff;'>";
|
||||
openSpans++;
|
||||
break; // White
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
oss << htmlEscaped[i];
|
||||
++i;
|
||||
}
|
||||
|
||||
closeSpans();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> wrapText(
|
||||
const std::string &text,
|
||||
size_t width
|
||||
) {
|
||||
std::vector<std::string> lines;
|
||||
std::istringstream words(text);
|
||||
std::string word, line;
|
||||
|
||||
while (words >> word) {
|
||||
if (line.length() + word.length() + 1 > width) {
|
||||
if (!line.empty()) {
|
||||
lines.push_back(line);
|
||||
line.clear();
|
||||
}
|
||||
if (word.length() > width) {
|
||||
lines.push_back(word.substr(0, width - 3) + "...");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!line.empty())
|
||||
line += " ";
|
||||
line += word;
|
||||
}
|
||||
if (!line.empty())
|
||||
lines.push_back(line);
|
||||
if (lines.empty())
|
||||
lines.push_back("");
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
class CheckReporter : public Catch::StreamingReporterBase {
|
||||
// Accumulate failure messages for the current test case
|
||||
struct TestCaseData {
|
||||
std::string name;
|
||||
std::string tags;
|
||||
bool passed;
|
||||
std::size_t assertionsPassed;
|
||||
std::size_t assertionsFailed;
|
||||
std::vector<std::string> failureMessages;
|
||||
std::vector<std::string> infoMessages;
|
||||
};
|
||||
|
||||
std::vector<std::string> m_currentFailures;
|
||||
std::vector<std::string> m_currentInfos;
|
||||
std::vector<TestCaseData> m_testRunData;
|
||||
|
||||
public:
|
||||
using StreamingReporterBase::StreamingReporterBase;
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Fixed-width table reporter with detailed assertion failures.";
|
||||
return "Console reporter with wrapping, tags, and collapsible HTML "
|
||||
"export "
|
||||
"with ANSI color rendering.";
|
||||
}
|
||||
|
||||
void testRunStarting(Catch::TestRunInfo const& _testRunInfo) override {
|
||||
void testRunStarting(Catch::TestRunInfo const &_testRunInfo) override {
|
||||
StreamingReporterBase::testRunStarting(_testRunInfo);
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << std::left << std::setw(55) << "Test Case Name"
|
||||
<< "Status "
|
||||
<< std::right << std::setw(8) << "Passed"
|
||||
std::cout << std::left << std::setw(85) << "Test Case Name"
|
||||
<< "Status " << std::right << std::setw(8) << "Passed"
|
||||
<< std::setw(8) << "Failed" << '\n';
|
||||
std::cout << std::string(81, '-') << '\n';
|
||||
std::cout << std::string(121, '-') << '\n';
|
||||
}
|
||||
|
||||
// 1. Hook into every assertion to catch failures
|
||||
void assertionEnded(Catch::AssertionStats const& assertionStats) override {
|
||||
void assertionEnded(Catch::AssertionStats const &assertionStats) override {
|
||||
StreamingReporterBase::assertionEnded(assertionStats);
|
||||
|
||||
// If the assertion failed, build a detailed message
|
||||
// Capture INFO messages regardless of pass/fail status
|
||||
for (auto const &msg : assertionStats.infoMessages) {
|
||||
m_currentInfos.push_back(msg.message);
|
||||
}
|
||||
|
||||
if (!assertionStats.assertionResult.isOk()) {
|
||||
auto const& result = assertionStats.assertionResult;
|
||||
auto const &result = assertionStats.assertionResult;
|
||||
std::ostringstream oss;
|
||||
|
||||
// Format: -> FAILED: [file:line]
|
||||
oss << " \033[31m-> FAILED:\033[0m "
|
||||
<< result.getSourceInfo().file << ":" << result.getSourceInfo().line << '\n';
|
||||
<< result.getSourceInfo().file << ":"
|
||||
<< result.getSourceInfo().line << '\n';
|
||||
oss << " " << result.getTestMacroName() << "( "
|
||||
<< result.getExpression() << " )\n";
|
||||
|
||||
// Print the macro used (e.g., REQUIRE, CHECK) and the expression
|
||||
oss << " " << result.getTestMacroName() << "( " << result.getExpression() << " )\n";
|
||||
|
||||
// Print what it actually evaluated to (e.g., 1 == 2)
|
||||
if (result.hasExpandedExpression()) {
|
||||
oss << " with expansion:\n"
|
||||
<< " " << result.getExpandedExpression() << '\n';
|
||||
}
|
||||
|
||||
// Capture any INFO() messages attached to this assertion
|
||||
for (auto const& msg : assertionStats.infoMessages) {
|
||||
oss << " info: " << msg.message << '\n';
|
||||
for (auto const &msg : assertionStats.infoMessages) {
|
||||
oss << " \033[36m[INFO]\033[0m " << msg.message << '\n';
|
||||
}
|
||||
|
||||
m_currentFailures.push_back(oss.str());
|
||||
}
|
||||
}
|
||||
|
||||
void testCaseEnded(Catch::TestCaseStats const& stats) override {
|
||||
void testCaseEnded(Catch::TestCaseStats const &stats) override {
|
||||
StreamingReporterBase::testCaseEnded(stats);
|
||||
|
||||
bool passed = stats.totals.assertions.allPassed();
|
||||
bool passed = stats.totals.assertions.allPassed();
|
||||
std::string mark = passed ? "\033[32m✓\033[0m" : "\033[31m✗\033[0m";
|
||||
|
||||
std::string name = stats.testInfo->name;
|
||||
if (name.length() > 53) {
|
||||
name = name.substr(0, 50) + "...";
|
||||
auto wrappedName = wrapText(name, 83);
|
||||
|
||||
std::cout << std::left << std::setw(85) << wrappedName[0] << mark
|
||||
<< " " << std::right << std::setw(8)
|
||||
<< stats.totals.assertions.passed << std::setw(8)
|
||||
<< stats.totals.assertions.failed << '\n';
|
||||
|
||||
for (size_t i = 1; i < wrappedName.size(); ++i) {
|
||||
std::cout << " \033[90m↳ \033[0m" // Dim indent arrow
|
||||
<< std::left << std::setw(81) << wrappedName[i] << '\n';
|
||||
}
|
||||
|
||||
// Print the table row
|
||||
std::cout << std::left << std::setw(55) << name
|
||||
<< mark << " "
|
||||
<< std::right << std::setw(8) << stats.totals.assertions.passed
|
||||
<< std::setw(8) << stats.totals.assertions.failed << '\n';
|
||||
std::string tagsStr = stats.testInfo->tagsAsString();
|
||||
if (!tagsStr.empty()) {
|
||||
auto wrappedTags = wrapText("Tags: " + tagsStr, 83);
|
||||
for (const auto &line : wrappedTags) {
|
||||
std::cout << " \033[36m" << line << "\033[0m\n"; // Cyan
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Print all accumulated failures under the row
|
||||
if (!m_currentFailures.empty()) {
|
||||
std::cout << '\n';
|
||||
for (auto const& failure : m_currentFailures) {
|
||||
for (auto const &failure : m_currentFailures) {
|
||||
std::cout << failure << '\n';
|
||||
}
|
||||
// Add a separator so multiple failing tests don't blur together
|
||||
std::cout << std::string(81, '-') << '\n';
|
||||
|
||||
// Clear the buffer for the next test case
|
||||
m_currentFailures.clear();
|
||||
std::cout << std::string(121, '-') << '\n';
|
||||
}
|
||||
|
||||
m_testRunData.push_back(
|
||||
{name, tagsStr, passed, stats.totals.assertions.passed,
|
||||
stats.totals.assertions.failed, m_currentFailures, m_currentInfos}
|
||||
);
|
||||
|
||||
m_currentFailures.clear();
|
||||
m_currentInfos.clear();
|
||||
}
|
||||
|
||||
void testRunEnded(Catch::TestRunStats const& _testRunStats) override {
|
||||
void testRunEnded(Catch::TestRunStats const &_testRunStats) override {
|
||||
StreamingReporterBase::testRunEnded(_testRunStats);
|
||||
|
||||
std::cout << std::string(81, '=') << '\n';
|
||||
std::cout << std::string(121, '=') << '\n';
|
||||
|
||||
auto const& tc = _testRunStats.totals.testCases;
|
||||
auto const& as = _testRunStats.totals.assertions;
|
||||
auto const &tc = _testRunStats.totals.testCases;
|
||||
auto const &as = _testRunStats.totals.assertions;
|
||||
|
||||
std::string tc_passed_str = tc.passed > 0 ? "\033[32m" + std::to_string(tc.passed) + " passed\033[0m" : "0 passed";
|
||||
std::string tc_failed_str = tc.failed > 0 ? "\033[31m" + std::to_string(tc.failed) + " failed\033[0m" : "0 failed";
|
||||
std::string tc_passed_str =
|
||||
tc.passed > 0
|
||||
? "\033[32m" + std::to_string(tc.passed) + " passed\033[0m"
|
||||
: "0 passed";
|
||||
std::string tc_failed_str =
|
||||
tc.failed > 0
|
||||
? "\033[31m" + std::to_string(tc.failed) + " failed\033[0m"
|
||||
: "0 failed";
|
||||
|
||||
std::string as_passed_str = as.passed > 0 ? "\033[32m" + std::to_string(as.passed) + " passed\033[0m" : "0 passed";
|
||||
std::string as_failed_str = as.failed > 0 ? "\033[31m" + std::to_string(as.failed) + " failed\033[0m" : "0 failed";
|
||||
std::string as_passed_str =
|
||||
as.passed > 0
|
||||
? "\033[32m" + std::to_string(as.passed) + " passed\033[0m"
|
||||
: "0 passed";
|
||||
std::string as_failed_str =
|
||||
as.failed > 0
|
||||
? "\033[31m" + std::to_string(as.failed) + " failed\033[0m"
|
||||
: "0 failed";
|
||||
|
||||
std::cout << "Test Cases: " << tc_passed_str << ", " << tc_failed_str << ", " << tc.total() << " total\n";
|
||||
std::cout << "Assertions: " << as_passed_str << ", " << as_failed_str << ", " << as.total() << " total\n\n";
|
||||
std::cout << "Test Cases: " << tc_passed_str << ", " << tc_failed_str
|
||||
<< ", " << tc.total() << " total\n";
|
||||
std::cout << "Assertions: " << as_passed_str << ", " << as_failed_str
|
||||
<< ", " << as.total() << " total\n\n";
|
||||
|
||||
generateHtmlReport(_testRunStats);
|
||||
}
|
||||
|
||||
private:
|
||||
void generateHtmlReport(Catch::TestRunStats const &stats) {
|
||||
std::ofstream html("test_summary.html");
|
||||
if (!html)
|
||||
return;
|
||||
|
||||
html
|
||||
<< "<!DOCTYPE html>\n<html lang='en'>\n<head>\n"
|
||||
<< "<meta charset='UTF-8'>\n"
|
||||
<< "<meta name='viewport' content='width=device-width, "
|
||||
"initial-scale=1.0'>\n"
|
||||
<< "<title>Test Run Summary</title>\n"
|
||||
<< "<style>\n"
|
||||
<< "body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe "
|
||||
"UI', "
|
||||
"Roboto, Helvetica, Arial, sans-serif; "
|
||||
"background: #f4f6f8; color: #333; margin: 0; padding: 2rem; }\n"
|
||||
<< "h1 { color: #2c3e50; border-bottom: 2px solid #e0e0e0; "
|
||||
"padding-bottom: 0.5rem; }\n"
|
||||
<< ".summary-cards { display: flex; gap: 1rem; margin-bottom: "
|
||||
"2rem; }\n"
|
||||
<< ".card { background: white; padding: 1rem 1.5rem; "
|
||||
"border-radius: "
|
||||
"8px; box-shadow: 0 2px 4px "
|
||||
"rgba(0,0,0,0.05); flex: 1; }\n"
|
||||
<< ".card h3 { margin-top: 0; font-size: 0.9rem; color: #7f8c8d; "
|
||||
"text-transform: uppercase; }\n"
|
||||
<< ".card p { font-size: 1.5rem; font-weight: bold; margin: 0; }\n"
|
||||
<< ".text-green { color: #27ae60; }\n"
|
||||
<< ".text-red { color: #e74c3c; }\n"
|
||||
<< ".test-item { background: white; border-radius: 8px; padding: "
|
||||
"1rem; "
|
||||
"margin-bottom: 1rem; box-shadow: 0 2px "
|
||||
"4px rgba(0,0,0,0.05); border-left: 5px solid #bdc3c7; }\n"
|
||||
<< ".test-item.passed { border-left-color: #27ae60; }\n"
|
||||
<< ".test-item.failed { border-left-color: #e74c3c; }\n"
|
||||
<< ".test-header { display: flex; justify-content: space-between; "
|
||||
"align-items: flex-start; }\n"
|
||||
<< ".test-name { font-size: 1.1rem; font-weight: 600; margin: 0 0 "
|
||||
"0.5rem 0; word-break: break-word; }\n"
|
||||
<< ".tags { font-size: 0.8rem; color: #2980b9; background: "
|
||||
"#ebf5fb; "
|
||||
"padding: 2px 6px; border-radius: 4px; "
|
||||
"display: inline-block; margin-top: 4px; }\n"
|
||||
<< ".stats { font-size: 0.9rem; color: #7f8c8d; }\n"
|
||||
<< "details { margin-top: 0.8rem; background: #f8f9fa; border: 1px "
|
||||
"solid #e9ecef; border-radius: 6px; "
|
||||
"padding: 0.5rem 0.8rem; }\n"
|
||||
<< "summary { cursor: pointer; font-weight: 600; color: #34495e; "
|
||||
"user-select: none; font-size: 0.9rem; }\n"
|
||||
<< "summary:hover { color: #2980b9; }\n"
|
||||
<< "pre { background: #1e293b; color: #f8fafc; padding: 1rem; "
|
||||
"border-radius: 4px; overflow-x: auto; "
|
||||
"font-size: 0.85rem; line-height: 1.4; margin-top: 0.5rem; }\n"
|
||||
<< "pre.info-block { background: #0f172a; border-left: 4px solid "
|
||||
"#0284c7; }\n"
|
||||
<< "</style>\n</head>\n<body>\n";
|
||||
|
||||
html << "<h1>Test Run Summary</h1>\n";
|
||||
|
||||
// Summary Cards
|
||||
html << "<div class='summary-cards'>\n";
|
||||
html << "<div class='card'><h3>Total Cases</h3><p>"
|
||||
<< stats.totals.testCases.total() << "</p></div>\n";
|
||||
html << "<div class='card'><h3>Cases Passed</h3><p class='text-green'>"
|
||||
<< stats.totals.testCases.passed << "</p></div>\n";
|
||||
html << "<div class='card'><h3>Cases Failed</h3><p class='text-red'>"
|
||||
<< stats.totals.testCases.failed << "</p></div>\n";
|
||||
html << "</div>\n";
|
||||
|
||||
for (const auto &test : m_testRunData) {
|
||||
std::string statusClass = test.passed ? "passed" : "failed";
|
||||
html << "<div class='test-item " << statusClass << "'>\n";
|
||||
html << " <div class='test-header'>\n";
|
||||
html << " <div>\n";
|
||||
html << " <h3 class='test-name'>" << escapeHtml(test.name)
|
||||
<< "</h3>\n";
|
||||
if (!test.tags.empty()) {
|
||||
html << " <div class='tags'>" << escapeHtml(test.tags)
|
||||
<< "</div>\n";
|
||||
}
|
||||
html << " </div>\n";
|
||||
html << " <div class='stats'>\n";
|
||||
html << " <span class='text-green'>✓ "
|
||||
<< test.assertionsPassed << "</span> | ";
|
||||
html << " <span class='text-red'>✗ "
|
||||
<< test.assertionsFailed << "</span>\n";
|
||||
html << " </div>\n";
|
||||
html << " </div>\n";
|
||||
|
||||
// Collapsible INFO Messages section with ANSI color rendering
|
||||
if (!test.infoMessages.empty()) {
|
||||
html << " <details>\n";
|
||||
html << " <summary>Info Logs (" << test.infoMessages.size()
|
||||
<< ")</summary>\n";
|
||||
html << " <pre class='info-block'>";
|
||||
for (const auto &info : test.infoMessages) {
|
||||
html << "[INFO] " << ansiToHtml(info) << "\n";
|
||||
}
|
||||
html << "</pre>\n";
|
||||
html << " </details>\n";
|
||||
}
|
||||
|
||||
// Collapsible Failures section with ANSI color rendering
|
||||
if (!test.failureMessages.empty()) {
|
||||
html << " <details open>\n";
|
||||
html << " <summary class='text-red'>Failure Details ("
|
||||
<< test.failureMessages.size() << ")</summary>\n";
|
||||
html << " <pre>";
|
||||
for (const auto &msg : test.failureMessages) {
|
||||
html << ansiToHtml(msg) << "\n";
|
||||
}
|
||||
html << " </pre>\n";
|
||||
html << " </details>\n";
|
||||
}
|
||||
|
||||
html << "</div>\n";
|
||||
}
|
||||
|
||||
html << "</body>\n</html>\n";
|
||||
}
|
||||
};
|
||||
CATCH_REGISTER_REPORTER("check", CheckReporter)
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CATCH_REGISTER_REPORTER(
|
||||
"check",
|
||||
CheckReporter
|
||||
)
|
||||
|
||||
int main(
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
fourdst::config::Config<mean_field::utils::Args> cfg;
|
||||
CLI::App app{"Mean Field Tests"};
|
||||
|
||||
@@ -148,49 +517,56 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const char*> config_argv;
|
||||
std::vector<const char *> config_argv;
|
||||
config_argv.reserve(config_arguments.size());
|
||||
|
||||
for (const std::string& argument : config_arguments) {
|
||||
for (const std::string &argument : config_arguments) {
|
||||
config_argv.push_back(argument.c_str());
|
||||
}
|
||||
|
||||
try {
|
||||
app.parse(static_cast<int>(config_argv.size()), config_argv.data());
|
||||
} catch (const CLI::ParseError& error) {
|
||||
} catch (const CLI::ParseError &error) {
|
||||
return app.exit(error);
|
||||
}
|
||||
|
||||
std::vector<std::string> catch_arguments;
|
||||
catch_arguments.emplace_back(argv[0]);
|
||||
|
||||
for (const std::string& argument : app.remaining()) {
|
||||
for (const std::string &argument : app.remaining()) {
|
||||
catch_arguments.push_back(argument);
|
||||
}
|
||||
|
||||
for (const std::string& argument : forced_catch_arguments) {
|
||||
for (const std::string &argument : forced_catch_arguments) {
|
||||
catch_arguments.push_back(argument);
|
||||
}
|
||||
|
||||
const auto is_reporter_option = [](const std::string& argument) {
|
||||
return argument == "-r" || argument == "--reporter" || argument.starts_with("-r=") || argument.starts_with("--reporter=");
|
||||
const auto is_reporter_option = [](const std::string &argument) {
|
||||
return argument == "-r" || argument == "--reporter" ||
|
||||
argument.starts_with("-r=") ||
|
||||
argument.starts_with("--reporter=");
|
||||
};
|
||||
|
||||
if (const bool has_reporter = std::ranges::any_of(catch_arguments, is_reporter_option); !has_reporter) {
|
||||
if (const bool has_reporter =
|
||||
std::ranges::any_of(catch_arguments, is_reporter_option);
|
||||
!has_reporter) {
|
||||
catch_arguments.emplace_back("--reporter");
|
||||
catch_arguments.emplace_back("check");
|
||||
}
|
||||
|
||||
std::vector<const char*> catch_argv;
|
||||
std::vector<const char *> catch_argv;
|
||||
catch_argv.reserve(catch_arguments.size());
|
||||
|
||||
for (const std::string& argument : catch_arguments) {
|
||||
for (const std::string &argument : catch_arguments) {
|
||||
catch_argv.push_back(argument.c_str());
|
||||
}
|
||||
|
||||
Catch::Session session;
|
||||
|
||||
if (const int catch_parse_result = session.applyCommandLine(static_cast<int>(catch_argv.size()), catch_argv.data()); catch_parse_result != 0) {
|
||||
if (const int catch_parse_result = session.applyCommandLine(
|
||||
static_cast<int>(catch_argv.size()), catch_argv.data()
|
||||
);
|
||||
catch_parse_result != 0) {
|
||||
return catch_parse_result;
|
||||
}
|
||||
|
||||
@@ -201,7 +577,8 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
const int hdiv_max_q1d = mfem::DeviceDofQuadLimits::Get().HDIV_MAX_Q1D;
|
||||
std::cout << "H(div) maximum Q1D = " << hdiv_max_q1d << '\n';
|
||||
std::cout << "Approximate maximum safe integration order = " << 2 * hdiv_max_q1d - 1 << '\n';
|
||||
std::cout << "Approximate maximum safe integration order = "
|
||||
<< 2 * hdiv_max_q1d - 1 << '\n';
|
||||
|
||||
mean_field::utils::Args test_args = cfg.main();
|
||||
|
||||
|
||||
861
tests/utils/blocks.cpp
Normal file
861
tests/utils/blocks.cpp
Normal file
@@ -0,0 +1,861 @@
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
|
||||
namespace {
|
||||
namespace blocks = utils::blocks;
|
||||
|
||||
template <typename Query, typename List> struct contains_type;
|
||||
|
||||
template <typename Query>
|
||||
struct contains_type<Query, blocks::type_list<>> : std::false_type { };
|
||||
|
||||
template <typename Query, typename Head, typename... Tail>
|
||||
struct contains_type<Query, blocks::type_list<Head, Tail...>>
|
||||
: std::conditional_t<
|
||||
std::is_same_v<Query, Head>,
|
||||
std::true_type,
|
||||
contains_type<Query, blocks::type_list<Tail...>>> { };
|
||||
|
||||
template <typename Query, typename List>
|
||||
inline constexpr bool contains_type_v = contains_type<Query, List>::value;
|
||||
|
||||
template <int index, typename List> struct type_at;
|
||||
|
||||
template <typename Head, typename... Tail>
|
||||
struct type_at<0, blocks::type_list<Head, Tail...>> {
|
||||
using type = Head;
|
||||
};
|
||||
|
||||
template <int index, typename Head, typename... Tail>
|
||||
struct type_at<index, blocks::type_list<Head, Tail...>> {
|
||||
static_assert(index > 0);
|
||||
using type =
|
||||
typename type_at<index - 1, blocks::type_list<Tail...>>::type;
|
||||
};
|
||||
|
||||
template <int index, typename List>
|
||||
using type_at_t = typename type_at<index, List>::type;
|
||||
|
||||
template <typename Row> struct block_row_traits;
|
||||
|
||||
template <typename Residual, typename... Values>
|
||||
struct block_row_traits<blocks::block_row<Residual, Values...>> {
|
||||
using residual = Residual;
|
||||
using values = blocks::type_list<Values...>;
|
||||
|
||||
static constexpr int value_count = sizeof...(Values);
|
||||
};
|
||||
|
||||
template <typename Row, typename... ExpectedValues>
|
||||
inline constexpr bool row_has_exact_values_v =
|
||||
block_row_traits<Row>::value_count == sizeof...(ExpectedValues) &&
|
||||
(contains_type_v<
|
||||
ExpectedValues,
|
||||
typename block_row_traits<Row>::values> &&
|
||||
...);
|
||||
|
||||
struct foreign_value final : blocks::value_block_base { };
|
||||
struct foreign_residual final : blocks::residual_block_base { };
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Block Types Preserve Their Semantic Hierarchy",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<blocks::block, blocks::residual_block_base>
|
||||
);
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::block, blocks::value_block_base>);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::residual_block_base, blocks::residual_block<0>>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<blocks::value_block_base, blocks::value_block<0>>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::density>);
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::displacement>);
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::gravity>);
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::enthalpy>);
|
||||
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::density::mass>);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<blocks::term, blocks::displacement::geometry>
|
||||
);
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::gravity::gradient>);
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::gravity::poisson>);
|
||||
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::enthalpy::specific>);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::value_block_base, blocks::density::mass::value>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::residual_block_base, blocks::density::mass::residual>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::value_block_base, blocks::gravity::gradient::value>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::residual_block_base, blocks::gravity::gradient::residual>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::value_block_base, blocks::gravity::poisson::value>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::residual_block_base, blocks::gravity::poisson::residual>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::value_block_base, blocks::enthalpy::specific::value>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::residual_block_base, blocks::enthalpy::specific::residual>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::density>);
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::displacement>);
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::gravity>);
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::enthalpy>);
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::density::mass>);
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::gravity::gradient>);
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::gravity::poisson>);
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::enthalpy::specific>);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<blocks::field, blocks::barotropic_constant>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::term, blocks::barotropic_constant::mass_normalization>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::value_block_base,
|
||||
blocks::barotropic_constant::mass_normalization::value>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_base_of_v<
|
||||
blocks::residual_block_base,
|
||||
blocks::barotropic_constant::mass_normalization::residual>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
blocks::barotropic_constant::mass_normalization::value::
|
||||
static_block_size == 1
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
blocks::barotropic_constant::mass_normalization::residual::
|
||||
static_block_size == 1
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(std::is_empty_v<blocks::barotropic_constant>);
|
||||
STATIC_REQUIRE(
|
||||
std::is_empty_v<blocks::barotropic_constant::mass_normalization>
|
||||
);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Type Lists Resolve Their Types At Compile Time",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using list = blocks::type_list<
|
||||
blocks::density::mass::value, blocks::displacement::geometry::value,
|
||||
blocks::gravity::gradient::value, blocks::gravity::poisson::value>;
|
||||
|
||||
STATIC_REQUIRE(list::size == 4);
|
||||
STATIC_REQUIRE(
|
||||
blocks::type_index_v<blocks::density::mass::value, list> == 0
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
blocks::type_index_v<blocks::displacement::geometry::value, list> == 1
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
blocks::type_index_v<blocks::gravity::gradient::value, list> == 2
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
blocks::type_index_v<blocks::gravity::poisson::value, list> == 3
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<type_at_t<0, list>, blocks::density::mass::value>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
type_at_t<1, list>, blocks::displacement::geometry::value>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<type_at_t<2, list>, blocks::gravity::gradient::value>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<type_at_t<3, list>, blocks::gravity::poisson::value>
|
||||
);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Field Form Resolves Value And Residual Blocks",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::gravity_field_form;
|
||||
|
||||
constexpr auto density_value =
|
||||
blocks::get_value_block<form>(blocks::density_field.mass_term);
|
||||
constexpr auto displacement_value =
|
||||
blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
|
||||
constexpr auto gravity_gradient_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_potential_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
|
||||
constexpr auto gravity_gradient_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_poisson_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
|
||||
|
||||
STATIC_REQUIRE(form::value_block_count == 4);
|
||||
STATIC_REQUIRE(form::residual_block_count == 2);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
std::remove_cv_t<decltype(density_value)>, blocks::value_block<0>>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
std::remove_cv_t<decltype(displacement_value)>,
|
||||
blocks::value_block<1>>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
std::remove_cv_t<decltype(gravity_gradient_value)>,
|
||||
blocks::value_block<2>>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
std::remove_cv_t<decltype(gravity_potential_value)>,
|
||||
blocks::value_block<3>>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
std::remove_cv_t<decltype(gravity_gradient_residual)>,
|
||||
blocks::residual_block<0>>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
std::remove_cv_t<decltype(gravity_poisson_residual)>,
|
||||
blocks::residual_block<1>>
|
||||
);
|
||||
|
||||
CHECK(static_cast<int>(density_value) == 0);
|
||||
CHECK(static_cast<int>(displacement_value) == 1);
|
||||
CHECK(static_cast<int>(gravity_gradient_value) == 2);
|
||||
CHECK(static_cast<int>(gravity_potential_value) == 3);
|
||||
CHECK(static_cast<int>(gravity_gradient_residual) == 0);
|
||||
CHECK(static_cast<int>(gravity_poisson_residual) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Resolved Blocks Implicitly Convert For MFEM Interfaces",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::gravity_field_form;
|
||||
|
||||
constexpr auto density_value =
|
||||
blocks::get_value_block<form>(blocks::density_field.mass_term);
|
||||
constexpr auto gravity_potential_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
|
||||
constexpr auto gravity_gradient_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_poisson_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
|
||||
|
||||
auto consume_block_index = [](const int block_index) {
|
||||
return block_index;
|
||||
};
|
||||
|
||||
CHECK(consume_block_index(density_value) == 0);
|
||||
CHECK(consume_block_index(gravity_potential_value) == 3);
|
||||
CHECK(consume_block_index(gravity_gradient_residual) == 0);
|
||||
CHECK(consume_block_index(gravity_poisson_residual) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Block Indices Belong To Their Form",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using reordered_form = blocks::block_form<
|
||||
blocks::type_list<
|
||||
blocks::gravity::poisson::value, blocks::gravity::gradient::value,
|
||||
blocks::density::mass::value,
|
||||
blocks::displacement::geometry::value>,
|
||||
blocks::type_list<
|
||||
blocks::gravity::poisson::residual,
|
||||
blocks::gravity::gradient::residual>>;
|
||||
|
||||
constexpr auto gravity_potential_value =
|
||||
blocks::get_value_block<reordered_form>(
|
||||
blocks::gravity_field.poisson_term
|
||||
);
|
||||
constexpr auto gravity_gradient_value =
|
||||
blocks::get_value_block<reordered_form>(
|
||||
blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto density_value = blocks::get_value_block<reordered_form>(
|
||||
blocks::density_field.mass_term
|
||||
);
|
||||
constexpr auto displacement_value = blocks::get_value_block<reordered_form>(
|
||||
blocks::displacement_field.geometry_term
|
||||
);
|
||||
constexpr auto gravity_poisson_residual =
|
||||
blocks::get_residual_block<reordered_form>(
|
||||
blocks::gravity_field.poisson_term
|
||||
);
|
||||
constexpr auto gravity_gradient_residual =
|
||||
blocks::get_residual_block<reordered_form>(
|
||||
blocks::gravity_field.gradient_term
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(static_cast<int>(gravity_potential_value) == 0);
|
||||
STATIC_REQUIRE(static_cast<int>(gravity_gradient_value) == 1);
|
||||
STATIC_REQUIRE(static_cast<int>(density_value) == 2);
|
||||
STATIC_REQUIRE(static_cast<int>(displacement_value) == 3);
|
||||
STATIC_REQUIRE(static_cast<int>(gravity_poisson_residual) == 0);
|
||||
STATIC_REQUIRE(static_cast<int>(gravity_gradient_residual) == 1);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Form Layout Constructs Runtime Offsets From Block Sizes",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::gravity_field_form;
|
||||
|
||||
const std::array<int, form::value_block_count> value_sizes{11, 13, 17, 19};
|
||||
const std::array<int, form::residual_block_count> residual_sizes{23, 29};
|
||||
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
|
||||
|
||||
constexpr auto density_value =
|
||||
blocks::get_value_block<form>(blocks::density_field.mass_term);
|
||||
constexpr auto displacement_value =
|
||||
blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
|
||||
constexpr auto gravity_gradient_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_potential_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
|
||||
constexpr auto gravity_gradient_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_poisson_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
|
||||
|
||||
CHECK(layout.size(density_value) == 11);
|
||||
CHECK(layout.size(displacement_value) == 13);
|
||||
CHECK(layout.size(gravity_gradient_value) == 17);
|
||||
CHECK(layout.size(gravity_potential_value) == 19);
|
||||
CHECK(layout.size(gravity_gradient_residual) == 23);
|
||||
CHECK(layout.size(gravity_poisson_residual) == 29);
|
||||
|
||||
CHECK(layout.offset(density_value) == 0);
|
||||
CHECK(layout.offset(displacement_value) == 11);
|
||||
CHECK(layout.offset(gravity_gradient_value) == 24);
|
||||
CHECK(layout.offset(gravity_potential_value) == 41);
|
||||
CHECK(layout.offset(gravity_gradient_residual) == 0);
|
||||
CHECK(layout.offset(gravity_poisson_residual) == 23);
|
||||
|
||||
REQUIRE(layout.value_offsets().Size() == 5);
|
||||
CHECK(layout.value_offsets()[0] == 0);
|
||||
CHECK(layout.value_offsets()[1] == 11);
|
||||
CHECK(layout.value_offsets()[2] == 24);
|
||||
CHECK(layout.value_offsets()[3] == 41);
|
||||
CHECK(layout.value_offsets()[4] == 60);
|
||||
|
||||
REQUIRE(layout.residual_offsets().Size() == 3);
|
||||
CHECK(layout.residual_offsets()[0] == 0);
|
||||
CHECK(layout.residual_offsets()[1] == 23);
|
||||
CHECK(layout.residual_offsets()[2] == 52);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Form Layout Supports Empty Runtime Blocks",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::gravity_field_form;
|
||||
|
||||
const std::array<int, form::value_block_count> value_sizes{3, 0, 5, 0};
|
||||
const std::array<int, form::residual_block_count> residual_sizes{0, 7};
|
||||
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
|
||||
|
||||
constexpr auto density_value =
|
||||
blocks::get_value_block<form>(blocks::density_field.mass_term);
|
||||
constexpr auto displacement_value =
|
||||
blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
|
||||
constexpr auto gravity_gradient_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_potential_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
|
||||
constexpr auto gravity_gradient_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_poisson_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
|
||||
|
||||
CHECK(layout.size(density_value) == 3);
|
||||
CHECK(layout.size(displacement_value) == 0);
|
||||
CHECK(layout.size(gravity_gradient_value) == 5);
|
||||
CHECK(layout.size(gravity_potential_value) == 0);
|
||||
CHECK(layout.size(gravity_gradient_residual) == 0);
|
||||
CHECK(layout.size(gravity_poisson_residual) == 7);
|
||||
|
||||
CHECK(layout.offset(density_value) == 0);
|
||||
CHECK(layout.offset(displacement_value) == 3);
|
||||
CHECK(layout.offset(gravity_gradient_value) == 3);
|
||||
CHECK(layout.offset(gravity_potential_value) == 8);
|
||||
CHECK(layout.offset(gravity_gradient_residual) == 0);
|
||||
CHECK(layout.offset(gravity_poisson_residual) == 0);
|
||||
|
||||
CHECK(layout.value_offsets().Last() == 8);
|
||||
CHECK(layout.residual_offsets().Last() == 7);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Form Layout Integrates With MFEM Block Vectors",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::gravity_field_form;
|
||||
|
||||
const std::array<int, form::value_block_count> value_sizes{3, 5, 7, 11};
|
||||
const std::array<int, form::residual_block_count> residual_sizes{13, 17};
|
||||
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
|
||||
|
||||
constexpr auto density_value =
|
||||
blocks::get_value_block<form>(blocks::density_field.mass_term);
|
||||
constexpr auto displacement_value =
|
||||
blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
|
||||
constexpr auto gravity_gradient_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_potential_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
|
||||
constexpr auto gravity_gradient_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_poisson_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
|
||||
|
||||
mfem::BlockVector values(layout.value_offsets());
|
||||
mfem::BlockVector residuals(layout.residual_offsets());
|
||||
|
||||
values = 0.0;
|
||||
residuals = 0.0;
|
||||
|
||||
values.GetBlock(density_value) = 1.0;
|
||||
values.GetBlock(displacement_value) = 2.0;
|
||||
values.GetBlock(gravity_gradient_value) = 3.0;
|
||||
values.GetBlock(gravity_potential_value) = 4.0;
|
||||
|
||||
residuals.GetBlock(gravity_gradient_residual) = 5.0;
|
||||
residuals.GetBlock(gravity_poisson_residual) = 6.0;
|
||||
|
||||
CHECK(values.GetBlock(density_value).Size() == 3);
|
||||
CHECK(values.GetBlock(displacement_value).Size() == 5);
|
||||
CHECK(values.GetBlock(gravity_gradient_value).Size() == 7);
|
||||
CHECK(values.GetBlock(gravity_potential_value).Size() == 11);
|
||||
CHECK(residuals.GetBlock(gravity_gradient_residual).Size() == 13);
|
||||
CHECK(residuals.GetBlock(gravity_poisson_residual).Size() == 17);
|
||||
|
||||
CHECK(values.GetBlock(density_value).Min() == 1.0);
|
||||
CHECK(values.GetBlock(density_value).Max() == 1.0);
|
||||
CHECK(values.GetBlock(displacement_value).Min() == 2.0);
|
||||
CHECK(values.GetBlock(displacement_value).Max() == 2.0);
|
||||
CHECK(values.GetBlock(gravity_gradient_value).Min() == 3.0);
|
||||
CHECK(values.GetBlock(gravity_gradient_value).Max() == 3.0);
|
||||
CHECK(values.GetBlock(gravity_potential_value).Min() == 4.0);
|
||||
CHECK(values.GetBlock(gravity_potential_value).Max() == 4.0);
|
||||
CHECK(residuals.GetBlock(gravity_gradient_residual).Min() == 5.0);
|
||||
CHECK(residuals.GetBlock(gravity_gradient_residual).Max() == 5.0);
|
||||
CHECK(residuals.GetBlock(gravity_poisson_residual).Min() == 6.0);
|
||||
CHECK(residuals.GetBlock(gravity_poisson_residual).Max() == 6.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Compile Time Blocks Configure An MFEM Block Operator",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::gravity_field_form;
|
||||
|
||||
const std::array<int, form::value_block_count> value_sizes{3, 5, 7, 11};
|
||||
const std::array<int, form::residual_block_count> residual_sizes{13, 17};
|
||||
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
|
||||
|
||||
constexpr auto density_value =
|
||||
blocks::get_value_block<form>(blocks::density_field.mass_term);
|
||||
constexpr auto gravity_gradient_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_poisson_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
|
||||
|
||||
mfem::DenseMatrix source_block(
|
||||
layout.size(gravity_poisson_residual), layout.size(density_value)
|
||||
);
|
||||
source_block = 1.0;
|
||||
|
||||
mfem::BlockOperator block_operator(
|
||||
layout.residual_offsets(), layout.value_offsets()
|
||||
);
|
||||
block_operator.SetBlock(
|
||||
gravity_poisson_residual, density_value, &source_block
|
||||
);
|
||||
|
||||
mfem::BlockVector values(layout.value_offsets());
|
||||
mfem::BlockVector residuals(layout.residual_offsets());
|
||||
|
||||
values = 0.0;
|
||||
residuals = 0.0;
|
||||
values.GetBlock(density_value) = 2.0;
|
||||
|
||||
block_operator.Mult(values, residuals);
|
||||
|
||||
CHECK(residuals.GetBlock(gravity_gradient_residual).Norml2() == 0.0);
|
||||
|
||||
const mfem::Vector &poisson_residual =
|
||||
residuals.GetBlock(gravity_poisson_residual);
|
||||
REQUIRE(poisson_residual.Size() == residual_sizes[1]);
|
||||
|
||||
for (int i = 0; i < poisson_residual.Size(); ++i) {
|
||||
CHECK(poisson_residual(i) == 2.0 * value_sizes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Jacobian Form Describes The Expected Couplings",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using gradient_row = type_at_t<0, blocks::gravity_jacobian_form>;
|
||||
using poisson_row = type_at_t<1, blocks::gravity_jacobian_form>;
|
||||
|
||||
using gradient_traits = block_row_traits<gradient_row>;
|
||||
using poisson_traits = block_row_traits<poisson_row>;
|
||||
|
||||
STATIC_REQUIRE(blocks::gravity_jacobian_form::size == 2);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
typename gradient_traits::residual,
|
||||
blocks::gravity::gradient::residual>
|
||||
);
|
||||
STATIC_REQUIRE(gradient_traits::value_count == 3);
|
||||
STATIC_REQUIRE(
|
||||
contains_type_v<
|
||||
blocks::gravity::gradient::value, typename gradient_traits::values>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
contains_type_v<
|
||||
blocks::gravity::poisson::value, typename gradient_traits::values>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
contains_type_v<
|
||||
blocks::displacement::geometry::value,
|
||||
typename gradient_traits::values>
|
||||
);
|
||||
STATIC_REQUIRE_FALSE(
|
||||
contains_type_v<
|
||||
blocks::density::mass::value, typename gradient_traits::values>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
std::is_same_v<
|
||||
typename poisson_traits::residual,
|
||||
blocks::gravity::poisson::residual>
|
||||
);
|
||||
STATIC_REQUIRE(poisson_traits::value_count == 3);
|
||||
STATIC_REQUIRE(
|
||||
contains_type_v<
|
||||
blocks::gravity::gradient::value, typename poisson_traits::values>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
contains_type_v<
|
||||
blocks::density::mass::value, typename poisson_traits::values>
|
||||
);
|
||||
STATIC_REQUIRE(
|
||||
contains_type_v<
|
||||
blocks::displacement::geometry::value,
|
||||
typename poisson_traits::values>
|
||||
);
|
||||
STATIC_REQUIRE_FALSE(
|
||||
contains_type_v<
|
||||
blocks::gravity::poisson::value, typename poisson_traits::values>
|
||||
);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Equilibrium Form Encodes The Agreed Row And Column Layouts",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::barotropic_equilibrium_form;
|
||||
|
||||
constexpr auto density_value =
|
||||
blocks::get_value_block<form>(blocks::density_field.mass_term);
|
||||
constexpr auto displacement_value =
|
||||
blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
|
||||
constexpr auto gravity_gradient_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_potential_value =
|
||||
blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
|
||||
constexpr auto enthalpy_value =
|
||||
blocks::get_value_block<form>(blocks::enthalpy_field.specific_term);
|
||||
constexpr auto barotropic_constant_value = blocks::get_value_block<form>(
|
||||
blocks::barotropic_constant_field.mass_normalization_term
|
||||
);
|
||||
|
||||
constexpr auto gravity_gradient_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravity_poisson_residual =
|
||||
blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
|
||||
constexpr auto density_residual =
|
||||
blocks::get_residual_block<form>(blocks::density_field.mass_term);
|
||||
constexpr auto displacement_residual = blocks::get_residual_block<form>(
|
||||
blocks::displacement_field.geometry_term
|
||||
);
|
||||
constexpr auto enthalpy_residual =
|
||||
blocks::get_residual_block<form>(blocks::enthalpy_field.specific_term);
|
||||
constexpr auto mass_residual = blocks::get_residual_block<form>(
|
||||
blocks::barotropic_constant_field.mass_normalization_term
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(form::value_block_count == 6);
|
||||
STATIC_REQUIRE(form::residual_block_count == 6);
|
||||
|
||||
STATIC_REQUIRE(static_cast<int>(density_value) == 0);
|
||||
STATIC_REQUIRE(static_cast<int>(displacement_value) == 1);
|
||||
STATIC_REQUIRE(static_cast<int>(gravity_gradient_value) == 2);
|
||||
STATIC_REQUIRE(static_cast<int>(gravity_potential_value) == 3);
|
||||
STATIC_REQUIRE(static_cast<int>(enthalpy_value) == 4);
|
||||
STATIC_REQUIRE(static_cast<int>(barotropic_constant_value) == 5);
|
||||
|
||||
STATIC_REQUIRE(static_cast<int>(gravity_gradient_residual) == 0);
|
||||
STATIC_REQUIRE(static_cast<int>(gravity_poisson_residual) == 1);
|
||||
STATIC_REQUIRE(static_cast<int>(density_residual) == 2);
|
||||
STATIC_REQUIRE(static_cast<int>(displacement_residual) == 3);
|
||||
STATIC_REQUIRE(static_cast<int>(enthalpy_residual) == 4);
|
||||
STATIC_REQUIRE(static_cast<int>(mass_residual) == 5);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Jacobian Form Encodes The Exact Direct Coupling Graph",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using jacobian = blocks::barotropic_equilibrium_jacobian_form;
|
||||
|
||||
using gradient_row = type_at_t<0, jacobian>;
|
||||
using poisson_row = type_at_t<1, jacobian>;
|
||||
using density_row = type_at_t<2, jacobian>;
|
||||
using displacement_row = type_at_t<3, jacobian>;
|
||||
using enthalpy_row = type_at_t<4, jacobian>;
|
||||
using mass_row = type_at_t<5, jacobian>;
|
||||
|
||||
STATIC_REQUIRE(jacobian::size == 6);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
row_has_exact_values_v<
|
||||
gradient_row, blocks::gravity::gradient::value,
|
||||
blocks::gravity::poisson::value,
|
||||
blocks::displacement::geometry::value>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
row_has_exact_values_v<
|
||||
poisson_row, blocks::gravity::gradient::value,
|
||||
blocks::density::mass::value, blocks::displacement::geometry::value>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
row_has_exact_values_v<
|
||||
density_row, blocks::density::mass::value,
|
||||
blocks::enthalpy::specific::value,
|
||||
blocks::displacement::geometry::value>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
row_has_exact_values_v<
|
||||
displacement_row, blocks::displacement::geometry::value,
|
||||
blocks::enthalpy::specific::value>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
row_has_exact_values_v<
|
||||
enthalpy_row, blocks::enthalpy::specific::value,
|
||||
blocks::gravity::poisson::value,
|
||||
blocks::displacement::geometry::value,
|
||||
blocks::barotropic_constant::mass_normalization::value>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
row_has_exact_values_v<
|
||||
mass_row, blocks::density::mass::value,
|
||||
blocks::displacement::geometry::value>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
blocks::has_jacobian_coupling_v<
|
||||
blocks::displacement::geometry::residual,
|
||||
blocks::gravity::poisson::value, jacobian>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
blocks::has_jacobian_coupling_v<
|
||||
blocks::barotropic_constant::mass_normalization::residual,
|
||||
blocks::barotropic_constant::mass_normalization::value, jacobian>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
blocks::has_jacobian_coupling_v<
|
||||
blocks::barotropic_constant::mass_normalization::residual,
|
||||
blocks::enthalpy::specific::value, jacobian>
|
||||
);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Block Validators Reject Structurally Malformed Forms",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::barotropic_equilibrium_form;
|
||||
using jacobian = blocks::barotropic_equilibrium_jacobian_form;
|
||||
|
||||
using gradient_row = type_at_t<0, jacobian>;
|
||||
using poisson_row = type_at_t<1, jacobian>;
|
||||
using density_row = type_at_t<2, jacobian>;
|
||||
using displacement_row = type_at_t<3, jacobian>;
|
||||
using enthalpy_row = type_at_t<4, jacobian>;
|
||||
using mass_row = type_at_t<5, jacobian>;
|
||||
|
||||
using missing_row = blocks::type_list<
|
||||
gradient_row, poisson_row, density_row, displacement_row, enthalpy_row>;
|
||||
|
||||
using duplicate_row = blocks::type_list<
|
||||
gradient_row, poisson_row, density_row, displacement_row, enthalpy_row,
|
||||
enthalpy_row>;
|
||||
|
||||
using reordered_rows = blocks::type_list<
|
||||
poisson_row, gradient_row, density_row, displacement_row, enthalpy_row,
|
||||
mass_row>;
|
||||
|
||||
using foreign_value_row = blocks::block_row<
|
||||
blocks::enthalpy::specific::residual, blocks::enthalpy::specific::value,
|
||||
blocks::gravity::poisson::value, blocks::displacement::geometry::value,
|
||||
foreign_value>;
|
||||
|
||||
using foreign_value_jacobian = blocks::type_list<
|
||||
gradient_row, poisson_row, density_row, displacement_row,
|
||||
foreign_value_row, mass_row>;
|
||||
|
||||
using duplicate_value_row = blocks::block_row<
|
||||
blocks::enthalpy::specific::residual, blocks::enthalpy::specific::value,
|
||||
blocks::gravity::poisson::value, blocks::displacement::geometry::value,
|
||||
blocks::barotropic_constant::mass_normalization::value,
|
||||
blocks::barotropic_constant::mass_normalization::value>;
|
||||
|
||||
using duplicate_value_jacobian = blocks::type_list<
|
||||
gradient_row, poisson_row, density_row, displacement_row,
|
||||
duplicate_value_row, mass_row>;
|
||||
|
||||
using unknown_residual_row =
|
||||
blocks::block_row<foreign_residual, blocks::density::mass::value>;
|
||||
|
||||
using unknown_residual_jacobian = blocks::type_list<
|
||||
gradient_row, poisson_row, density_row, displacement_row, enthalpy_row,
|
||||
unknown_residual_row>;
|
||||
|
||||
using duplicate_value_form = blocks::block_form<
|
||||
blocks::type_list<
|
||||
blocks::density::mass::value, blocks::density::mass::value>,
|
||||
blocks::type_list<blocks::density::mass::residual>>;
|
||||
|
||||
STATIC_REQUIRE(blocks::block_form_is_valid_v<form>);
|
||||
STATIC_REQUIRE((blocks::valid_jacobian_form<form, jacobian>));
|
||||
|
||||
STATIC_REQUIRE_FALSE(blocks::block_form_is_valid_v<duplicate_value_form>);
|
||||
|
||||
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, missing_row>));
|
||||
|
||||
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, duplicate_row>));
|
||||
|
||||
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, reordered_rows>));
|
||||
|
||||
STATIC_REQUIRE_FALSE((
|
||||
blocks::valid_jacobian_form<form, foreign_value_jacobian>
|
||||
));
|
||||
|
||||
STATIC_REQUIRE_FALSE((
|
||||
blocks::valid_jacobian_form<form, duplicate_value_jacobian>
|
||||
));
|
||||
|
||||
STATIC_REQUIRE_FALSE((
|
||||
blocks::valid_jacobian_form<form, unknown_residual_jacobian>
|
||||
));
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Layout Preserves Distinct Row And Column Offsets",
|
||||
tags::unit &tags::solver &tags::utils
|
||||
) {
|
||||
using form = blocks::barotropic_equilibrium_form;
|
||||
|
||||
// Columns: [rho, d, g, Phi, h, C]
|
||||
const std::array<int, form::value_block_count> value_sizes{11, 13, 17,
|
||||
19, 23, 1};
|
||||
|
||||
// Rows: [R_g, R_Phi, R_rho, R_d, R_h, R_M]
|
||||
const std::array<int, form::residual_block_count> residual_sizes{17, 19, 11,
|
||||
13, 23, 1};
|
||||
|
||||
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
|
||||
|
||||
constexpr auto constant_value = blocks::get_value_block<form>(
|
||||
blocks::barotropic_constant_field.mass_normalization_term
|
||||
);
|
||||
constexpr auto mass_residual = blocks::get_residual_block<form>(
|
||||
blocks::barotropic_constant_field.mass_normalization_term
|
||||
);
|
||||
|
||||
CHECK(layout.size(constant_value) == 1);
|
||||
CHECK(layout.size(mass_residual) == 1);
|
||||
|
||||
CHECK(layout.offset(constant_value) == 83);
|
||||
CHECK(layout.offset(mass_residual) == 83);
|
||||
|
||||
CHECK(layout.value_offsets().Last() == 84);
|
||||
CHECK(layout.residual_offsets().Last() == 84);
|
||||
|
||||
const std::array<int, form::value_block_count> invalid_value_sizes{11, 13,
|
||||
17, 19,
|
||||
23, 2};
|
||||
|
||||
CHECK_THROWS(
|
||||
blocks::form_layout<form>(invalid_value_sizes, residual_sizes)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user