mass normaliztion, gravity, displacement, and hydrostatic equilibrium are now migrated
3569 lines
152 KiB
C++
3569 lines
152 KiB
C++
#include "profile.h"
|
|
#include <array>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
#include <cmath>
|
|
#include <mfem.hpp>
|
|
|
|
import mean_field;
|
|
import test_helpers;
|
|
|
|
using namespace mean_field;
|
|
using Catch::Matchers::WithinAbs;
|
|
|
|
namespace {
|
|
namespace blocks = utils::blocks;
|
|
using form = blocks::gravity_field_form;
|
|
|
|
constexpr auto density_block = blocks::get_value_block<form>(blocks::density_field.mass_term);
|
|
constexpr auto displacement_block = blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
|
|
constexpr auto gravity_gradient_block = blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
|
|
constexpr auto gravity_potential_block = blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
|
|
constexpr auto gravity_gradient_residual_block =
|
|
blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
|
constexpr auto gravity_poisson_residual_block =
|
|
blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
|
|
|
|
blocks::form_layout<form> make_gravity_layout(const fem::FEM &f) {
|
|
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
|
const auto density_map = field::make_field_dof_map<field::Density, DomainSchema>(*f.densityFes);
|
|
const auto displacement_map = field::make_field_dof_map<field::Displacement, DomainSchema>(*f.displacementFes);
|
|
const auto flux_map = field::make_field_dof_map<field::Gravity, DomainSchema>(*f.gravityFluxFes);
|
|
const auto potential_map = field::make_field_dof_map<field::Gravity, DomainSchema>(*f.gravityPotentialFes);
|
|
const std::array<int, form::value_block_count> value_sizes{
|
|
density_map.reduced_size(), displacement_map.reduced_size(), flux_map.reduced_size(),
|
|
potential_map.reduced_size()
|
|
};
|
|
|
|
const std::array<int, form::residual_block_count> residual_sizes{
|
|
flux_map.reduced_size(), potential_map.reduced_size()
|
|
};
|
|
|
|
return blocks::form_layout<form>(value_sizes, residual_sizes);
|
|
}
|
|
|
|
template <typename Block>
|
|
void set_block(
|
|
mfem::Vector &vector,
|
|
const mfem::Array<int> &offsets,
|
|
const Block block,
|
|
const mfem::Vector &values
|
|
) {
|
|
const int block_id = block;
|
|
const int begin = offsets[block_id];
|
|
const int size = offsets[block_id + 1] - begin;
|
|
|
|
REQUIRE(values.Size() == size);
|
|
for (int i = 0; i < size; ++i)
|
|
vector(begin + i) = values(i);
|
|
}
|
|
|
|
template <typename Block>
|
|
mfem::Vector get_block(
|
|
const mfem::Vector &vector,
|
|
const mfem::Array<int> &offsets,
|
|
const Block block
|
|
) {
|
|
const int block_id = block;
|
|
const int begin = offsets[block_id];
|
|
const int size = offsets[block_id + 1] - begin;
|
|
|
|
mfem::Vector result(size);
|
|
for (int i = 0; i < size; ++i)
|
|
result(i) = vector(begin + i);
|
|
return result;
|
|
}
|
|
|
|
mfem::Vector make_test_vector(
|
|
const int size,
|
|
const double phase
|
|
) {
|
|
mfem::Vector vector(size);
|
|
for (int i = 0; i < size; ++i)
|
|
vector(i) = 0.4 * std::sin(0.37 * static_cast<double>(i + 1) + phase) +
|
|
0.2 * std::cos(0.19 * static_cast<double>(i + 1) - phase);
|
|
return vector;
|
|
}
|
|
|
|
mfem::Vector make_displacement(const fem::FEM &f) {
|
|
auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) {
|
|
value.SetSize(3);
|
|
value(0) = 0.015 * position(0) + 0.004 * position(1);
|
|
value(1) = -0.003 * position(0) + 0.012 * position(1);
|
|
value(2) = -0.008 * position(2);
|
|
};
|
|
|
|
mfem::VectorFunctionCoefficient coefficient(3, displacement_function);
|
|
mfem::ParGridFunction displacement(f.displacementFes.get());
|
|
mfem::Vector displacement_true;
|
|
|
|
displacement.ProjectCoefficient(coefficient);
|
|
displacement.GetTrueDofs(displacement_true);
|
|
return displacement_true;
|
|
}
|
|
|
|
mfem::Vector make_constant_density(
|
|
const fem::FEM &f,
|
|
const double value
|
|
) {
|
|
mfem::ConstantCoefficient coefficient(value);
|
|
mfem::ParGridFunction density(f.densityFes.get());
|
|
mfem::Vector density_true;
|
|
|
|
density.ProjectCoefficient(coefficient);
|
|
density.GetTrueDofs(density_true);
|
|
return density_true;
|
|
}
|
|
|
|
mfem::Vector make_vacuum_density(
|
|
const fem::FEM &f,
|
|
const double value
|
|
) {
|
|
mfem::ParGridFunction density(f.densityFes.get());
|
|
density = 0.0;
|
|
|
|
mfem::Array<int> element_dofs;
|
|
mfem::Vector element_values;
|
|
|
|
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
|
if (f.mesh->GetAttribute(element_id) != f.domainMapperStateless->GetVacuumElementAttribute())
|
|
continue;
|
|
|
|
f.densityFes->GetElementDofs(element_id, element_dofs);
|
|
element_values.SetSize(element_dofs.Size());
|
|
element_values = value;
|
|
density.SetSubVector(element_dofs, element_values);
|
|
}
|
|
|
|
mfem::Vector density_true;
|
|
density.GetTrueDofs(density_true);
|
|
return density_true;
|
|
}
|
|
|
|
double relative_difference(
|
|
const mfem::Vector &lhs,
|
|
const mfem::Vector &rhs
|
|
) {
|
|
REQUIRE(lhs.Size() == rhs.Size());
|
|
|
|
mfem::Vector difference(lhs);
|
|
difference -= rhs;
|
|
|
|
return difference.Norml2() / std::max({lhs.Norml2(), rhs.Norml2(), 1.0e-14});
|
|
}
|
|
|
|
mfem::Vector make_core_supported_gravity_gradient(const fem::FEM &f) {
|
|
constexpr double support_radius = 0.15 * utils::RADIUS;
|
|
constexpr double support_radius_squared = support_radius * support_radius;
|
|
|
|
auto field_function = [](const mfem::Vector &position, mfem::Vector &value) {
|
|
const double radius_squared = position * position;
|
|
|
|
value.SetSize(3);
|
|
value = 0.0;
|
|
|
|
if (radius_squared >= support_radius_squared)
|
|
return;
|
|
|
|
const double normalized_radius_squared = radius_squared / support_radius_squared;
|
|
const double envelope = std::pow(1.0 - normalized_radius_squared, 3.0);
|
|
|
|
value(0) = envelope;
|
|
value(1) = -0.4 * envelope;
|
|
value(2) = 0.7 * envelope;
|
|
};
|
|
|
|
mfem::VectorFunctionCoefficient coefficient(3, field_function);
|
|
mfem::ParGridFunction gravity_gradient(f.gravityFluxFes.get());
|
|
mfem::Vector gravity_gradient_true;
|
|
|
|
gravity_gradient.ProjectCoefficient(coefficient);
|
|
gravity_gradient.GetTrueDofs(gravity_gradient_true);
|
|
return gravity_gradient_true;
|
|
}
|
|
|
|
double global_norm(
|
|
const mfem::Vector &vector,
|
|
MPI_Comm communicator
|
|
) {
|
|
REQUIRE(vector.Size() > 0);
|
|
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);
|
|
}
|
|
|
|
double global_relative_difference(
|
|
const mfem::Vector &lhs,
|
|
const mfem::Vector &rhs,
|
|
MPI_Comm communicator
|
|
) {
|
|
REQUIRE(lhs.Size() == rhs.Size());
|
|
|
|
mfem::Vector difference(lhs);
|
|
difference -= rhs;
|
|
|
|
const double difference_norm = global_norm(difference, communicator);
|
|
const double lhs_norm = global_norm(lhs, communicator);
|
|
const double rhs_norm = global_norm(rhs, communicator);
|
|
|
|
return difference_norm / std::max({lhs_norm, rhs_norm, 1.0e-14});
|
|
}
|
|
|
|
struct StatelessHDivMassReference {
|
|
mfem::Vector total_action;
|
|
mfem::Vector stellar_action;
|
|
mfem::Vector vacuum_action;
|
|
long long stellar_elements{0};
|
|
long long vacuum_elements{0};
|
|
long long stellar_quadrature_points{0};
|
|
long long vacuum_quadrature_points{0};
|
|
double minimum_stellar_determinant{std::numeric_limits<double>::infinity()};
|
|
double maximum_stellar_determinant{0.0};
|
|
double minimum_vacuum_determinant{std::numeric_limits<double>::infinity()};
|
|
double maximum_vacuum_determinant{0.0};
|
|
};
|
|
|
|
void reference_true_to_local(
|
|
const mfem::ParFiniteElementSpace &fes,
|
|
const mfem::Vector &true_vector,
|
|
mfem::Vector &local_vector
|
|
) {
|
|
local_vector.SetSize(fes.GetVSize());
|
|
|
|
const mfem::Operator *prolongation = fes.GetProlongationMatrix();
|
|
if (prolongation != nullptr) {
|
|
prolongation->Mult(true_vector, local_vector);
|
|
} else {
|
|
local_vector = true_vector;
|
|
}
|
|
}
|
|
|
|
void reference_local_to_true(
|
|
const mfem::ParFiniteElementSpace &fes,
|
|
const mfem::Vector &local_vector,
|
|
mfem::Vector &true_vector
|
|
) {
|
|
true_vector.SetSize(fes.GetTrueVSize());
|
|
true_vector = 0.0;
|
|
|
|
const mfem::Operator *prolongation = fes.GetProlongationMatrix();
|
|
if (prolongation != nullptr) {
|
|
prolongation->MultTranspose(local_vector, true_vector);
|
|
} else {
|
|
true_vector = local_vector;
|
|
}
|
|
}
|
|
|
|
double global_vector_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);
|
|
}
|
|
|
|
double global_vector_dot(
|
|
const mfem::Vector &lhs,
|
|
const mfem::Vector &rhs,
|
|
MPI_Comm communicator
|
|
) {
|
|
const double local_dot = lhs * rhs;
|
|
double global_dot = 0.0;
|
|
MPI_Allreduce(&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
|
return global_dot;
|
|
}
|
|
|
|
double global_relative_vector_error(
|
|
const mfem::Vector &computed,
|
|
const mfem::Vector &reference,
|
|
MPI_Comm communicator
|
|
) {
|
|
mfem::Vector difference(computed);
|
|
difference -= reference;
|
|
return global_vector_norm(difference, communicator) /
|
|
std::max(global_vector_norm(reference, communicator), std::numeric_limits<double>::epsilon());
|
|
}
|
|
|
|
const mfem::IntegrationRule &get_stateless_hdiv_reference_rule(
|
|
const fem::FEM &f,
|
|
const mfem::FiniteElement &element,
|
|
const mfem::ElementTransformation &transformation
|
|
) {
|
|
const bool is_vacuum = transformation.Attribute == f.domainMapperStateless->GetVacuumElementAttribute();
|
|
|
|
const quadrature::Query query{
|
|
.term = quadrature::Term::gravity_hdiv_mass,
|
|
.role = quadrature::QuadratureRole::discretization,
|
|
.domain = utils::DOMAINS::ALL,
|
|
.mapping = is_vacuum ? quadrature::MappingKind::kelvin : quadrature::MappingKind::general,
|
|
.trial_order = element.GetOrder(),
|
|
.test_order = element.GetOrder(),
|
|
.coefficient_order = 0,
|
|
.geometry_weight_order = transformation.OrderW()
|
|
};
|
|
|
|
return *f.quadratureFactory->get(query, transformation.GetGeometryType()).integration_rule;
|
|
}
|
|
|
|
mfem::Vector make_full_support_gravity_gradient(const fem::FEM &f) {
|
|
mfem::Vector gravity_gradient(f.gravityFluxFes->GetTrueVSize());
|
|
|
|
for (int i = 0; i < gravity_gradient.Size(); ++i) {
|
|
const double index = static_cast<double>(i + 1);
|
|
gravity_gradient(i) = std::sin(0.37 * index) + 0.31 * std::cos(0.19 * index);
|
|
}
|
|
|
|
return gravity_gradient;
|
|
}
|
|
|
|
mfem::Vector make_stateless_reference_displacement(
|
|
const fem::FEM &f,
|
|
const bool deformed
|
|
) {
|
|
mfem::ParGridFunction displacement(f.displacementFes.get());
|
|
displacement = 0.0;
|
|
|
|
if (deformed) {
|
|
auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) {
|
|
value.SetSize(3);
|
|
value(0) = 0.04 * position(0) + 0.01 * position(1) * position(2);
|
|
value(1) = -0.03 * position(1) + 0.008 * position(0) * position(2);
|
|
value(2) = 0.02 * position(2) - 0.006 * position(0) * position(1);
|
|
};
|
|
|
|
mfem::VectorFunctionCoefficient displacement_coefficient(3, displacement_function);
|
|
displacement.ProjectCoefficient(displacement_coefficient);
|
|
}
|
|
|
|
mfem::Vector displacement_true;
|
|
displacement.GetTrueDofs(displacement_true);
|
|
return displacement_true;
|
|
}
|
|
|
|
std::string_view mapping_status_name(const mapping::MappingStatus status) {
|
|
switch (status) {
|
|
case mapping::MappingStatus::valid:
|
|
return "valid";
|
|
case mapping::MappingStatus::invalid_dimension:
|
|
return "invalid_dimension";
|
|
case mapping::MappingStatus::non_finite_input:
|
|
return "non_finite_input";
|
|
case mapping::MappingStatus::invalid_reference_radius:
|
|
return "invalid_reference_radius";
|
|
case mapping::MappingStatus::at_compactified_infinity:
|
|
return "at_compactified_infinity";
|
|
case mapping::MappingStatus::outside_reference_domain:
|
|
return "outside_reference_domain";
|
|
case mapping::MappingStatus::non_finite_result:
|
|
return "non_finite_result";
|
|
case mapping::MappingStatus::non_positive_determinant:
|
|
return "non_positive_determinant";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
StatelessHDivMassReference evaluate_stateless_hdiv_mass_quadrature_reference(
|
|
const fem::FEM &fem,
|
|
const mfem::Vector &gravity_gradient_true,
|
|
const mfem::Vector &displacement_true
|
|
) {
|
|
MFEM_VERIFY(fem.domainMapperStateless != nullptr, "The stateless domain mapper is unavailable.");
|
|
|
|
MFEM_VERIFY(fem.compactificationFes != nullptr, "The compactification finite-element space is unavailable.");
|
|
|
|
MFEM_VERIFY(fem.compactificationCoordinate != nullptr, "The compactification coordinate is unavailable.");
|
|
|
|
MFEM_VERIFY(
|
|
gravity_gradient_true.Size() == fem.gravityFluxFes->GetTrueVSize(),
|
|
"The gravity-gradient vector has the wrong size."
|
|
);
|
|
|
|
MFEM_VERIFY(
|
|
displacement_true.Size() == fem.displacementFes->GetTrueVSize(),
|
|
"The displacement vector has the wrong size."
|
|
);
|
|
|
|
mfem::Vector gravity_gradient_local;
|
|
mfem::Vector displacement_local;
|
|
|
|
reference_true_to_local(*fem.gravityFluxFes, gravity_gradient_true, gravity_gradient_local);
|
|
|
|
reference_true_to_local(*fem.displacementFes, displacement_true, displacement_local);
|
|
|
|
mfem::Vector total_local(fem.gravityFluxFes->GetVSize());
|
|
|
|
mfem::Vector stellar_local(fem.gravityFluxFes->GetVSize());
|
|
|
|
mfem::Vector vacuum_local(fem.gravityFluxFes->GetVSize());
|
|
|
|
total_local = 0.0;
|
|
stellar_local = 0.0;
|
|
vacuum_local = 0.0;
|
|
|
|
StatelessHDivMassReference reference;
|
|
|
|
mapping::DomainMapperStateless::Workspace workspace(fem.mesh->Dimension());
|
|
|
|
mapping::VolumeMappingContext mapping_context;
|
|
|
|
mfem::Array<int> gravity_dofs;
|
|
mfem::Array<int> displacement_dofs;
|
|
mfem::Array<int> compactification_dofs;
|
|
|
|
mfem::Vector element_gravity_gradient;
|
|
mfem::Vector element_displacement;
|
|
mfem::Vector element_compactification;
|
|
mfem::Vector element_action;
|
|
mfem::Vector quadrature_flux;
|
|
mfem::Vector mapped_quadrature_flux;
|
|
|
|
mfem::DenseMatrix vector_shape;
|
|
mfem::DenseMatrix mapped_mass_tensor;
|
|
|
|
const int vacuum_attribute = fem.domainMapperStateless->GetVacuumElementAttribute();
|
|
|
|
for (int element_id = 0; element_id < fem.mesh->GetNE(); ++element_id) {
|
|
const mfem::FiniteElement &gravity_element = *fem.gravityFluxFes->GetFE(element_id);
|
|
|
|
const mfem::FiniteElement &displacement_element = *fem.displacementFes->GetFE(element_id);
|
|
|
|
const mfem::FiniteElement &compactification_element = *fem.compactificationFes->GetFE(element_id);
|
|
|
|
mfem::ElementTransformation &transformation = *fem.mesh->GetElementTransformation(element_id);
|
|
|
|
const bool is_vacuum = transformation.Attribute == vacuum_attribute;
|
|
|
|
mfem::DofTransformation *gravity_dof_transformation =
|
|
fem.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs);
|
|
|
|
mfem::DofTransformation *displacement_dof_transformation =
|
|
fem.displacementFes->GetElementVDofs(element_id, displacement_dofs);
|
|
|
|
mfem::DofTransformation *compactification_dof_transformation =
|
|
fem.compactificationFes->GetElementDofs(element_id, compactification_dofs);
|
|
|
|
gravity_gradient_local.GetSubVector(gravity_dofs, element_gravity_gradient);
|
|
|
|
displacement_local.GetSubVector(displacement_dofs, element_displacement);
|
|
|
|
fem.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
|
|
|
|
if (gravity_dof_transformation != nullptr) {
|
|
gravity_dof_transformation->InvTransformPrimal(element_gravity_gradient);
|
|
}
|
|
|
|
if (displacement_dof_transformation != nullptr) {
|
|
displacement_dof_transformation->InvTransformPrimal(element_displacement);
|
|
}
|
|
|
|
if (compactification_dof_transformation != nullptr) {
|
|
compactification_dof_transformation->InvTransformPrimal(element_compactification);
|
|
}
|
|
|
|
const mapping::ElementDisplacementData displacement_data =
|
|
mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement);
|
|
|
|
const mapping::ElementCompactificationData compactification_data(
|
|
compactification_element, element_compactification
|
|
);
|
|
|
|
const mapping::ElementMappingData mapping_data{
|
|
.displacement = displacement_data, .compactification = compactification_data
|
|
};
|
|
|
|
const int gravity_dof_count = gravity_element.GetDof();
|
|
|
|
const int dimension = transformation.GetSpaceDim();
|
|
|
|
element_action.SetSize(gravity_dof_count);
|
|
element_action = 0.0;
|
|
|
|
quadrature_flux.SetSize(dimension);
|
|
mapped_quadrature_flux.SetSize(dimension);
|
|
|
|
vector_shape.SetSize(gravity_dof_count, dimension);
|
|
|
|
mapped_mass_tensor.SetSize(dimension, dimension);
|
|
|
|
const mfem::IntegrationRule &integration_rule =
|
|
get_stateless_hdiv_reference_rule(fem, gravity_element, transformation);
|
|
|
|
for (int quadrature_point = 0; quadrature_point < integration_rule.GetNPoints(); ++quadrature_point) {
|
|
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(quadrature_point);
|
|
|
|
transformation.SetIntPoint(&integration_point);
|
|
|
|
const mapping::MappingStatus status = fem.domainMapperStateless->EvaluateVolume(
|
|
mapping_data, transformation, integration_point, workspace, mapping_context
|
|
);
|
|
|
|
MFEM_VERIFY(
|
|
status == mean_field::mapping::MappingStatus::valid,
|
|
"Stateless mapping failed while evaluating the H(div) "
|
|
"quadrature reference."
|
|
<< "\nElement ID = " << element_id << "\nElement attribute = " << transformation.Attribute
|
|
<< "\nQuadrature point = " << quadrature_point
|
|
<< "\nMapping status = " << mapping_status_name(status)
|
|
);
|
|
|
|
gravity_element.CalcVShape(transformation, vector_shape);
|
|
|
|
mean_field::mapping::ComputeHDivMassTensor(mapping_context.mapping, mapped_mass_tensor);
|
|
|
|
// Evaluate B*x at this quadrature point.
|
|
vector_shape.MultTranspose(element_gravity_gradient, quadrature_flux);
|
|
|
|
// Apply the mapped H(div) mass tensor.
|
|
mapped_mass_tensor.Mult(quadrature_flux, mapped_quadrature_flux);
|
|
|
|
const double weight = integration_point.weight * transformation.Weight();
|
|
|
|
// Accumulate B^T*D*B*x directly.
|
|
for (int dof = 0; dof < gravity_dof_count; ++dof) {
|
|
double contribution = 0.0;
|
|
|
|
for (int component = 0; component < dimension; ++component) {
|
|
contribution += vector_shape(dof, component) * mapped_quadrature_flux(component);
|
|
}
|
|
|
|
element_action(dof) += weight * contribution;
|
|
}
|
|
|
|
const double mapping_determinant = mapping_context.mapping.mapping_determinant;
|
|
|
|
MFEM_VERIFY(
|
|
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
|
|
"The quadrature reference encountered an invalid mapping "
|
|
"determinant."
|
|
);
|
|
|
|
if (is_vacuum) {
|
|
reference.minimum_vacuum_determinant =
|
|
std::min(reference.minimum_vacuum_determinant, mapping_determinant);
|
|
|
|
reference.maximum_vacuum_determinant =
|
|
std::max(reference.maximum_vacuum_determinant, mapping_determinant);
|
|
|
|
++reference.vacuum_quadrature_points;
|
|
} else {
|
|
reference.minimum_stellar_determinant =
|
|
std::min(reference.minimum_stellar_determinant, mapping_determinant);
|
|
|
|
reference.maximum_stellar_determinant =
|
|
std::max(reference.maximum_stellar_determinant, mapping_determinant);
|
|
|
|
++reference.stellar_quadrature_points;
|
|
}
|
|
}
|
|
|
|
if (gravity_dof_transformation != nullptr) {
|
|
gravity_dof_transformation->TransformDual(element_action);
|
|
}
|
|
|
|
total_local.AddElementVector(gravity_dofs, element_action);
|
|
|
|
if (is_vacuum) {
|
|
vacuum_local.AddElementVector(gravity_dofs, element_action);
|
|
|
|
++reference.vacuum_elements;
|
|
} else {
|
|
stellar_local.AddElementVector(gravity_dofs, element_action);
|
|
|
|
++reference.stellar_elements;
|
|
}
|
|
}
|
|
|
|
reference_local_to_true(*fem.gravityFluxFes, total_local, reference.total_action);
|
|
|
|
reference_local_to_true(*fem.gravityFluxFes, stellar_local, reference.stellar_action);
|
|
|
|
reference_local_to_true(*fem.gravityFluxFes, vacuum_local, reference.vacuum_action);
|
|
|
|
const MPI_Comm communicator = fem.gravityFluxFes->GetComm();
|
|
|
|
const long long local_counts[4]{
|
|
reference.stellar_elements, reference.vacuum_elements, reference.stellar_quadrature_points,
|
|
reference.vacuum_quadrature_points
|
|
};
|
|
|
|
long long global_counts[4]{};
|
|
|
|
MPI_Allreduce(local_counts, global_counts, 4, MPI_LONG_LONG, MPI_SUM, communicator);
|
|
|
|
reference.stellar_elements = global_counts[0];
|
|
|
|
reference.vacuum_elements = global_counts[1];
|
|
|
|
reference.stellar_quadrature_points = global_counts[2];
|
|
|
|
reference.vacuum_quadrature_points = global_counts[3];
|
|
|
|
const double local_minimums[2]{reference.minimum_stellar_determinant, reference.minimum_vacuum_determinant};
|
|
|
|
const double local_maximums[2]{reference.maximum_stellar_determinant, reference.maximum_vacuum_determinant};
|
|
|
|
double global_minimums[2]{};
|
|
double global_maximums[2]{};
|
|
|
|
MPI_Allreduce(local_minimums, global_minimums, 2, MPI_DOUBLE, MPI_MIN, communicator);
|
|
|
|
MPI_Allreduce(local_maximums, global_maximums, 2, MPI_DOUBLE, MPI_MAX, communicator);
|
|
|
|
reference.minimum_stellar_determinant = global_minimums[0];
|
|
|
|
reference.minimum_vacuum_determinant = global_minimums[1];
|
|
|
|
reference.maximum_stellar_determinant = global_maximums[0];
|
|
|
|
reference.maximum_vacuum_determinant = global_maximums[1];
|
|
|
|
return reference;
|
|
}
|
|
|
|
using gravity_form = blocks::gravity_field_form;
|
|
using gravity_layout = blocks::form_layout<gravity_form>;
|
|
gravity_layout make_gravity_jacobian_layout(const fem::FEM &f) {
|
|
return make_gravity_layout(f);
|
|
}
|
|
|
|
template <int index>
|
|
void set_value_block(
|
|
mfem::Vector &vector,
|
|
const gravity_layout &layout,
|
|
const blocks::value_block<index> block,
|
|
const mfem::Vector &values
|
|
) {
|
|
REQUIRE(values.Size() == layout.size(block));
|
|
for (int i = 0; i < values.Size(); ++i)
|
|
vector(layout.offset(block) + i) = values(i);
|
|
}
|
|
|
|
template <int index>
|
|
mfem::Vector get_value_block(
|
|
const mfem::Vector &vector,
|
|
const gravity_layout &layout,
|
|
const blocks::value_block<index> block
|
|
) {
|
|
mfem::Vector values(layout.size(block));
|
|
for (int i = 0; i < values.Size(); ++i)
|
|
values(i) = vector(layout.offset(block) + i);
|
|
return values;
|
|
}
|
|
|
|
template <int index>
|
|
mfem::Vector get_residual_block(
|
|
const mfem::Vector &vector,
|
|
const gravity_layout &layout,
|
|
const blocks::residual_block<index> block
|
|
) {
|
|
mfem::Vector values(layout.size(block));
|
|
for (int i = 0; i < values.Size(); ++i)
|
|
values(i) = vector(layout.offset(block) + i);
|
|
return values;
|
|
}
|
|
|
|
template <int index>
|
|
void fill_value_block(
|
|
mfem::Vector &vector,
|
|
const gravity_layout &layout,
|
|
const blocks::value_block<index> block,
|
|
const double scale,
|
|
const double phase
|
|
) {
|
|
for (int i = 0; i < layout.size(block); ++i) {
|
|
const double index_value = static_cast<double>(i + 1);
|
|
vector(layout.offset(block) + i) =
|
|
scale * (std::sin(0.31 * index_value + phase) + 0.37 * std::cos(0.17 * index_value - phase));
|
|
}
|
|
}
|
|
|
|
mfem::Vector make_gravity_jacobian_state(
|
|
const fem::FEM &f,
|
|
const gravity_layout &layout,
|
|
const bool deformed
|
|
) {
|
|
mfem::Vector state(layout.value_offsets().Last());
|
|
state = 0.0;
|
|
|
|
fill_value_block(state, layout, density_block, 0.7, 0.11);
|
|
fill_value_block(state, layout, gravity_gradient_block, 0.4, 0.23);
|
|
fill_value_block(state, layout, gravity_potential_block, 0.3, 0.37);
|
|
|
|
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
|
const auto displacement_map = field::make_field_dof_map<field::Displacement, DomainSchema>(*f.displacementFes);
|
|
const mfem::Vector displacement = displacement_map.gather(make_stateless_reference_displacement(f, deformed));
|
|
set_value_block(state, layout, displacement_block, displacement);
|
|
|
|
return state;
|
|
}
|
|
|
|
mfem::Vector make_density_direction(const gravity_layout &layout) {
|
|
mfem::Vector direction(layout.value_offsets().Last());
|
|
direction = 0.0;
|
|
fill_value_block(direction, layout, density_block, 0.13, 0.19);
|
|
return direction;
|
|
}
|
|
|
|
mfem::Vector make_gravity_gradient_direction(const gravity_layout &layout) {
|
|
mfem::Vector direction(layout.value_offsets().Last());
|
|
direction = 0.0;
|
|
fill_value_block(direction, layout, gravity_gradient_block, 0.11, 0.29);
|
|
return direction;
|
|
}
|
|
|
|
mfem::Vector make_gravity_potential_direction(const gravity_layout &layout) {
|
|
mfem::Vector direction(layout.value_offsets().Last());
|
|
direction = 0.0;
|
|
fill_value_block(direction, layout, gravity_potential_block, 0.09, 0.41);
|
|
return direction;
|
|
}
|
|
|
|
mfem::Vector make_combined_fixed_geometry_direction(const gravity_layout &layout) {
|
|
mfem::Vector direction = make_density_direction(layout);
|
|
const mfem::Vector gravity_gradient_direction = make_gravity_gradient_direction(layout);
|
|
const mfem::Vector gravity_potential_direction = make_gravity_potential_direction(layout);
|
|
direction += gravity_gradient_direction;
|
|
direction += gravity_potential_direction;
|
|
return direction;
|
|
}
|
|
|
|
mfem::Vector make_displacement_direction(
|
|
const gravity_layout &layout,
|
|
const mfem::Vector &displacement_direction
|
|
) {
|
|
mfem::Vector direction(layout.value_offsets().Last());
|
|
direction = 0.0;
|
|
set_value_block(direction, layout, displacement_block, displacement_direction);
|
|
return direction;
|
|
}
|
|
|
|
mfem::Vector evaluate_centered_difference(
|
|
operators::GravityFieldOperator &gravity_operator,
|
|
const mfem::Vector &state,
|
|
const mfem::Vector &direction,
|
|
const double step
|
|
) {
|
|
mfem::Vector plus_state(state);
|
|
mfem::Vector minus_state(state);
|
|
plus_state.Add(step, direction);
|
|
minus_state.Add(-step, direction);
|
|
|
|
mfem::Vector plus_residual;
|
|
mfem::Vector minus_residual;
|
|
gravity_operator.Mult(plus_state, plus_residual);
|
|
gravity_operator.Mult(minus_state, minus_residual);
|
|
|
|
mfem::Vector difference(plus_residual);
|
|
difference -= minus_residual;
|
|
difference /= 2.0 * step;
|
|
return difference;
|
|
}
|
|
|
|
struct HdivMassVariationTestFields {
|
|
mfem::Vector gravity_gradient;
|
|
mfem::Vector displacement;
|
|
mfem::Vector displacement_direction_1;
|
|
mfem::Vector displacement_direction_2;
|
|
};
|
|
|
|
double global_relative_error(
|
|
const mfem::Vector &computed,
|
|
const mfem::Vector &reference,
|
|
MPI_Comm communicator
|
|
) {
|
|
REQUIRE(computed.Size() == reference.Size());
|
|
|
|
mfem::Vector difference(computed);
|
|
difference -= reference;
|
|
|
|
return global_norm(difference, communicator) / std::max(global_norm(reference, communicator), 1.0e-14);
|
|
}
|
|
|
|
HdivMassVariationTestFields make_hdiv_mass_variation_test_fields(const fem::FEM &f) {
|
|
const int dimension = f.mesh->Dimension();
|
|
|
|
auto gravity_gradient_function = [](const mfem::Vector &position, mfem::Vector &value) {
|
|
value.SetSize(3);
|
|
value(0) = 0.4 + 0.18 * position(0) - 0.07 * position(1) * position(2);
|
|
value(1) = -0.3 + 0.11 * position(1) + 0.05 * position(0) * position(2);
|
|
value(2) = 0.2 - 0.09 * position(2) + 0.04 * position(0) * position(1);
|
|
};
|
|
|
|
auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) {
|
|
value.SetSize(3);
|
|
value(0) = 0.025 * position(0) + 0.006 * position(1) * position(2);
|
|
value(1) = -0.018 * position(1) + 0.005 * position(0) * position(2);
|
|
value(2) = 0.014 * position(2) + 0.004 * position(0) * position(1);
|
|
};
|
|
|
|
auto displacement_direction_1_function = [](const mfem::Vector &position, mfem::Vector &value) {
|
|
value.SetSize(3);
|
|
value(0) = 0.16 * position(0) + 0.03 * position(1);
|
|
value(1) = -0.11 * position(1) + 0.02 * position(2);
|
|
value(2) = 0.13 * position(2) - 0.025 * position(0);
|
|
};
|
|
|
|
auto displacement_direction_2_function = [](const mfem::Vector &position, mfem::Vector &value) {
|
|
value.SetSize(3);
|
|
value(0) = -0.07 * position(1) + 0.025 * position(2);
|
|
value(1) = 0.09 * position(0) + 0.04 * position(2);
|
|
value(2) = -0.08 * position(2) + 0.03 * position(0) * position(1);
|
|
};
|
|
|
|
mfem::VectorFunctionCoefficient gravity_gradient_coefficient(dimension, gravity_gradient_function);
|
|
mfem::VectorFunctionCoefficient displacement_coefficient(dimension, displacement_function);
|
|
mfem::VectorFunctionCoefficient displacement_direction_1_coefficient(
|
|
dimension, displacement_direction_1_function
|
|
);
|
|
mfem::VectorFunctionCoefficient displacement_direction_2_coefficient(
|
|
dimension, displacement_direction_2_function
|
|
);
|
|
|
|
mfem::ParGridFunction gravity_gradient_grid(f.gravityFluxFes.get());
|
|
mfem::ParGridFunction displacement_grid(f.displacementFes.get());
|
|
mfem::ParGridFunction displacement_direction_1_grid(f.displacementFes.get());
|
|
mfem::ParGridFunction displacement_direction_2_grid(f.displacementFes.get());
|
|
|
|
gravity_gradient_grid.ProjectCoefficient(gravity_gradient_coefficient);
|
|
displacement_grid.ProjectCoefficient(displacement_coefficient);
|
|
displacement_direction_1_grid.ProjectCoefficient(displacement_direction_1_coefficient);
|
|
displacement_direction_2_grid.ProjectCoefficient(displacement_direction_2_coefficient);
|
|
|
|
HdivMassVariationTestFields fields;
|
|
gravity_gradient_grid.GetTrueDofs(fields.gravity_gradient);
|
|
displacement_grid.GetTrueDofs(fields.displacement);
|
|
displacement_direction_1_grid.GetTrueDofs(fields.displacement_direction_1);
|
|
displacement_direction_2_grid.GetTrueDofs(fields.displacement_direction_2);
|
|
|
|
return fields;
|
|
}
|
|
|
|
mfem::Vector centered_hdiv_mass_geometry_difference(
|
|
const fem::FEM &f,
|
|
const mapping::DomainMapperStateless &domain_mapper,
|
|
const mfem::Vector &gravity_gradient,
|
|
const mfem::Vector &displacement,
|
|
const mfem::Vector &displacement_direction,
|
|
const double difference_step
|
|
) {
|
|
mfem::Vector plus_displacement(displacement);
|
|
mfem::Vector minus_displacement(displacement);
|
|
plus_displacement.Add(difference_step, displacement_direction);
|
|
minus_displacement.Add(-difference_step, displacement_direction);
|
|
|
|
mfem::Vector plus_action;
|
|
mfem::Vector minus_action;
|
|
|
|
operators::kernels::apply_mapped_hdiv_mass(f, domain_mapper, gravity_gradient, plus_displacement, plus_action);
|
|
operators::kernels::apply_mapped_hdiv_mass(
|
|
f, domain_mapper, gravity_gradient, minus_displacement, minus_action
|
|
);
|
|
|
|
plus_action -= minus_action;
|
|
plus_action /= 2.0 * difference_step;
|
|
|
|
return plus_action;
|
|
}
|
|
|
|
mfem::Vector make_source_variation_density(const fem::FEM &f) {
|
|
auto density_function = [](const mfem::Vector &position) {
|
|
return 1.2 + 0.16 * position(0) - 0.09 * position(1) + 0.07 * position(2) +
|
|
0.04 * position(0) * position(1);
|
|
};
|
|
|
|
mfem::FunctionCoefficient density_coefficient(density_function);
|
|
mfem::ParGridFunction density_grid(f.densityFes.get());
|
|
mfem::Vector density_true;
|
|
|
|
density_grid.ProjectCoefficient(density_coefficient);
|
|
density_grid.GetTrueDofs(density_true);
|
|
|
|
return density_true;
|
|
}
|
|
|
|
mfem::Vector centered_source_geometry_difference(
|
|
const fem::FEM &f,
|
|
const mapping::DomainMapperStateless &domain_mapper,
|
|
const mfem::Vector &density,
|
|
const mfem::Vector &displacement,
|
|
const mfem::Vector &displacement_direction,
|
|
const double difference_step
|
|
) {
|
|
mfem::Vector plus_displacement(displacement);
|
|
mfem::Vector minus_displacement(displacement);
|
|
plus_displacement.Add(difference_step, displacement_direction);
|
|
minus_displacement.Add(-difference_step, displacement_direction);
|
|
|
|
mfem::Vector plus_action;
|
|
mfem::Vector minus_action;
|
|
|
|
operators::kernels::apply_mapped_source(f, domain_mapper, density, plus_displacement, plus_action);
|
|
operators::kernels::apply_mapped_source(f, domain_mapper, density, minus_displacement, minus_action);
|
|
|
|
plus_action -= minus_action;
|
|
plus_action /= 2.0 * difference_step;
|
|
|
|
return plus_action;
|
|
}
|
|
double global_dot(
|
|
const mfem::Vector &left,
|
|
const mfem::Vector &right,
|
|
MPI_Comm communicator
|
|
) {
|
|
REQUIRE(left.Size() > 0);
|
|
REQUIRE(left.Size() == right.Size());
|
|
|
|
const double local_dot = left * right;
|
|
double global_dot_value = 0.0;
|
|
MPI_Allreduce(&local_dot, &global_dot_value, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
|
|
|
return global_dot_value;
|
|
}
|
|
|
|
mfem::Vector make_secondary_gravity_gradient(const fem::FEM &f) {
|
|
auto gravity_gradient_function = [](const mfem::Vector &position, mfem::Vector &value) {
|
|
value.SetSize(3);
|
|
value(0) = -0.17 + 0.09 * position(1) + 0.03 * position(0) * position(2);
|
|
value(1) = 0.31 - 0.14 * position(0) + 0.05 * position(1) * position(2);
|
|
value(2) = -0.22 + 0.12 * position(2) - 0.04 * position(0) * position(1);
|
|
};
|
|
|
|
mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), gravity_gradient_function);
|
|
mfem::ParGridFunction grid_function(f.gravityFluxFes.get());
|
|
mfem::Vector true_dofs;
|
|
|
|
grid_function.ProjectCoefficient(coefficient);
|
|
grid_function.GetTrueDofs(true_dofs);
|
|
|
|
return true_dofs;
|
|
}
|
|
|
|
mfem::Vector make_secondary_source_density(const fem::FEM &f) {
|
|
auto density_function = [](const mfem::Vector &position) {
|
|
return 0.8 - 0.11 * position(0) + 0.13 * position(1) - 0.06 * position(2) +
|
|
0.03 * position(1) * position(2);
|
|
};
|
|
|
|
mfem::FunctionCoefficient coefficient(density_function);
|
|
mfem::ParGridFunction grid_function(f.densityFes.get());
|
|
mfem::Vector true_dofs;
|
|
|
|
grid_function.ProjectCoefficient(coefficient);
|
|
grid_function.GetTrueDofs(true_dofs);
|
|
|
|
return true_dofs;
|
|
}
|
|
|
|
mfem::Vector make_vacuum_only_density(
|
|
const fem::FEM &f,
|
|
const int vacuum_attribute
|
|
) {
|
|
mfem::ParGridFunction grid_function(f.densityFes.get());
|
|
grid_function = 0.0;
|
|
|
|
mfem::Array<int> element_dofs;
|
|
mfem::Vector element_values;
|
|
|
|
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
|
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
|
|
REQUIRE(transformation != nullptr);
|
|
|
|
if (transformation->Attribute != vacuum_attribute)
|
|
continue;
|
|
|
|
const mfem::FiniteElement &element = *f.densityFes->GetFE(element_id);
|
|
mfem::DofTransformation *dof_transformation = f.densityFes->GetElementDofs(element_id, element_dofs);
|
|
|
|
element_values.SetSize(element.GetDof());
|
|
for (int i = 0; i < element_values.Size(); ++i)
|
|
element_values(i) = 0.9 + 0.01 * static_cast<double>(i);
|
|
|
|
if (dof_transformation != nullptr)
|
|
dof_transformation->TransformPrimal(element_values);
|
|
grid_function.SetSubVector(element_dofs, element_values);
|
|
}
|
|
|
|
mfem::Vector true_dofs;
|
|
grid_function.GetTrueDofs(true_dofs);
|
|
|
|
return true_dofs;
|
|
}
|
|
|
|
template <int index>
|
|
mfem::Vector make_read_only_value_view(
|
|
const mfem::Vector &vector,
|
|
const mfem::Array<int> &offsets,
|
|
const blocks::value_block<index>
|
|
) {
|
|
const int offset = offsets[index];
|
|
const int size = offsets[index + 1] - offset;
|
|
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + offset, size);
|
|
}
|
|
|
|
void check_linearization_context_matches_state(
|
|
const operators::context::gravity_field::GravityFieldLinearizationContext &context,
|
|
const mfem::Vector &state,
|
|
const mfem::Array<int> &state_offsets,
|
|
MPI_Comm communicator
|
|
) {
|
|
using form = blocks::gravity_field_form;
|
|
|
|
constexpr auto density_block = utils::blocks::get_value_block<form>(blocks::density_field.mass_term);
|
|
constexpr auto displacement_block =
|
|
utils::blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
|
|
constexpr auto gravity_gradient_block =
|
|
utils::blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
|
|
|
|
const mfem::Vector density = make_read_only_value_view(state, state_offsets, density_block);
|
|
const mfem::Vector displacement = make_read_only_value_view(state, state_offsets, displacement_block);
|
|
const mfem::Vector gravity_gradient = make_read_only_value_view(state, state_offsets, gravity_gradient_block);
|
|
|
|
CHECK_THAT(
|
|
global_relative_vector_error(
|
|
context.GetDensityTrue(), context.GetDensityMap().scatter(density), communicator
|
|
),
|
|
Catch::Matchers::WithinAbs(0.0, 0.0)
|
|
);
|
|
CHECK_THAT(
|
|
global_relative_vector_error(
|
|
context.GetGeometryContext().GetDisplacementTrue(), context.GetDisplacementMap().scatter(displacement),
|
|
communicator
|
|
),
|
|
Catch::Matchers::WithinAbs(0.0, 0.0)
|
|
);
|
|
CHECK_THAT(
|
|
global_relative_vector_error(
|
|
context.GetGravityGradientTrue(), context.GetGravityGradientMap().scatter(gravity_gradient),
|
|
communicator
|
|
),
|
|
Catch::Matchers::WithinAbs(0.0, 0.0)
|
|
);
|
|
}
|
|
|
|
StatelessHDivMassReference assemble_legacy_hdiv_mass_reference(
|
|
const fem::FEM &f,
|
|
const mfem::Vector &gravity_gradient_true
|
|
) {
|
|
MFEM_VERIFY(f.mesh != nullptr, "The legacy H(div) reference requires a mesh.");
|
|
MFEM_VERIFY(f.gravityFluxFes != nullptr, "The legacy H(div) reference requires the RT finite-element space.");
|
|
MFEM_VERIFY(f.mapping != nullptr, "The legacy H(div) reference requires the legacy domain mapper.");
|
|
MFEM_VERIFY(f.domainMapperStateless != nullptr, "The vacuum attribute is unavailable.");
|
|
|
|
mfem::Vector gravity_gradient_local;
|
|
reference_true_to_local(*f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local);
|
|
|
|
mfem::Vector total_local(f.gravityFluxFes->GetVSize());
|
|
mfem::Vector stellar_local(f.gravityFluxFes->GetVSize());
|
|
mfem::Vector vacuum_local(f.gravityFluxFes->GetVSize());
|
|
|
|
total_local = 0.0;
|
|
stellar_local = 0.0;
|
|
vacuum_local = 0.0;
|
|
|
|
StatelessHDivMassReference reference;
|
|
const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute();
|
|
|
|
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
|
const mfem::FiniteElement &gravity_element = *f.gravityFluxFes->GetFE(element_id);
|
|
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
|
|
|
|
MFEM_VERIFY(
|
|
transformation != nullptr, "The legacy H(div) reference received a null element "
|
|
"transformation."
|
|
);
|
|
|
|
const bool is_vacuum = transformation->Attribute == vacuum_attribute;
|
|
|
|
mfem::Array<int> gravity_dofs;
|
|
f.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs);
|
|
|
|
mfem::Vector element_gravity_gradient;
|
|
gravity_gradient_local.GetSubVector(gravity_dofs, element_gravity_gradient);
|
|
|
|
const int gravity_dof_count = gravity_element.GetDof();
|
|
const int dimension = transformation->GetSpaceDim();
|
|
|
|
mfem::DenseMatrix element_matrix(gravity_dof_count);
|
|
mfem::DenseMatrix vector_shape(gravity_dof_count, dimension);
|
|
mfem::DenseMatrix mapping_jacobian(dimension);
|
|
mfem::DenseMatrix mapped_mass_tensor(dimension);
|
|
|
|
element_matrix = 0.0;
|
|
|
|
const mfem::IntegrationRule &integration_rule =
|
|
get_stateless_hdiv_reference_rule(f, gravity_element, *transformation);
|
|
|
|
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
|
|
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q);
|
|
transformation->SetIntPoint(&integration_point);
|
|
|
|
/*
|
|
* This reproduces the legacy MappedHDivMassCoefficient.
|
|
*
|
|
* In particular, it does not evaluate or otherwise use
|
|
* f.compactificationCoordinate.
|
|
*/
|
|
f.mapping->ComputeJacobian(*transformation, mapping_jacobian);
|
|
|
|
const double mapping_determinant = mapping_jacobian.Det();
|
|
|
|
CAPTURE(element_id, q, transformation->Attribute);
|
|
REQUIRE(std::isfinite(mapping_determinant));
|
|
REQUIRE(mapping_determinant > 0.0);
|
|
|
|
mfem::MultAtB(mapping_jacobian, mapping_jacobian, mapped_mass_tensor);
|
|
mapped_mass_tensor *= 1.0 / std::abs(mapping_determinant);
|
|
|
|
gravity_element.CalcVShape(*transformation, vector_shape);
|
|
|
|
const double weight = integration_point.weight * transformation->Weight();
|
|
|
|
for (int i = 0; i < gravity_dof_count; ++i) {
|
|
for (int j = 0; j < gravity_dof_count; ++j) {
|
|
double entry = 0.0;
|
|
|
|
for (int row = 0; row < dimension; ++row) {
|
|
for (int column = 0; column < dimension; ++column) {
|
|
entry +=
|
|
vector_shape(i, row) * mapped_mass_tensor(row, column) * vector_shape(j, column);
|
|
}
|
|
}
|
|
|
|
element_matrix(i, j) += weight * entry;
|
|
}
|
|
}
|
|
|
|
if (is_vacuum) {
|
|
++reference.vacuum_quadrature_points;
|
|
} else {
|
|
++reference.stellar_quadrature_points;
|
|
}
|
|
}
|
|
|
|
mfem::Vector element_action(gravity_dof_count);
|
|
element_matrix.Mult(element_gravity_gradient, element_action);
|
|
|
|
total_local.AddElementVector(gravity_dofs, element_action);
|
|
|
|
if (is_vacuum) {
|
|
vacuum_local.AddElementVector(gravity_dofs, element_action);
|
|
++reference.vacuum_elements;
|
|
} else {
|
|
stellar_local.AddElementVector(gravity_dofs, element_action);
|
|
++reference.stellar_elements;
|
|
}
|
|
}
|
|
|
|
reference_local_to_true(*f.gravityFluxFes, total_local, reference.total_action);
|
|
reference_local_to_true(*f.gravityFluxFes, stellar_local, reference.stellar_action);
|
|
reference_local_to_true(*f.gravityFluxFes, vacuum_local, reference.vacuum_action);
|
|
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
const long long local_counts[4]{
|
|
reference.stellar_elements, reference.vacuum_elements, reference.stellar_quadrature_points,
|
|
reference.vacuum_quadrature_points
|
|
};
|
|
|
|
long long global_counts[4]{};
|
|
|
|
MPI_Allreduce(local_counts, global_counts, 4, MPI_LONG_LONG, MPI_SUM, communicator);
|
|
|
|
reference.stellar_elements = global_counts[0];
|
|
reference.vacuum_elements = global_counts[1];
|
|
reference.stellar_quadrature_points = global_counts[2];
|
|
reference.vacuum_quadrature_points = global_counts[3];
|
|
|
|
return reference;
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Operator Mult Preserves Zero State And Static Blocks",
|
|
tags::gravity_operator_unit
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const blocks::form_layout<form> layout = make_gravity_layout(f);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
mfem::Vector state(layout.value_offsets().Last());
|
|
mfem::Vector residual;
|
|
state = 0.0;
|
|
constexpr operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
gravity_operator.Mult(state, residual);
|
|
|
|
REQUIRE(residual.Size() == layout.residual_offsets().Last());
|
|
CHECK_THAT(residual.Norml2(), WithinAbs(0.0, 1.0e-14));
|
|
|
|
const mfem::Vector gravity_potential = make_test_vector(layout.size(gravity_potential_block), 0.31);
|
|
set_block(state, layout.value_offsets(), gravity_potential_block, gravity_potential);
|
|
|
|
gravity_operator.Mult(state, residual);
|
|
|
|
const mfem::Vector gradient_residual =
|
|
get_block(residual, layout.residual_offsets(), gravity_gradient_residual_block);
|
|
const mfem::Vector poisson_residual =
|
|
get_block(residual, layout.residual_offsets(), gravity_poisson_residual_block);
|
|
|
|
mfem::Vector expected_gradient_residual(layout.size(gravity_gradient_residual_block));
|
|
f.gravityContext.BT->Mult(gravity_potential, expected_gradient_residual);
|
|
|
|
CHECK_THAT(relative_difference(gradient_residual, expected_gradient_residual), WithinAbs(0.0, 1.0e-13));
|
|
CHECK_THAT(poisson_residual.Norml2(), WithinAbs(0.0, 1.0e-14));
|
|
|
|
state = 0.0;
|
|
|
|
const mfem::Vector gravity_gradient = make_test_vector(layout.size(gravity_gradient_block), 0.73);
|
|
set_block(state, layout.value_offsets(), gravity_gradient_block, gravity_gradient);
|
|
|
|
gravity_operator.Mult(state, residual);
|
|
|
|
const mfem::Vector gradient_only_residual =
|
|
get_block(residual, layout.residual_offsets(), gravity_gradient_residual_block);
|
|
const mfem::Vector poisson_only_residual =
|
|
get_block(residual, layout.residual_offsets(), gravity_poisson_residual_block);
|
|
|
|
mfem::Vector expected_poisson_residual(layout.size(gravity_poisson_residual_block));
|
|
f.gravityContext.b_form->Mult(gravity_gradient, expected_poisson_residual);
|
|
|
|
CHECK(gradient_only_residual.Norml2() > 0.0);
|
|
CHECK_THAT(relative_difference(poisson_only_residual, expected_poisson_residual), WithinAbs(0.0, 1.0e-13));
|
|
|
|
const double adjoint_lhs = gravity_gradient * expected_gradient_residual;
|
|
const double adjoint_rhs = gravity_potential * expected_poisson_residual;
|
|
const double adjoint_scale = std::max({std::abs(adjoint_lhs), std::abs(adjoint_rhs), 1.0e-14});
|
|
|
|
CHECK_THAT(std::abs(adjoint_lhs - adjoint_rhs) / adjoint_scale, WithinAbs(0.0, 1.0e-12));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Operator Mult Applies Stellar Source With Correct Sign",
|
|
tags::gravity_operator_unit
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const blocks::form_layout<form> layout = make_gravity_layout(f);
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
mfem::Vector state(layout.value_offsets().Last());
|
|
mfem::Vector residual;
|
|
state = 0.0;
|
|
constexpr operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
const mfem::Vector density = linearization_context.GetDensityMap().gather(make_constant_density(f, 1.0));
|
|
set_block(state, layout.value_offsets(), density_block, density);
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
gravity_operator.Mult(state, residual);
|
|
|
|
const mfem::Vector gradient_residual =
|
|
get_block(residual, layout.residual_offsets(), gravity_gradient_residual_block);
|
|
const mfem::Vector poisson_residual =
|
|
get_block(residual, layout.residual_offsets(), gravity_poisson_residual_block);
|
|
|
|
CHECK_THAT(gradient_residual.Norml2(), WithinAbs(0.0, 1.0e-14));
|
|
CHECK(poisson_residual.Norml2() > 0.0);
|
|
|
|
const mfem::Vector constant_test = make_constant_density(f, 1.0);
|
|
const double integrated_source_residual = constant_test * poisson_residual;
|
|
|
|
INFO("Integrated Poisson source residual = " << integrated_source_residual);
|
|
CHECK(integrated_source_residual < 0.0);
|
|
|
|
mfem::Vector doubled_state(state);
|
|
mfem::Vector doubled_density(density);
|
|
doubled_density *= 2.0;
|
|
set_block(doubled_state, layout.value_offsets(), density_block, doubled_density);
|
|
|
|
mfem::Vector doubled_residual;
|
|
gravity_operator.Mult(doubled_state, doubled_residual);
|
|
|
|
mfem::Vector expected_doubled_residual(residual);
|
|
expected_doubled_residual *= 2.0;
|
|
|
|
CHECK_THAT(relative_difference(doubled_residual, expected_doubled_residual), WithinAbs(0.0, 1.0e-12));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Operator Mult Ignores Vacuum Density",
|
|
tags::gravity_operator_unit
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const blocks::form_layout<form> layout = make_gravity_layout(f);
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
mfem::Vector state(layout.value_offsets().Last());
|
|
mfem::Vector residual;
|
|
state = 0.0;
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
const mfem::Vector vacuum_density_true = make_vacuum_density(f, 1.0);
|
|
REQUIRE(vacuum_density_true.Norml2() > 0.0);
|
|
const mfem::Vector vacuum_density = linearization_context.GetDensityMap().gather(vacuum_density_true);
|
|
|
|
set_block(state, layout.value_offsets(), density_block, vacuum_density);
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
gravity_operator.Mult(state, residual);
|
|
|
|
INFO("Vacuum-source residual norm = " << residual.Norml2());
|
|
CHECK_THAT(residual.Norml2(), WithinAbs(0.0, 1.0e-13));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Operator Mult Produces A Symmetric Positive Hdiv Mass "
|
|
"Action",
|
|
tags::gravity_operator_unit
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const blocks::form_layout<form> layout = make_gravity_layout(f);
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
const mfem::Vector displacement = linearization_context.GetDisplacementMap().gather(make_displacement(f));
|
|
const mfem::Vector gravity_gradient_a = make_test_vector(layout.size(gravity_gradient_block), 0.27);
|
|
const mfem::Vector gravity_gradient_b = make_test_vector(layout.size(gravity_gradient_block), 1.13);
|
|
|
|
mfem::Vector state_a(layout.value_offsets().Last());
|
|
mfem::Vector state_b(layout.value_offsets().Last());
|
|
mfem::Vector residual_a;
|
|
mfem::Vector residual_b;
|
|
|
|
state_a = 0.0;
|
|
state_b = 0.0;
|
|
|
|
set_block(state_a, layout.value_offsets(), displacement_block, displacement);
|
|
set_block(state_a, layout.value_offsets(), gravity_gradient_block, gravity_gradient_a);
|
|
set_block(state_b, layout.value_offsets(), displacement_block, displacement);
|
|
set_block(state_b, layout.value_offsets(), gravity_gradient_block, gravity_gradient_b);
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state_a, revisions);
|
|
|
|
gravity_operator.Mult(state_a, residual_a);
|
|
gravity_operator.Mult(state_b, residual_b);
|
|
|
|
const mfem::Vector mass_action_a =
|
|
get_block(residual_a, layout.residual_offsets(), gravity_gradient_residual_block);
|
|
const mfem::Vector mass_action_b =
|
|
get_block(residual_b, layout.residual_offsets(), gravity_gradient_residual_block);
|
|
|
|
const double energy_a = gravity_gradient_a * mass_action_a;
|
|
const double energy_b = gravity_gradient_b * mass_action_b;
|
|
const double cross_ab = gravity_gradient_a * mass_action_b;
|
|
const double cross_ba = gravity_gradient_b * mass_action_a;
|
|
const double symmetry_scale = std::max({std::abs(cross_ab), std::abs(cross_ba), 1.0e-14});
|
|
|
|
INFO("Mapped H(div) energy A = " << energy_a);
|
|
INFO("Mapped H(div) energy B = " << energy_b);
|
|
INFO("Mapped H(div) cross action A-M-B = " << cross_ab);
|
|
INFO("Mapped H(div) cross action B-M-A = " << cross_ba);
|
|
|
|
CHECK(std::isfinite(energy_a));
|
|
CHECK(std::isfinite(energy_b));
|
|
CHECK(energy_a > 0.0);
|
|
CHECK(energy_b > 0.0);
|
|
CHECK_THAT(std::abs(cross_ab - cross_ba) / symmetry_scale, WithinAbs(0.0, 1.0e-11));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Operator Mult Is Additive In Fields At Fixed Displacement",
|
|
tags::gravity_operator_unit
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const blocks::form_layout<form> layout = make_gravity_layout(f);
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
const mfem::Vector displacement = linearization_context.GetDisplacementMap().gather(make_displacement(f));
|
|
const mfem::Vector density = linearization_context.GetDensityMap().gather(make_constant_density(f, 0.73));
|
|
const mfem::Vector gravity_gradient = make_test_vector(layout.size(gravity_gradient_block), 0.41);
|
|
const mfem::Vector gravity_potential = make_test_vector(layout.size(gravity_potential_block), 0.89);
|
|
|
|
mfem::Vector displacement_state(layout.value_offsets().Last());
|
|
mfem::Vector density_state(layout.value_offsets().Last());
|
|
mfem::Vector gradient_state(layout.value_offsets().Last());
|
|
mfem::Vector potential_state(layout.value_offsets().Last());
|
|
mfem::Vector complete_state(layout.value_offsets().Last());
|
|
|
|
displacement_state = 0.0;
|
|
density_state = 0.0;
|
|
gradient_state = 0.0;
|
|
potential_state = 0.0;
|
|
complete_state = 0.0;
|
|
|
|
set_block(displacement_state, layout.value_offsets(), displacement_block, displacement);
|
|
|
|
set_block(density_state, layout.value_offsets(), displacement_block, displacement);
|
|
set_block(density_state, layout.value_offsets(), density_block, density);
|
|
|
|
set_block(gradient_state, layout.value_offsets(), displacement_block, displacement);
|
|
set_block(gradient_state, layout.value_offsets(), gravity_gradient_block, gravity_gradient);
|
|
|
|
set_block(potential_state, layout.value_offsets(), displacement_block, displacement);
|
|
set_block(potential_state, layout.value_offsets(), gravity_potential_block, gravity_potential);
|
|
|
|
set_block(complete_state, layout.value_offsets(), displacement_block, displacement);
|
|
set_block(complete_state, layout.value_offsets(), density_block, density);
|
|
set_block(complete_state, layout.value_offsets(), gravity_gradient_block, gravity_gradient);
|
|
set_block(complete_state, layout.value_offsets(), gravity_potential_block, gravity_potential);
|
|
|
|
mfem::Vector displacement_residual;
|
|
mfem::Vector density_residual;
|
|
mfem::Vector gradient_residual;
|
|
mfem::Vector potential_residual;
|
|
mfem::Vector complete_residual;
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(complete_state, revisions);
|
|
|
|
gravity_operator.Mult(displacement_state, displacement_residual);
|
|
gravity_operator.Mult(density_state, density_residual);
|
|
gravity_operator.Mult(gradient_state, gradient_residual);
|
|
gravity_operator.Mult(potential_state, potential_residual);
|
|
gravity_operator.Mult(complete_state, complete_residual);
|
|
|
|
CHECK_THAT(displacement_residual.Norml2(), WithinAbs(0.0, 1.0e-14));
|
|
|
|
mfem::Vector additive_residual(density_residual);
|
|
additive_residual += gradient_residual;
|
|
additive_residual += potential_residual;
|
|
|
|
CHECK_THAT(relative_difference(complete_residual, additive_residual), WithinAbs(0.0, 1.0e-11));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Operator Stellar Hdiv Mass Matches Legacy Assembled Mass",
|
|
tags::gravity_legacy
|
|
) {
|
|
constexpr double parity_tolerance = 1.0e-10;
|
|
constexpr double vacuum_tolerance = 1.0e-13;
|
|
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.mapping != nullptr);
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const blocks::form_layout<form> layout = make_gravity_layout(f);
|
|
const mfem::Vector gravity_gradient = make_core_supported_gravity_gradient(f);
|
|
const mfem::Vector deformed_displacement = make_displacement(f);
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
REQUIRE(global_norm(gravity_gradient, communicator) > 0.0);
|
|
|
|
mfem::ParGridFunction local_gravity_gradient(f.gravityFluxFes.get());
|
|
local_gravity_gradient.SetFromTrueDofs(gravity_gradient);
|
|
|
|
double local_maximum_vacuum_dof = 0.0;
|
|
mfem::Array<int> element_vdofs;
|
|
mfem::Vector element_values;
|
|
|
|
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
|
if (f.mesh->GetAttribute(element_id) != f.domainMapperStateless->GetVacuumElementAttribute())
|
|
continue;
|
|
|
|
f.gravityFluxFes->GetElementVDofs(element_id, element_vdofs);
|
|
local_gravity_gradient.GetSubVector(element_vdofs, element_values);
|
|
|
|
if (element_values.Size() > 0)
|
|
local_maximum_vacuum_dof = std::max(local_maximum_vacuum_dof, element_values.Normlinf());
|
|
}
|
|
|
|
double global_maximum_vacuum_dof = 0.0;
|
|
MPI_Allreduce(&local_maximum_vacuum_dof, &global_maximum_vacuum_dof, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
|
|
|
INFO("Maximum gravity-gradient DOF on vacuum elements = " << global_maximum_vacuum_dof);
|
|
REQUIRE_THAT(global_maximum_vacuum_dof, WithinAbs(0.0, vacuum_tolerance));
|
|
|
|
for (const bool use_deformation : std::array{false, true}) {
|
|
DYNAMIC_SECTION("Geometry = " << (use_deformation ? "deformed" : "identity")) {
|
|
mfem::Vector displacement(layout.size(displacement_block));
|
|
displacement = 0.0;
|
|
if (use_deformation)
|
|
displacement = deformed_displacement;
|
|
|
|
mfem::ParGridFunction legacy_displacement(f.displacementFes.get());
|
|
legacy_displacement.SetFromTrueDofs(displacement);
|
|
f.mapping->SetDisplacement(legacy_displacement);
|
|
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.gravityContext.m_form != nullptr);
|
|
REQUIRE(f.gravityContext.b_form != nullptr);
|
|
REQUIRE(f.gravityContext.BT != nullptr);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
mfem::Vector state(layout.value_offsets().Last());
|
|
mfem::Vector matrix_free_residual;
|
|
state = 0.0;
|
|
|
|
set_block(state, layout.value_offsets(), displacement_block, displacement);
|
|
set_block(state, layout.value_offsets(), gravity_gradient_block, gravity_gradient);
|
|
|
|
constexpr operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1},
|
|
.displacement = {1},
|
|
.density = {1},
|
|
.gravity_gradient = {1},
|
|
.gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
gravity_operator.Mult(state, matrix_free_residual);
|
|
|
|
const mfem::Vector matrix_free_mass_action =
|
|
get_block(matrix_free_residual, layout.residual_offsets(), gravity_gradient_residual_block);
|
|
|
|
mfem::Vector legacy_mass_action(f.gravityFluxFes->GetTrueVSize());
|
|
f.gravityContext.m_form->Mult(gravity_gradient, legacy_mass_action);
|
|
|
|
const double matrix_free_mass_norm = global_norm(matrix_free_mass_action, communicator);
|
|
const double legacy_mass_norm = global_norm(legacy_mass_action, communicator);
|
|
const double mass_error =
|
|
global_relative_difference(matrix_free_mass_action, legacy_mass_action, communicator);
|
|
|
|
mfem::Vector mass_difference(matrix_free_mass_action);
|
|
mass_difference -= legacy_mass_action;
|
|
|
|
INFO("Geometry = " << (use_deformation ? "deformed" : "identity"));
|
|
INFO("Matrix-free stellar mass norm = " << matrix_free_mass_norm);
|
|
INFO("Legacy stellar mass norm = " << legacy_mass_norm);
|
|
INFO("Absolute stellar mass difference norm = " << global_norm(mass_difference, communicator));
|
|
INFO("Relative stellar mass-action error = " << mass_error);
|
|
|
|
REQUIRE(matrix_free_mass_norm > 0.0);
|
|
REQUIRE(legacy_mass_norm > 0.0);
|
|
CHECK_THAT(mass_error, WithinAbs(0.0, parity_tolerance));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Operator Hdiv Mass Action Matches Stateless Quadrature "
|
|
"Reference",
|
|
tags::gravity_operator_integration
|
|
) {
|
|
const utils::Args args = test_utils::setup_args();
|
|
|
|
fem::FEM fem = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
fem.mapping->ResetDisplacement();
|
|
|
|
physics::update_stiffness_matrix(fem);
|
|
|
|
constexpr auto displacement_block = mean_field::utils::blocks::get_value_block<blocks::gravity_field_form>(
|
|
blocks::displacement_field.geometry_term
|
|
);
|
|
|
|
constexpr auto gravity_gradient_block =
|
|
mean_field::utils::blocks::get_value_block<blocks::gravity_field_form>(blocks::gravity_field.gradient_term);
|
|
|
|
constexpr auto gravity_gradient_residual_block =
|
|
mean_field::utils::blocks::get_residual_block<blocks::gravity_field_form>(blocks::gravity_field.gradient_term);
|
|
|
|
const blocks::form_layout<blocks::gravity_field_form> layout = make_gravity_layout(fem);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
fem, *fem.domainMapperStateless
|
|
);
|
|
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
fem, *fem.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
|
|
operators::GravityFieldOperator gravity_operator(
|
|
fem, *fem.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
const MPI_Comm communicator = fem.gravityFluxFes->GetComm();
|
|
|
|
const mfem::Vector gravity_gradient_true = make_full_support_gravity_gradient(fem);
|
|
|
|
for (const bool deformed : {false, true}) {
|
|
DYNAMIC_SECTION("Geometry = " << (deformed ? "deformed" : "identity")) {
|
|
CAPTURE(deformed);
|
|
|
|
const mfem::Vector displacement_true = make_stateless_reference_displacement(fem, deformed);
|
|
|
|
mfem::Vector state(layout.value_offsets().Last());
|
|
state = 0.0;
|
|
|
|
for (int i = 0; i < gravity_gradient_true.Size(); ++i) {
|
|
state(layout.offset(gravity_gradient_block) + i) = gravity_gradient_true(i);
|
|
}
|
|
|
|
for (int i = 0; i < displacement_true.Size(); ++i) {
|
|
state(layout.offset(displacement_block) + i) = displacement_true(i);
|
|
}
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1},
|
|
.displacement = {1},
|
|
.density = {1},
|
|
.gravity_gradient = {1},
|
|
.gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
|
|
mfem::Vector residual;
|
|
|
|
gravity_operator.Mult(state, residual);
|
|
|
|
mfem::Vector operator_action(layout.size(gravity_gradient_residual_block));
|
|
|
|
for (int i = 0; i < operator_action.Size(); ++i) {
|
|
operator_action(i) = residual(layout.offset(gravity_gradient_residual_block) + i);
|
|
}
|
|
|
|
const StatelessHDivMassReference reference =
|
|
evaluate_stateless_hdiv_mass_quadrature_reference(fem, gravity_gradient_true, displacement_true);
|
|
|
|
mfem::Vector decomposed_reference(reference.stellar_action);
|
|
|
|
decomposed_reference += reference.vacuum_action;
|
|
|
|
mfem::Vector action_difference(operator_action);
|
|
|
|
action_difference -= reference.total_action;
|
|
|
|
const double operator_norm = global_vector_norm(operator_action, communicator);
|
|
|
|
const double reference_norm = global_vector_norm(reference.total_action, communicator);
|
|
|
|
const double stellar_action_norm = global_vector_norm(reference.stellar_action, communicator);
|
|
|
|
const double vacuum_action_norm = global_vector_norm(reference.vacuum_action, communicator);
|
|
|
|
const double absolute_action_error = global_vector_norm(action_difference, communicator);
|
|
|
|
const double relative_action_error =
|
|
global_relative_vector_error(operator_action, reference.total_action, communicator);
|
|
|
|
const double decomposition_error =
|
|
global_relative_vector_error(reference.total_action, decomposed_reference, communicator);
|
|
|
|
const double operator_energy = global_vector_dot(gravity_gradient_true, operator_action, communicator);
|
|
|
|
const double reference_energy =
|
|
global_vector_dot(gravity_gradient_true, reference.total_action, communicator);
|
|
|
|
const double stellar_energy =
|
|
global_vector_dot(gravity_gradient_true, reference.stellar_action, communicator);
|
|
|
|
const double vacuum_energy =
|
|
global_vector_dot(gravity_gradient_true, reference.vacuum_action, communicator);
|
|
|
|
const double relative_energy_error =
|
|
std::abs(operator_energy - reference_energy) /
|
|
std::max(std::abs(reference_energy), std::numeric_limits<double>::epsilon());
|
|
|
|
const double vacuum_action_fraction =
|
|
vacuum_action_norm / std::max(reference_norm, std::numeric_limits<double>::epsilon());
|
|
|
|
const double vacuum_energy_fraction = vacuum_energy / reference_energy;
|
|
|
|
const double stellar_vacuum_dot =
|
|
global_vector_dot(reference.stellar_action, reference.vacuum_action, communicator);
|
|
|
|
const double stellar_vacuum_alignment =
|
|
stellar_vacuum_dot /
|
|
std::max(stellar_action_norm * vacuum_action_norm, std::numeric_limits<double>::epsilon());
|
|
|
|
INFO("Geometry = " << (deformed ? "deformed" : "identity"));
|
|
|
|
INFO("Stellar elements = " << reference.stellar_elements);
|
|
|
|
INFO("Vacuum elements = " << reference.vacuum_elements);
|
|
|
|
INFO("Stellar quadrature points = " << reference.stellar_quadrature_points);
|
|
|
|
INFO("Vacuum quadrature points = " << reference.vacuum_quadrature_points);
|
|
|
|
INFO(
|
|
"Stellar mapping determinant range = [" << reference.minimum_stellar_determinant << ", "
|
|
<< reference.maximum_stellar_determinant << "]"
|
|
);
|
|
|
|
INFO(
|
|
"Vacuum mapping determinant range = [" << reference.minimum_vacuum_determinant << ", "
|
|
<< reference.maximum_vacuum_determinant << "]"
|
|
);
|
|
|
|
INFO("Operator mass-action norm = " << operator_norm);
|
|
|
|
INFO("Stateless quadrature-reference norm = " << reference_norm);
|
|
|
|
INFO("Stellar action norm = " << stellar_action_norm);
|
|
|
|
INFO("Vacuum action norm = " << vacuum_action_norm);
|
|
|
|
INFO("Vacuum action fraction = " << vacuum_action_fraction);
|
|
|
|
INFO("Stellar-vacuum action alignment = " << stellar_vacuum_alignment);
|
|
|
|
INFO("Absolute action error = " << absolute_action_error);
|
|
|
|
INFO("Relative action error = " << relative_action_error);
|
|
|
|
INFO("Domain-decomposition error = " << decomposition_error);
|
|
|
|
INFO("Operator quadratic energy = " << operator_energy);
|
|
|
|
INFO("Quadrature-reference energy = " << reference_energy);
|
|
|
|
INFO("Stellar energy = " << stellar_energy);
|
|
|
|
INFO("Vacuum energy = " << vacuum_energy);
|
|
|
|
INFO("Vacuum energy fraction = " << vacuum_energy_fraction);
|
|
|
|
INFO("Relative energy error = " << relative_energy_error);
|
|
|
|
REQUIRE(reference.stellar_elements > 0);
|
|
|
|
REQUIRE(reference.vacuum_elements > 0);
|
|
|
|
REQUIRE(reference.stellar_quadrature_points > 0);
|
|
|
|
REQUIRE(reference.vacuum_quadrature_points > 0);
|
|
|
|
REQUIRE(reference.minimum_stellar_determinant > 0.0);
|
|
|
|
REQUIRE(reference.minimum_vacuum_determinant > 0.0);
|
|
|
|
REQUIRE(reference_energy > 0.0);
|
|
|
|
REQUIRE(stellar_energy > 0.0);
|
|
|
|
REQUIRE(vacuum_energy > 0.0);
|
|
|
|
constexpr double decomposition_tolerance = 5.0e-14;
|
|
|
|
constexpr double action_tolerance = 1.0e-11;
|
|
|
|
constexpr double energy_tolerance = 1.0e-11;
|
|
|
|
CHECK_THAT(decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance));
|
|
|
|
CHECK_THAT(relative_action_error, Catch::Matchers::WithinAbs(0.0, action_tolerance));
|
|
|
|
CHECK_THAT(relative_energy_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Jacobian Fixed Geometry Blocks Match Centered Differences",
|
|
tags::gravity_operator_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
f.mapping->ResetDisplacement();
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
mfem::Vector state(layout.value_offsets().Last());
|
|
state = 0.0;
|
|
|
|
const mfem::Vector displacement =
|
|
linearization_context.GetDisplacementMap().gather(make_stateless_reference_displacement(f, true));
|
|
set_value_block(state, layout, displacement_block, displacement);
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
mfem::Operator &gradient = gravity_operator.GetGradient(state);
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
struct DirectionCase {
|
|
std::string name;
|
|
mfem::Vector direction;
|
|
bool expect_gradient_residual;
|
|
bool expect_poisson_residual;
|
|
};
|
|
|
|
std::vector<DirectionCase> cases;
|
|
cases.push_back({"density", make_density_direction(layout), false, true});
|
|
cases.push_back({"gravity gradient", make_gravity_gradient_direction(layout), true, true});
|
|
cases.push_back({"gravity potential", make_gravity_potential_direction(layout), true, false});
|
|
|
|
for (const DirectionCase &direction_case : cases) {
|
|
DYNAMIC_SECTION("Direction = " << direction_case.name) {
|
|
constexpr double difference_step = 1.0e-6;
|
|
|
|
mfem::Vector jacobian_action;
|
|
gradient.Mult(direction_case.direction, jacobian_action);
|
|
|
|
const mfem::Vector finite_difference =
|
|
evaluate_centered_difference(gravity_operator, state, direction_case.direction, difference_step);
|
|
|
|
const mfem::Vector gradient_action =
|
|
get_residual_block(jacobian_action, layout, gravity_gradient_residual_block);
|
|
const mfem::Vector poisson_action =
|
|
get_residual_block(jacobian_action, layout, gravity_poisson_residual_block);
|
|
|
|
const double relative_error =
|
|
global_relative_vector_error(jacobian_action, finite_difference, communicator);
|
|
const double gradient_norm = global_vector_norm(gradient_action, communicator);
|
|
const double poisson_norm = global_vector_norm(poisson_action, communicator);
|
|
|
|
INFO("Direction = " << direction_case.name);
|
|
INFO("Jacobian action norm = " << global_vector_norm(jacobian_action, communicator));
|
|
INFO("Finite-difference action norm = " << global_vector_norm(finite_difference, communicator));
|
|
INFO("Gradient residual action norm = " << gradient_norm);
|
|
INFO("Poisson residual action norm = " << poisson_norm);
|
|
INFO("Relative Jacobian error = " << relative_error);
|
|
|
|
constexpr double jacobian_tolerance = 2.0e-8;
|
|
constexpr double zero_tolerance = 1.0e-13;
|
|
|
|
CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, jacobian_tolerance));
|
|
|
|
if (direction_case.expect_gradient_residual) {
|
|
CHECK(gradient_norm > zero_tolerance);
|
|
} else {
|
|
CHECK_THAT(gradient_norm, Catch::Matchers::WithinAbs(0.0, zero_tolerance));
|
|
}
|
|
|
|
if (direction_case.expect_poisson_residual) {
|
|
CHECK(poisson_norm > zero_tolerance);
|
|
} else {
|
|
CHECK_THAT(poisson_norm, Catch::Matchers::WithinAbs(0.0, zero_tolerance));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
TEST_CASE(
|
|
"Gravity Field Jacobian Combined Fixed Geometry Direction Matches Centered "
|
|
"Differences",
|
|
tags::gravity_operator_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
f.mapping->ResetDisplacement();
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
const mfem::Vector state = make_gravity_jacobian_state(f, layout, true);
|
|
const mfem::Vector direction = make_combined_fixed_geometry_direction(layout);
|
|
|
|
operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
gravity_operator.Prepare(state, revisions);
|
|
|
|
mfem::Operator &gradient = gravity_operator.GetGradient(state);
|
|
mfem::Vector jacobian_action;
|
|
gradient.Mult(direction, jacobian_action);
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
double best_error = std::numeric_limits<double>::infinity();
|
|
|
|
for (const double step : std::array{1.0e-4, 1.0e-6, 1.0e-7}) {
|
|
const mfem::Vector finite_difference = evaluate_centered_difference(gravity_operator, state, direction, step);
|
|
const double relative_error = global_relative_vector_error(jacobian_action, finite_difference, communicator);
|
|
best_error = std::min(best_error, relative_error);
|
|
|
|
INFO("Centered-difference step = " << step);
|
|
INFO("Relative Jacobian error = " << relative_error);
|
|
|
|
CHECK(relative_error < 2.0e-7);
|
|
}
|
|
|
|
INFO("Best relative Jacobian error = " << best_error);
|
|
CHECK(best_error < 2.0e-8);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Jacobian Action Is Linear At Fixed Geometry",
|
|
tags::gravity_operator_unit
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
f.mapping->ResetDisplacement();
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
const mfem::Vector state = make_gravity_jacobian_state(f, layout, false);
|
|
const mfem::Vector direction_a = make_density_direction(layout);
|
|
const mfem::Vector direction_b = make_gravity_gradient_direction(layout);
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
mfem::Operator &gradient = gravity_operator.GetGradient(state);
|
|
|
|
constexpr double scale_a = 1.7;
|
|
constexpr double scale_b = -0.6;
|
|
|
|
mfem::Vector combined_direction(direction_a);
|
|
combined_direction *= scale_a;
|
|
combined_direction.Add(scale_b, direction_b);
|
|
|
|
mfem::Vector action_a;
|
|
mfem::Vector action_b;
|
|
mfem::Vector combined_action;
|
|
|
|
gradient.Mult(direction_a, action_a);
|
|
gradient.Mult(direction_b, action_b);
|
|
gradient.Mult(combined_direction, combined_action);
|
|
|
|
mfem::Vector expected_action(action_a);
|
|
expected_action *= scale_a;
|
|
expected_action.Add(scale_b, action_b);
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
const double relative_error = global_relative_vector_error(combined_action, expected_action, communicator);
|
|
|
|
INFO("Relative Jacobian linearity error = " << relative_error);
|
|
CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, 2.0e-13));
|
|
}
|
|
TEST_CASE(
|
|
"Gravity Field Prepare Updates Shared Linearization Context",
|
|
tags::gravity_operator_unit
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
f.mapping->ResetDisplacement();
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
mfem::Vector state_a = make_gravity_jacobian_state(f, layout, false);
|
|
const mfem::Vector state_b = make_gravity_jacobian_state(f, layout, true);
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions_a{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions_b{
|
|
.discretization = {1}, .displacement = {2}, .density = {2}, .gravity_gradient = {2}, .gravity_potential = {2}
|
|
};
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
gravity_operator.Prepare(state_a, revisions_a);
|
|
|
|
mfem::Operator &returned_gradient_a = gravity_operator.GetGradient(state_a);
|
|
REQUIRE(&returned_gradient_a == static_cast<mfem::Operator *>(&gravity_jacobian));
|
|
|
|
check_linearization_context_matches_state(linearization_context, state_a, layout.value_offsets(), communicator);
|
|
|
|
state_a = 0.0;
|
|
|
|
CHECK(linearization_context.IsPrepared());
|
|
|
|
const double stored_state_norm =
|
|
global_vector_norm(linearization_context.GetDensityTrue(), communicator) +
|
|
global_vector_norm(linearization_context.GetGeometryContext().GetDisplacementTrue(), communicator) +
|
|
global_vector_norm(linearization_context.GetGravityGradientTrue(), communicator);
|
|
|
|
CHECK(stored_state_norm > 0.0);
|
|
|
|
gravity_operator.Prepare(state_b, revisions_b);
|
|
|
|
mfem::Operator &returned_gradient_b = gravity_operator.GetGradient(state_b);
|
|
REQUIRE(&returned_gradient_b == static_cast<mfem::Operator *>(&gravity_jacobian));
|
|
|
|
check_linearization_context_matches_state(linearization_context, state_b, layout.value_offsets(), communicator);
|
|
}
|
|
TEST_CASE(
|
|
"Mapped Hdiv Mass Variation Is Linear In Displacement Direction",
|
|
tags::gravity_kernel_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
mfem::Vector zero_direction(f.displacementFes->GetTrueVSize());
|
|
mfem::Vector zero_gravity_gradient(f.gravityFluxFes->GetTrueVSize());
|
|
zero_direction = 0.0;
|
|
zero_gravity_gradient = 0.0;
|
|
|
|
mfem::Vector zero_direction_action;
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, fields.gravity_gradient, fields.displacement, zero_direction, zero_direction_action
|
|
);
|
|
|
|
INFO("Zero-direction action norm = " << global_norm(zero_direction_action, communicator));
|
|
CHECK(global_norm(zero_direction_action, communicator) < 1.0e-13);
|
|
|
|
mfem::Vector zero_gravity_action;
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, zero_gravity_gradient, fields.displacement, fields.displacement_direction_1,
|
|
zero_gravity_action
|
|
);
|
|
|
|
INFO("Zero-gravity action norm = " << global_norm(zero_gravity_action, communicator));
|
|
CHECK(global_norm(zero_gravity_action, communicator) < 1.0e-13);
|
|
|
|
constexpr double scale_1 = 1.7;
|
|
constexpr double scale_2 = -0.6;
|
|
|
|
mfem::Vector combined_direction(fields.displacement_direction_1);
|
|
combined_direction *= scale_1;
|
|
combined_direction.Add(scale_2, fields.displacement_direction_2);
|
|
|
|
mfem::Vector action_1;
|
|
mfem::Vector action_2;
|
|
mfem::Vector combined_action;
|
|
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_1, action_1
|
|
);
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_2, action_2
|
|
);
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, fields.gravity_gradient, fields.displacement, combined_direction, combined_action
|
|
);
|
|
|
|
mfem::Vector expected_action(action_1);
|
|
expected_action *= scale_1;
|
|
expected_action.Add(scale_2, action_2);
|
|
|
|
const double linearity_error = global_relative_error(combined_action, expected_action, communicator);
|
|
|
|
INFO("Combined action norm = " << global_norm(combined_action, communicator));
|
|
INFO("Expected action norm = " << global_norm(expected_action, communicator));
|
|
INFO("Displacement-direction linearity error = " << linearity_error);
|
|
|
|
CHECK_THAT(linearity_error, Catch::Matchers::WithinAbs(0.0, 2.0e-12));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Mapped Hdiv Mass Variation Matches Centered Geometry Differences",
|
|
tags::gravity_kernel_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
constexpr double difference_step = 1.0e-5;
|
|
constexpr double comparison_tolerance = 2.0e-7;
|
|
|
|
mfem::Vector identity_displacement(f.displacementFes->GetTrueVSize());
|
|
identity_displacement = 0.0;
|
|
|
|
for (const bool deformed : std::array{false, true}) {
|
|
const mfem::Vector &base_displacement = deformed ? fields.displacement : identity_displacement;
|
|
|
|
mfem::Vector analytic_action;
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, fields.gravity_gradient, base_displacement, fields.displacement_direction_1,
|
|
analytic_action
|
|
);
|
|
|
|
const mfem::Vector finite_difference_action = centered_hdiv_mass_geometry_difference(
|
|
f, domain_mapper, fields.gravity_gradient, base_displacement, fields.displacement_direction_1,
|
|
difference_step
|
|
);
|
|
|
|
const double relative_error = global_relative_error(analytic_action, finite_difference_action, communicator);
|
|
|
|
INFO("Geometry = " << (deformed ? "deformed" : "identity"));
|
|
INFO("Analytic variation norm = " << global_norm(analytic_action, communicator));
|
|
INFO("Finite-difference variation norm = " << global_norm(finite_difference_action, communicator));
|
|
INFO("Relative H(div) mass-variation error = " << relative_error);
|
|
|
|
CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, comparison_tolerance));
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Mapped Hdiv Mass Geometry Difference Converges To Analytic Variation",
|
|
tags::gravity_kernel_convergence
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
mfem::Vector analytic_action;
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_1, analytic_action
|
|
);
|
|
|
|
constexpr std::array<double, 3> difference_steps{8.0e-2, 4.0e-2, 2.0e-2};
|
|
std::array<double, difference_steps.size()> errors{};
|
|
|
|
for (std::size_t i = 0; i < difference_steps.size(); ++i) {
|
|
const mfem::Vector finite_difference_action = centered_hdiv_mass_geometry_difference(
|
|
f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_1,
|
|
difference_steps[i]
|
|
);
|
|
|
|
errors[i] = global_relative_error(finite_difference_action, analytic_action, communicator);
|
|
|
|
INFO("Difference step = " << difference_steps[i]);
|
|
INFO("Relative error = " << errors[i]);
|
|
}
|
|
|
|
const double first_observed_order = std::log(errors[0] / errors[1]) / std::log(2.0);
|
|
const double second_observed_order = std::log(errors[1] / errors[2]) / std::log(2.0);
|
|
|
|
INFO("Errors = [" << errors[0] << ", " << errors[1] << ", " << errors[2] << "]");
|
|
INFO("First observed convergence order = " << first_observed_order);
|
|
INFO("Second observed convergence order = " << second_observed_order);
|
|
|
|
CHECK(errors[1] < errors[0]);
|
|
CHECK(errors[2] < errors[1]);
|
|
CHECK(first_observed_order > 1.8);
|
|
CHECK(second_observed_order > 1.8);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Mapped Source Variation Is Linear In Displacement Direction",
|
|
tags::gravity_kernel_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector density = make_source_variation_density(f);
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
mfem::Vector zero_density(f.densityFes->GetTrueVSize());
|
|
mfem::Vector zero_direction(f.displacementFes->GetTrueVSize());
|
|
zero_density = 0.0;
|
|
zero_direction = 0.0;
|
|
|
|
mfem::Vector zero_direction_action;
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, density, fields.displacement, zero_direction, zero_direction_action
|
|
);
|
|
|
|
REQUIRE(zero_direction_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
CHECK(global_norm(zero_direction_action, communicator) < 1.0e-13);
|
|
|
|
mfem::Vector zero_density_action;
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, zero_density, fields.displacement, fields.displacement_direction_1, zero_density_action
|
|
);
|
|
|
|
REQUIRE(zero_density_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
CHECK(global_norm(zero_density_action, communicator) < 1.0e-13);
|
|
|
|
constexpr double scale_1 = 1.4;
|
|
constexpr double scale_2 = -0.7;
|
|
|
|
mfem::Vector combined_direction(fields.displacement_direction_1);
|
|
combined_direction *= scale_1;
|
|
combined_direction.Add(scale_2, fields.displacement_direction_2);
|
|
|
|
mfem::Vector action_1;
|
|
mfem::Vector action_2;
|
|
mfem::Vector combined_action;
|
|
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, density, fields.displacement, fields.displacement_direction_1, action_1
|
|
);
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, density, fields.displacement, fields.displacement_direction_2, action_2
|
|
);
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, density, fields.displacement, combined_direction, combined_action
|
|
);
|
|
|
|
REQUIRE(action_1.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
REQUIRE(action_2.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
REQUIRE(combined_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
|
|
mfem::Vector expected_action(action_1);
|
|
expected_action *= scale_1;
|
|
expected_action.Add(scale_2, action_2);
|
|
|
|
const double linearity_error = global_relative_error(combined_action, expected_action, communicator);
|
|
|
|
INFO("Combined source-variation norm = " << global_norm(combined_action, communicator));
|
|
INFO("Expected source-variation norm = " << global_norm(expected_action, communicator));
|
|
INFO("Source-variation linearity error = " << linearity_error);
|
|
|
|
CHECK_THAT(linearity_error, Catch::Matchers::WithinAbs(0.0, 2.0e-12));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Mapped Source Variation Matches Centered Geometry Differences",
|
|
tags::gravity_kernel_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector density = make_source_variation_density(f);
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
constexpr double difference_step = 1.0e-5;
|
|
constexpr double comparison_tolerance = 2.0e-7;
|
|
|
|
mfem::Vector identity_displacement(f.displacementFes->GetTrueVSize());
|
|
identity_displacement = 0.0;
|
|
|
|
for (const bool deformed : std::array{false, true}) {
|
|
const mfem::Vector &base_displacement = deformed ? fields.displacement : identity_displacement;
|
|
|
|
mfem::Vector analytic_action;
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, density, base_displacement, fields.displacement_direction_1, analytic_action
|
|
);
|
|
|
|
const mfem::Vector finite_difference_action = centered_source_geometry_difference(
|
|
f, domain_mapper, density, base_displacement, fields.displacement_direction_1, difference_step
|
|
);
|
|
|
|
REQUIRE(analytic_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
REQUIRE(finite_difference_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
|
|
const double relative_error = global_relative_error(analytic_action, finite_difference_action, communicator);
|
|
|
|
INFO("Geometry = " << (deformed ? "deformed" : "identity"));
|
|
INFO("Analytic source-variation norm = " << global_norm(analytic_action, communicator));
|
|
INFO("Finite-difference source-variation norm = " << global_norm(finite_difference_action, communicator));
|
|
INFO("Relative source-variation error = " << relative_error);
|
|
|
|
CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, comparison_tolerance));
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Mapped Source Geometry Difference Converges To Analytic Variation",
|
|
tags::gravity_kernel_convergence
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector density = make_source_variation_density(f);
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
mfem::Vector analytic_action;
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, density, fields.displacement, fields.displacement_direction_1, analytic_action
|
|
);
|
|
|
|
REQUIRE(analytic_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
|
|
constexpr std::array<double, 3> difference_steps{8.0e-2, 4.0e-2, 2.0e-2};
|
|
std::array<double, difference_steps.size()> errors{};
|
|
|
|
for (std::size_t i = 0; i < difference_steps.size(); ++i) {
|
|
const mfem::Vector finite_difference_action = centered_source_geometry_difference(
|
|
f, domain_mapper, density, fields.displacement, fields.displacement_direction_1, difference_steps[i]
|
|
);
|
|
|
|
REQUIRE(finite_difference_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
errors[i] = global_relative_error(finite_difference_action, analytic_action, communicator);
|
|
}
|
|
|
|
const double first_observed_order = std::log(errors[0] / errors[1]) / std::log(2.0);
|
|
const double second_observed_order = std::log(errors[1] / errors[2]) / std::log(2.0);
|
|
|
|
INFO("Errors = [" << errors[0] << ", " << errors[1] << ", " << errors[2] << "]");
|
|
INFO("First observed convergence order = " << first_observed_order);
|
|
INFO("Second observed convergence order = " << second_observed_order);
|
|
|
|
CHECK(errors[1] < errors[0]);
|
|
CHECK(errors[2] < errors[1]);
|
|
CHECK(first_observed_order > 1.8);
|
|
CHECK(second_observed_order > 1.8);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Mapped Hdiv Mass Variation Is Symmetric",
|
|
tags::gravity_kernel_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector second_gravity_gradient = make_secondary_gravity_gradient(f);
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
mfem::Vector identity_displacement(f.displacementFes->GetTrueVSize());
|
|
identity_displacement = 0.0;
|
|
|
|
for (const bool deformed : std::array{false, true}) {
|
|
const mfem::Vector &base_displacement = deformed ? fields.displacement : identity_displacement;
|
|
|
|
mfem::Vector first_action;
|
|
mfem::Vector second_action;
|
|
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, fields.gravity_gradient, base_displacement, fields.displacement_direction_1, first_action
|
|
);
|
|
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, second_gravity_gradient, base_displacement, fields.displacement_direction_1, second_action
|
|
);
|
|
|
|
REQUIRE(first_action.Size() == f.gravityFluxFes->GetTrueVSize());
|
|
REQUIRE(second_action.Size() == f.gravityFluxFes->GetTrueVSize());
|
|
|
|
const double left_pairing = global_dot(fields.gravity_gradient, second_action, communicator);
|
|
const double right_pairing = global_dot(second_gravity_gradient, first_action, communicator);
|
|
const double symmetry_error = std::abs(left_pairing - right_pairing) /
|
|
std::max({std::abs(left_pairing), std::abs(right_pairing), 1.0e-14});
|
|
|
|
INFO("Geometry = " << (deformed ? "deformed" : "identity"));
|
|
INFO("g1^T delta_M g2 = " << left_pairing);
|
|
INFO("g2^T delta_M g1 = " << right_pairing);
|
|
INFO("Relative symmetry error = " << symmetry_error);
|
|
|
|
CHECK_THAT(symmetry_error, Catch::Matchers::WithinAbs(0.0, 1.0e-11));
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Mapped Geometry Variations Are Linear In Base Fields",
|
|
tags::gravity_kernel_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector second_gravity_gradient = make_secondary_gravity_gradient(f);
|
|
const mfem::Vector first_density = make_source_variation_density(f);
|
|
const mfem::Vector second_density = make_secondary_source_density(f);
|
|
|
|
constexpr double scale_1 = 1.3;
|
|
constexpr double scale_2 = -0.4;
|
|
|
|
{
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
mfem::Vector combined_gravity_gradient(fields.gravity_gradient);
|
|
combined_gravity_gradient *= scale_1;
|
|
combined_gravity_gradient.Add(scale_2, second_gravity_gradient);
|
|
|
|
mfem::Vector first_action;
|
|
mfem::Vector second_action;
|
|
mfem::Vector combined_action;
|
|
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_1,
|
|
first_action
|
|
);
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, second_gravity_gradient, fields.displacement, fields.displacement_direction_1,
|
|
second_action
|
|
);
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, domain_mapper, combined_gravity_gradient, fields.displacement, fields.displacement_direction_1,
|
|
combined_action
|
|
);
|
|
|
|
REQUIRE(first_action.Size() == f.gravityFluxFes->GetTrueVSize());
|
|
REQUIRE(second_action.Size() == f.gravityFluxFes->GetTrueVSize());
|
|
REQUIRE(combined_action.Size() == f.gravityFluxFes->GetTrueVSize());
|
|
|
|
mfem::Vector expected_action(first_action);
|
|
expected_action *= scale_1;
|
|
expected_action.Add(scale_2, second_action);
|
|
|
|
const double linearity_error = global_relative_error(combined_action, expected_action, communicator);
|
|
|
|
INFO("H(div) base-field linearity error = " << linearity_error);
|
|
CHECK_THAT(linearity_error, Catch::Matchers::WithinAbs(0.0, 3.0e-12));
|
|
}
|
|
|
|
{
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
mfem::Vector combined_density(first_density);
|
|
combined_density *= scale_1;
|
|
combined_density.Add(scale_2, second_density);
|
|
|
|
mfem::Vector first_action;
|
|
mfem::Vector second_action;
|
|
mfem::Vector combined_action;
|
|
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, first_density, fields.displacement, fields.displacement_direction_1, first_action
|
|
);
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, second_density, fields.displacement, fields.displacement_direction_1, second_action
|
|
);
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, combined_density, fields.displacement, fields.displacement_direction_1, combined_action
|
|
);
|
|
|
|
REQUIRE(first_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
REQUIRE(second_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
REQUIRE(combined_action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
|
|
mfem::Vector expected_action(first_action);
|
|
expected_action *= scale_1;
|
|
expected_action.Add(scale_2, second_action);
|
|
|
|
const double linearity_error = global_relative_error(combined_action, expected_action, communicator);
|
|
|
|
INFO("Source base-field linearity error = " << linearity_error);
|
|
CHECK_THAT(linearity_error, Catch::Matchers::WithinAbs(0.0, 3.0e-12));
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Mapped Source Variation Ignores Vacuum Density",
|
|
tags::gravity_kernel_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless;
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const int vacuum_attribute = domain_mapper.GetVacuumElementAttribute();
|
|
const mfem::Vector vacuum_density = make_vacuum_only_density(f, vacuum_attribute);
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
const double vacuum_density_norm = global_norm(vacuum_density, communicator);
|
|
REQUIRE(vacuum_density_norm > 0.0);
|
|
|
|
mfem::Vector action;
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, domain_mapper, vacuum_density, fields.displacement, fields.displacement_direction_1, action
|
|
);
|
|
|
|
REQUIRE(action.Size() == f.gravityPotentialFes->GetTrueVSize());
|
|
|
|
const double action_norm = global_norm(action, communicator);
|
|
|
|
INFO("Vacuum density norm = " << vacuum_density_norm);
|
|
INFO("Source-variation action norm = " << action_norm);
|
|
|
|
CHECK_THAT(action_norm, Catch::Matchers::WithinAbs(0.0, 1.0e-14));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Jacobian Displacement Blocks Match Geometry Kernels",
|
|
tags::gravity_operator_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
f.mapping->ResetDisplacement();
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector state = make_gravity_jacobian_state(f, layout, true);
|
|
const mfem::Vector direction = make_displacement_direction(layout, fields.displacement_direction_1);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
|
|
mfem::Vector jacobian_action;
|
|
gravity_jacobian.Mult(direction, jacobian_action);
|
|
|
|
REQUIRE(jacobian_action.Size() == layout.residual_offsets().Last());
|
|
|
|
const mfem::Vector &density_true = linearization_context.GetDensityTrue();
|
|
const mfem::Vector &displacement_true =
|
|
linearization_context.GetGeometryContext().GetDisplacementTrue();
|
|
const mfem::Vector &gravity_gradient_true = linearization_context.GetGravityGradientTrue();
|
|
|
|
const mfem::Vector gradient_action = get_residual_block(jacobian_action, layout, gravity_gradient_residual_block);
|
|
const mfem::Vector poisson_action = get_residual_block(jacobian_action, layout, gravity_poisson_residual_block);
|
|
|
|
mfem::Vector expected_gradient_action_true;
|
|
mfem::Vector expected_poisson_action_true;
|
|
operators::kernels::apply_mapped_hdiv_mass_variation(
|
|
f, *f.domainMapperStateless, gravity_gradient_true, displacement_true, fields.displacement_direction_1,
|
|
expected_gradient_action_true
|
|
);
|
|
operators::kernels::apply_mapped_source_variation(
|
|
f, *f.domainMapperStateless, density_true, displacement_true, fields.displacement_direction_1,
|
|
expected_poisson_action_true
|
|
);
|
|
expected_poisson_action_true *= -1.0;
|
|
|
|
const mfem::Vector expected_gradient_action =
|
|
linearization_context.GetGravityGradientMap().gather(expected_gradient_action_true);
|
|
const mfem::Vector expected_poisson_action =
|
|
linearization_context.GetGravityPotentialMap().gather(expected_poisson_action_true);
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
const double gradient_error = global_relative_error(gradient_action, expected_gradient_action, communicator);
|
|
const double poisson_error = global_relative_error(poisson_action, expected_poisson_action, communicator);
|
|
|
|
INFO("Gradient displacement action norm = " << global_norm(gradient_action, communicator));
|
|
INFO("Poisson displacement action norm = " << global_norm(poisson_action, communicator));
|
|
INFO("Gradient displacement-block error = " << gradient_error);
|
|
INFO("Poisson displacement-block error = " << poisson_error);
|
|
|
|
REQUIRE(global_norm(gradient_action, communicator) > 1.0e-12);
|
|
REQUIRE(global_norm(poisson_action, communicator) > 1.0e-12);
|
|
CHECK_THAT(gradient_error, WithinAbs(0.0, 2.0e-12));
|
|
CHECK_THAT(poisson_error, WithinAbs(0.0, 2.0e-12));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Jacobian Displacement Direction Matches Centered "
|
|
"Differences",
|
|
tags::gravity_operator_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
f.mapping->ResetDisplacement();
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector direction = make_displacement_direction(layout, fields.displacement_direction_1);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
constexpr double difference_step = 1.0e-5;
|
|
constexpr double comparison_tolerance = 3.0e-7;
|
|
constexpr double nonzero_tolerance = 1.0e-12;
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
REQUIRE(direction.Size() == layout.value_offsets().Last());
|
|
REQUIRE(global_vector_norm(direction, communicator) > nonzero_tolerance);
|
|
|
|
for (const bool deformed : std::array{false, true}) {
|
|
DYNAMIC_SECTION("Geometry = " << (deformed ? "deformed" : "identity")) {
|
|
const mfem::Vector state = make_gravity_jacobian_state(f, layout, deformed);
|
|
|
|
REQUIRE(state.Size() == layout.value_offsets().Last());
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions base_revisions{
|
|
.discretization = {1},
|
|
.displacement = {1},
|
|
.density = {1},
|
|
.gravity_gradient = {1},
|
|
.gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state, base_revisions);
|
|
|
|
REQUIRE(linearization_context.IsPrepared());
|
|
|
|
check_linearization_context_matches_state(
|
|
linearization_context, state, layout.value_offsets(), communicator
|
|
);
|
|
|
|
const double base_density_norm = global_vector_norm(linearization_context.GetDensityTrue(), communicator);
|
|
const double base_gravity_gradient_norm =
|
|
global_vector_norm(linearization_context.GetGravityGradientTrue(), communicator);
|
|
|
|
INFO("Base density norm = " << base_density_norm);
|
|
INFO("Base gravity-gradient norm = " << base_gravity_gradient_norm);
|
|
|
|
REQUIRE(base_density_norm > nonzero_tolerance);
|
|
REQUIRE(base_gravity_gradient_norm > nonzero_tolerance);
|
|
|
|
mfem::Operator &gradient = gravity_operator.GetGradient(state);
|
|
REQUIRE(&gradient == static_cast<mfem::Operator *>(&gravity_jacobian));
|
|
|
|
mfem::Vector jacobian_action;
|
|
gradient.Mult(direction, jacobian_action);
|
|
|
|
REQUIRE(jacobian_action.Size() == layout.residual_offsets().Last());
|
|
|
|
mfem::Vector plus_state(state);
|
|
mfem::Vector minus_state(state);
|
|
plus_state.Add(difference_step, direction);
|
|
minus_state.Add(-difference_step, direction);
|
|
|
|
auto plus_revisions = base_revisions;
|
|
plus_revisions.displacement.value++;
|
|
|
|
mfem::Vector plus_residual;
|
|
gravity_operator.Prepare(plus_state, plus_revisions);
|
|
gravity_operator.Mult(plus_state, plus_residual);
|
|
|
|
REQUIRE(plus_residual.Size() == layout.residual_offsets().Last());
|
|
|
|
auto minus_revisions = plus_revisions;
|
|
minus_revisions.displacement.value++;
|
|
|
|
mfem::Vector minus_residual;
|
|
gravity_operator.Prepare(minus_state, minus_revisions);
|
|
gravity_operator.Mult(minus_state, minus_residual);
|
|
|
|
REQUIRE(minus_residual.Size() == layout.residual_offsets().Last());
|
|
|
|
mfem::Vector finite_difference(plus_residual);
|
|
finite_difference -= minus_residual;
|
|
finite_difference /= 2.0 * difference_step;
|
|
|
|
const mfem::Vector gradient_action =
|
|
get_residual_block(jacobian_action, layout, gravity_gradient_residual_block);
|
|
const mfem::Vector poisson_action =
|
|
get_residual_block(jacobian_action, layout, gravity_poisson_residual_block);
|
|
const mfem::Vector finite_difference_gradient =
|
|
get_residual_block(finite_difference, layout, gravity_gradient_residual_block);
|
|
const mfem::Vector finite_difference_poisson =
|
|
get_residual_block(finite_difference, layout, gravity_poisson_residual_block);
|
|
|
|
const double jacobian_gradient_norm = global_vector_norm(gradient_action, communicator);
|
|
const double jacobian_poisson_norm = global_vector_norm(poisson_action, communicator);
|
|
const double finite_difference_gradient_norm = global_vector_norm(finite_difference_gradient, communicator);
|
|
const double finite_difference_poisson_norm = global_vector_norm(finite_difference_poisson, communicator);
|
|
|
|
REQUIRE(jacobian_gradient_norm > nonzero_tolerance);
|
|
REQUIRE(jacobian_poisson_norm > nonzero_tolerance);
|
|
REQUIRE(finite_difference_gradient_norm > nonzero_tolerance);
|
|
REQUIRE(finite_difference_poisson_norm > nonzero_tolerance);
|
|
|
|
mfem::Vector gradient_difference(gradient_action);
|
|
gradient_difference -= finite_difference_gradient;
|
|
|
|
mfem::Vector poisson_difference(poisson_action);
|
|
poisson_difference -= finite_difference_poisson;
|
|
|
|
mfem::Vector combined_difference(jacobian_action);
|
|
combined_difference -= finite_difference;
|
|
|
|
const double absolute_gradient_error = global_vector_norm(gradient_difference, communicator);
|
|
const double absolute_poisson_error = global_vector_norm(poisson_difference, communicator);
|
|
const double absolute_combined_error = global_vector_norm(combined_difference, communicator);
|
|
|
|
const double gradient_error =
|
|
global_relative_error(gradient_action, finite_difference_gradient, communicator);
|
|
const double poisson_error = global_relative_error(poisson_action, finite_difference_poisson, communicator);
|
|
const double combined_error = global_relative_error(jacobian_action, finite_difference, communicator);
|
|
|
|
INFO("Geometry = " << (deformed ? "deformed" : "identity"));
|
|
INFO("Jacobian gradient action norm = " << jacobian_gradient_norm);
|
|
INFO("Finite-difference gradient norm = " << finite_difference_gradient_norm);
|
|
INFO("Jacobian Poisson action norm = " << jacobian_poisson_norm);
|
|
INFO("Finite-difference Poisson norm = " << finite_difference_poisson_norm);
|
|
INFO("Absolute gradient residual error = " << absolute_gradient_error);
|
|
INFO("Absolute Poisson residual error = " << absolute_poisson_error);
|
|
INFO("Absolute combined residual error = " << absolute_combined_error);
|
|
INFO("Gradient residual relative error = " << gradient_error);
|
|
INFO("Poisson residual relative error = " << poisson_error);
|
|
INFO("Combined residual relative error = " << combined_error);
|
|
|
|
REQUIRE(std::isfinite(gradient_error));
|
|
REQUIRE(std::isfinite(poisson_error));
|
|
REQUIRE(std::isfinite(combined_error));
|
|
|
|
CHECK_THAT(gradient_error, WithinAbs(0.0, comparison_tolerance));
|
|
CHECK_THAT(poisson_error, WithinAbs(0.0, comparison_tolerance));
|
|
CHECK_THAT(combined_error, WithinAbs(0.0, comparison_tolerance));
|
|
}
|
|
}
|
|
}
|
|
TEST_CASE(
|
|
"Gravity Field Jacobian Displacement Difference Converges At Second Order",
|
|
tags::gravity_operator_convergence
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
f.mapping->ResetDisplacement();
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector state = make_gravity_jacobian_state(f, layout, true);
|
|
const mfem::Vector direction = make_displacement_direction(layout, fields.displacement_direction_1);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
|
|
REQUIRE(linearization_context.IsPrepared());
|
|
|
|
check_linearization_context_matches_state(linearization_context, state, layout.value_offsets(), communicator);
|
|
|
|
mfem::Operator &gradient = gravity_operator.GetGradient(state);
|
|
REQUIRE(&gradient == static_cast<mfem::Operator *>(&gravity_jacobian));
|
|
|
|
mfem::Vector jacobian_action;
|
|
gradient.Mult(direction, jacobian_action);
|
|
|
|
REQUIRE(jacobian_action.Size() == layout.residual_offsets().Last());
|
|
|
|
const double jacobian_action_norm = global_vector_norm(jacobian_action, communicator);
|
|
|
|
INFO("Jacobian action norm = " << jacobian_action_norm);
|
|
REQUIRE(jacobian_action_norm > 1.0e-12);
|
|
|
|
auto evaluate_geometry_centered_difference = [&](const double difference_step) {
|
|
mfem::Vector plus_state(state);
|
|
mfem::Vector minus_state(state);
|
|
plus_state.Add(difference_step, direction);
|
|
minus_state.Add(-difference_step, direction);
|
|
|
|
revisions.displacement.value++;
|
|
|
|
mfem::Vector plus_residual;
|
|
gravity_operator.Prepare(plus_state, revisions);
|
|
gravity_operator.Mult(plus_state, plus_residual);
|
|
|
|
revisions.displacement.value++;
|
|
|
|
mfem::Vector minus_residual;
|
|
gravity_operator.Prepare(minus_state, revisions);
|
|
gravity_operator.Mult(minus_state, minus_residual);
|
|
|
|
REQUIRE(plus_residual.Size() == layout.residual_offsets().Last());
|
|
REQUIRE(minus_residual.Size() == layout.residual_offsets().Last());
|
|
|
|
mfem::Vector finite_difference(plus_residual);
|
|
finite_difference -= minus_residual;
|
|
finite_difference /= 2.0 * difference_step;
|
|
|
|
return finite_difference;
|
|
};
|
|
|
|
constexpr std::array<double, 3> difference_steps{8.0e-2, 4.0e-2, 2.0e-2};
|
|
|
|
std::array<double, difference_steps.size()> errors{};
|
|
std::array<double, difference_steps.size()> finite_difference_norms{};
|
|
|
|
for (std::size_t i = 0; i < difference_steps.size(); ++i) {
|
|
const mfem::Vector finite_difference = evaluate_geometry_centered_difference(difference_steps[i]);
|
|
|
|
finite_difference_norms[i] = global_vector_norm(finite_difference, communicator);
|
|
errors[i] = global_relative_error(finite_difference, jacobian_action, communicator);
|
|
|
|
INFO(
|
|
"Step = " << difference_steps[i] << ", finite-difference norm = " << finite_difference_norms[i]
|
|
<< ", relative error = " << errors[i]
|
|
);
|
|
|
|
REQUIRE(finite_difference_norms[i] > 1.0e-12);
|
|
REQUIRE(std::isfinite(errors[i]));
|
|
REQUIRE(errors[i] > 0.0);
|
|
}
|
|
|
|
const double first_observed_order = std::log(errors[0] / errors[1]) / std::log(2.0);
|
|
const double second_observed_order = std::log(errors[1] / errors[2]) / std::log(2.0);
|
|
|
|
INFO(
|
|
"Difference steps = [" << difference_steps[0] << ", " << difference_steps[1] << ", " << difference_steps[2]
|
|
<< "]"
|
|
);
|
|
INFO(
|
|
"Finite-difference norms = [" << finite_difference_norms[0] << ", " << finite_difference_norms[1] << ", "
|
|
<< finite_difference_norms[2] << "]"
|
|
);
|
|
INFO("Relative errors = [" << errors[0] << ", " << errors[1] << ", " << errors[2] << "]");
|
|
INFO("First observed convergence order = " << first_observed_order);
|
|
INFO("Second observed convergence order = " << second_observed_order);
|
|
|
|
REQUIRE(std::isfinite(first_observed_order));
|
|
REQUIRE(std::isfinite(second_observed_order));
|
|
|
|
CHECK(errors[1] < errors[0]);
|
|
CHECK(errors[2] < errors[1]);
|
|
CHECK(first_observed_order > 1.8);
|
|
CHECK(second_observed_order > 1.8);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Field Jacobian Complete Coupled Direction Matches Centered "
|
|
"Differences",
|
|
tags::gravity_operator_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
f.mapping->ResetDisplacement();
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector state = make_gravity_jacobian_state(f, layout, true);
|
|
|
|
mfem::Vector direction = make_combined_fixed_geometry_direction(layout);
|
|
set_value_block(direction, layout, displacement_block, fields.displacement_direction_2);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
operators::context::gravity_field::GravityFieldRevisions base_revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
|
|
REQUIRE(state.Size() == layout.value_offsets().Last());
|
|
REQUIRE(direction.Size() == layout.value_offsets().Last());
|
|
REQUIRE(global_vector_norm(direction, communicator) > 1.0e-12);
|
|
|
|
gravity_operator.Prepare(state, base_revisions);
|
|
|
|
REQUIRE(linearization_context.IsPrepared());
|
|
|
|
check_linearization_context_matches_state(linearization_context, state, layout.value_offsets(), communicator);
|
|
|
|
mfem::Operator &gradient = gravity_operator.GetGradient(state);
|
|
REQUIRE(&gradient == static_cast<mfem::Operator *>(&gravity_jacobian));
|
|
|
|
mfem::Vector jacobian_action;
|
|
gradient.Mult(direction, jacobian_action);
|
|
|
|
REQUIRE(jacobian_action.Size() == layout.residual_offsets().Last());
|
|
|
|
constexpr double difference_step = 1.0e-5;
|
|
|
|
mfem::Vector plus_state(state);
|
|
mfem::Vector minus_state(state);
|
|
plus_state.Add(difference_step, direction);
|
|
minus_state.Add(-difference_step, direction);
|
|
|
|
auto plus_revisions = base_revisions;
|
|
plus_revisions.displacement.value++;
|
|
plus_revisions.density.value++;
|
|
plus_revisions.gravity_gradient.value++;
|
|
plus_revisions.gravity_potential.value++;
|
|
|
|
mfem::Vector plus_residual;
|
|
gravity_operator.Prepare(plus_state, plus_revisions);
|
|
gravity_operator.Mult(plus_state, plus_residual);
|
|
|
|
REQUIRE(plus_residual.Size() == layout.residual_offsets().Last());
|
|
|
|
auto minus_revisions = plus_revisions;
|
|
minus_revisions.displacement.value++;
|
|
minus_revisions.density.value++;
|
|
minus_revisions.gravity_gradient.value++;
|
|
minus_revisions.gravity_potential.value++;
|
|
|
|
mfem::Vector minus_residual;
|
|
gravity_operator.Prepare(minus_state, minus_revisions);
|
|
gravity_operator.Mult(minus_state, minus_residual);
|
|
|
|
REQUIRE(minus_residual.Size() == layout.residual_offsets().Last());
|
|
|
|
mfem::Vector finite_difference(plus_residual);
|
|
finite_difference -= minus_residual;
|
|
finite_difference /= 2.0 * difference_step;
|
|
|
|
const mfem::Vector gradient_action = get_residual_block(jacobian_action, layout, gravity_gradient_residual_block);
|
|
const mfem::Vector poisson_action = get_residual_block(jacobian_action, layout, gravity_poisson_residual_block);
|
|
const mfem::Vector finite_difference_gradient =
|
|
get_residual_block(finite_difference, layout, gravity_gradient_residual_block);
|
|
const mfem::Vector finite_difference_poisson =
|
|
get_residual_block(finite_difference, layout, gravity_poisson_residual_block);
|
|
|
|
const double jacobian_gradient_norm = global_vector_norm(gradient_action, communicator);
|
|
const double jacobian_poisson_norm = global_vector_norm(poisson_action, communicator);
|
|
const double finite_difference_gradient_norm = global_vector_norm(finite_difference_gradient, communicator);
|
|
const double finite_difference_poisson_norm = global_vector_norm(finite_difference_poisson, communicator);
|
|
|
|
constexpr double nonzero_tolerance = 1.0e-12;
|
|
|
|
REQUIRE(jacobian_gradient_norm > nonzero_tolerance);
|
|
REQUIRE(jacobian_poisson_norm > nonzero_tolerance);
|
|
REQUIRE(finite_difference_gradient_norm > nonzero_tolerance);
|
|
REQUIRE(finite_difference_poisson_norm > nonzero_tolerance);
|
|
|
|
mfem::Vector gradient_difference(gradient_action);
|
|
gradient_difference -= finite_difference_gradient;
|
|
|
|
mfem::Vector poisson_difference(poisson_action);
|
|
poisson_difference -= finite_difference_poisson;
|
|
|
|
mfem::Vector combined_difference(jacobian_action);
|
|
combined_difference -= finite_difference;
|
|
|
|
const double absolute_gradient_error = global_vector_norm(gradient_difference, communicator);
|
|
const double absolute_poisson_error = global_vector_norm(poisson_difference, communicator);
|
|
const double absolute_combined_error = global_vector_norm(combined_difference, communicator);
|
|
|
|
const double gradient_error = global_relative_error(gradient_action, finite_difference_gradient, communicator);
|
|
const double poisson_error = global_relative_error(poisson_action, finite_difference_poisson, communicator);
|
|
const double combined_error = global_relative_error(jacobian_action, finite_difference, communicator);
|
|
|
|
INFO("Jacobian gradient action norm = " << jacobian_gradient_norm);
|
|
INFO("Finite-difference gradient norm = " << finite_difference_gradient_norm);
|
|
INFO("Jacobian Poisson action norm = " << jacobian_poisson_norm);
|
|
INFO("Finite-difference Poisson norm = " << finite_difference_poisson_norm);
|
|
INFO("Absolute gradient residual error = " << absolute_gradient_error);
|
|
INFO("Absolute Poisson residual error = " << absolute_poisson_error);
|
|
INFO("Absolute combined residual error = " << absolute_combined_error);
|
|
INFO("Gradient residual relative error = " << gradient_error);
|
|
INFO("Poisson residual relative error = " << poisson_error);
|
|
INFO("Combined residual relative error = " << combined_error);
|
|
|
|
REQUIRE(std::isfinite(gradient_error));
|
|
REQUIRE(std::isfinite(poisson_error));
|
|
REQUIRE(std::isfinite(combined_error));
|
|
|
|
CHECK_THAT(gradient_error, WithinAbs(0.0, 5.0e-7));
|
|
CHECK_THAT(poisson_error, WithinAbs(0.0, 5.0e-7));
|
|
CHECK_THAT(combined_error, WithinAbs(0.0, 5.0e-7));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Reduced Gravity Field Operator Solves Deformed Gravity System With MINRES",
|
|
tags::gravity_operator_integration
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.mapping != nullptr);
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const gravity_layout layout = make_gravity_jacobian_layout(f);
|
|
const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f);
|
|
const mfem::Vector density_true = make_source_variation_density(f);
|
|
const mfem::Vector displacement_true = fields.displacement;
|
|
|
|
mfem::ParGridFunction legacy_displacement(f.displacementFes.get());
|
|
legacy_displacement.SetFromTrueDofs(displacement_true);
|
|
f.mapping->SetDisplacement(legacy_displacement);
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.gravityContext.b_form != nullptr);
|
|
REQUIRE(f.gravityContext.BT != nullptr);
|
|
REQUIRE(f.gravityContext.block_prec != nullptr);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
const mfem::Vector density = linearization_context.GetDensityMap().gather(density_true);
|
|
const mfem::Vector displacement = linearization_context.GetDisplacementMap().gather(displacement_true);
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
operators::context::gravity_field::GravityFieldGeometryContext reduced_geometry_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
operators::ReducedGravityFieldOperator reduced_operator(gravity_operator, reduced_geometry_context, displacement);
|
|
|
|
REQUIRE(reduced_geometry_context.IsPrepared());
|
|
REQUIRE(reduced_operator.Width() == layout.residual_offsets().Last());
|
|
REQUIRE(reduced_operator.Height() == layout.residual_offsets().Last());
|
|
REQUIRE(reduced_operator.Width() == reduced_operator.Height());
|
|
|
|
const std::uint64_t mass_preparations_before_solve =
|
|
reduced_geometry_context.GetMassOperator().GetPreparationCount();
|
|
const std::uint64_t source_preparations_before_solve =
|
|
reduced_geometry_context.GetSourceOperator().GetPreparationCount();
|
|
|
|
REQUIRE(mass_preparations_before_solve > 0);
|
|
REQUIRE(source_preparations_before_solve > 0);
|
|
|
|
mfem::Vector right_hand_side;
|
|
reduced_operator.BuildRightHandSide(density, right_hand_side);
|
|
|
|
REQUIRE(right_hand_side.Size() == reduced_operator.Height());
|
|
|
|
MPI_Comm communicator = f.mesh->GetComm();
|
|
const double right_hand_side_norm = global_norm(right_hand_side, communicator);
|
|
|
|
INFO("Reduced gravity-system size = " << reduced_operator.Height());
|
|
INFO("Gravity right-hand-side norm = " << right_hand_side_norm);
|
|
INFO("Mass preparations before solve = " << mass_preparations_before_solve);
|
|
INFO("Source preparations before solve = " << source_preparations_before_solve);
|
|
|
|
REQUIRE(right_hand_side_norm > 0.0);
|
|
|
|
mfem::Vector gravity_state(reduced_operator.Width());
|
|
gravity_state = 0.0;
|
|
|
|
constexpr double relative_solver_tolerance = 1.0e-14;
|
|
constexpr double absolute_solver_tolerance = 1.0e-14;
|
|
const int maximum_iterations = 2000;
|
|
|
|
mfem::MINRESSolver minres(communicator);
|
|
minres.SetOperator(reduced_operator);
|
|
minres.SetPreconditioner(*f.gravityContext.block_prec);
|
|
minres.SetRelTol(relative_solver_tolerance);
|
|
minres.SetAbsTol(absolute_solver_tolerance);
|
|
minres.SetMaxIter(maximum_iterations);
|
|
minres.SetPrintLevel(1);
|
|
|
|
MEAN_FIELD_PROFILE_RESET();
|
|
MEAN_FIELD_PROFILE_CALL_WARMUP("MINRES total", 0, minres.Mult(right_hand_side, gravity_state));
|
|
MEAN_FIELD_PROFILE_PRINT(communicator);
|
|
|
|
REQUIRE(gravity_state.Size() == reduced_operator.Width());
|
|
|
|
const std::uint64_t mass_preparations_after_solve =
|
|
reduced_geometry_context.GetMassOperator().GetPreparationCount();
|
|
const std::uint64_t source_preparations_after_solve =
|
|
reduced_geometry_context.GetSourceOperator().GetPreparationCount();
|
|
|
|
INFO("Mass preparations after solve = " << mass_preparations_after_solve);
|
|
INFO("Source preparations after solve = " << source_preparations_after_solve);
|
|
|
|
CHECK(mass_preparations_after_solve == mass_preparations_before_solve);
|
|
CHECK(source_preparations_after_solve == source_preparations_before_solve);
|
|
|
|
mfem::Vector operator_action;
|
|
reduced_operator.Mult(gravity_state, operator_action);
|
|
|
|
mfem::Vector reduced_residual(operator_action);
|
|
reduced_residual -= right_hand_side;
|
|
|
|
const mfem::Vector gradient_residual =
|
|
get_residual_block(reduced_residual, layout, gravity_gradient_residual_block);
|
|
const mfem::Vector poisson_residual = get_residual_block(reduced_residual, layout, gravity_poisson_residual_block);
|
|
const mfem::Vector solved_gravity_gradient =
|
|
get_residual_block(gravity_state, layout, gravity_gradient_residual_block);
|
|
const mfem::Vector solved_gravity_potential =
|
|
get_residual_block(gravity_state, layout, gravity_poisson_residual_block);
|
|
|
|
const double gravity_state_norm = global_norm(gravity_state, communicator);
|
|
const double gravity_gradient_norm = global_norm(solved_gravity_gradient, communicator);
|
|
const double gravity_potential_norm = global_norm(solved_gravity_potential, communicator);
|
|
const double residual_norm = global_norm(reduced_residual, communicator);
|
|
const double gradient_residual_norm = global_norm(gradient_residual, communicator);
|
|
const double poisson_residual_norm = global_norm(poisson_residual, communicator);
|
|
const double relative_residual = residual_norm / right_hand_side_norm;
|
|
const double relative_gradient_residual = gradient_residual_norm / right_hand_side_norm;
|
|
const double relative_poisson_residual = poisson_residual_norm / right_hand_side_norm;
|
|
|
|
INFO("MINRES converged = " << minres.GetConverged());
|
|
INFO("MINRES iterations = " << minres.GetNumIterations());
|
|
INFO("MINRES reported final norm = " << minres.GetFinalNorm());
|
|
INFO("Gravity-state norm = " << gravity_state_norm);
|
|
INFO("Solved gravity-gradient norm = " << gravity_gradient_norm);
|
|
INFO("Solved gravity-potential norm = " << gravity_potential_norm);
|
|
INFO("Direct residual norm = " << residual_norm);
|
|
INFO("Direct relative residual = " << relative_residual);
|
|
INFO("Relative gradient-equation residual = " << relative_gradient_residual);
|
|
INFO("Relative Poisson-equation residual = " << relative_poisson_residual);
|
|
|
|
CHECK(minres.GetConverged());
|
|
CHECK(minres.GetNumIterations() <= maximum_iterations);
|
|
REQUIRE(gravity_state_norm > 0.0);
|
|
REQUIRE(gravity_gradient_norm > 0.0);
|
|
REQUIRE(gravity_potential_norm > 0.0);
|
|
|
|
constexpr double direct_residual_tolerance = 1.0e-11;
|
|
|
|
CHECK_THAT(relative_residual, WithinAbs(0.0, direct_residual_tolerance));
|
|
CHECK_THAT(relative_gradient_residual, WithinAbs(0.0, direct_residual_tolerance));
|
|
CHECK_THAT(relative_poisson_residual, WithinAbs(0.0, direct_residual_tolerance));
|
|
|
|
mfem::Vector full_state(layout.value_offsets().Last());
|
|
full_state = 0.0;
|
|
|
|
set_value_block(full_state, layout, density_block, density);
|
|
set_value_block(full_state, layout, displacement_block, displacement);
|
|
set_value_block(full_state, layout, gravity_gradient_block, solved_gravity_gradient);
|
|
set_value_block(full_state, layout, gravity_potential_block, solved_gravity_potential);
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions full_state_revisions{
|
|
.discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(full_state, full_state_revisions);
|
|
|
|
REQUIRE(linearization_context.IsPrepared());
|
|
|
|
check_linearization_context_matches_state(linearization_context, full_state, layout.value_offsets(), communicator);
|
|
|
|
mfem::Vector full_residual;
|
|
gravity_operator.Mult(full_state, full_residual);
|
|
|
|
REQUIRE(full_residual.Size() == layout.residual_offsets().Last());
|
|
|
|
mfem::Vector residual_representation_difference(full_residual);
|
|
residual_representation_difference -= reduced_residual;
|
|
|
|
const double residual_representation_error =
|
|
global_norm(residual_representation_difference, communicator) / right_hand_side_norm;
|
|
const double full_relative_residual = global_norm(full_residual, communicator) / right_hand_side_norm;
|
|
|
|
INFO("Full gravity residual relative norm = " << full_relative_residual);
|
|
INFO("Reduced/full residual representation error = " << residual_representation_error);
|
|
|
|
CHECK_THAT(full_relative_residual, WithinAbs(0.0, direct_residual_tolerance));
|
|
CHECK_THAT(residual_representation_error, WithinAbs(0.0, 1.0e-12));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Gravity Hdiv Operator Difference Is Localized To Vacuum Compactification",
|
|
tags::gravity_legacy
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.mapping != nullptr);
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
REQUIRE(f.compactificationCoordinate != nullptr);
|
|
|
|
using form = blocks::gravity_field_form;
|
|
|
|
constexpr auto displacement_block =
|
|
mean_field::utils::blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
|
|
|
|
constexpr auto gravity_gradient_block =
|
|
mean_field::utils::blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
|
|
|
|
constexpr auto gravity_gradient_residual_block =
|
|
mean_field::utils::blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
|
|
|
|
const blocks::form_layout<form> layout = make_gravity_layout(f);
|
|
|
|
const mfem::Vector gravity_gradient_true = make_full_support_gravity_gradient(f);
|
|
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
REQUIRE(global_vector_norm(gravity_gradient_true, communicator) > 0.0);
|
|
|
|
for (const bool deformed : std::array{false, true}) {
|
|
DYNAMIC_SECTION("Geometry = " << (deformed ? "deformed" : "identity")) {
|
|
const mfem::Vector displacement_true = make_stateless_reference_displacement(f, deformed);
|
|
|
|
/*
|
|
* Prepare the legacy geometry and assemble its PA operator.
|
|
* The legacy mapper performs its vacuum map without consulting
|
|
* the exterior-coordinate GridFunction.
|
|
*/
|
|
mfem::ParGridFunction legacy_displacement(f.displacementFes.get());
|
|
legacy_displacement.SetFromTrueDofs(displacement_true);
|
|
f.mapping->SetDisplacement(legacy_displacement);
|
|
|
|
physics::update_stiffness_matrix(f);
|
|
|
|
REQUIRE(f.gravityContext.m_form != nullptr);
|
|
|
|
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
|
f, *f.domainMapperStateless
|
|
);
|
|
|
|
operators::GravityFieldJacobianOperator gravity_jacobian(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
|
);
|
|
|
|
operators::GravityFieldOperator gravity_operator(
|
|
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
|
);
|
|
|
|
mfem::Vector state(layout.value_offsets().Last());
|
|
state = 0.0;
|
|
|
|
for (int i = 0; i < displacement_true.Size(); ++i) {
|
|
state(layout.offset(displacement_block) + i) = displacement_true(i);
|
|
}
|
|
|
|
for (int i = 0; i < gravity_gradient_true.Size(); ++i) {
|
|
state(layout.offset(gravity_gradient_block) + i) = gravity_gradient_true(i);
|
|
}
|
|
|
|
const operators::context::gravity_field::GravityFieldRevisions revisions{
|
|
.discretization = {1},
|
|
.displacement = {1},
|
|
.density = {1},
|
|
.gravity_gradient = {1},
|
|
.gravity_potential = {1}
|
|
};
|
|
|
|
gravity_operator.Prepare(state, revisions);
|
|
|
|
mfem::Vector new_residual;
|
|
gravity_operator.Mult(state, new_residual);
|
|
|
|
mfem::Vector new_operator_mass_action(layout.size(gravity_gradient_residual_block));
|
|
|
|
for (int i = 0; i < new_operator_mass_action.Size(); ++i) {
|
|
new_operator_mass_action(i) = new_residual(layout.offset(gravity_gradient_residual_block) + i);
|
|
}
|
|
|
|
mfem::Vector legacy_operator_mass_action(f.gravityFluxFes->GetTrueVSize());
|
|
|
|
legacy_operator_mass_action = 0.0;
|
|
f.gravityContext.m_form->Mult(gravity_gradient_true, legacy_operator_mass_action);
|
|
|
|
const StatelessHDivMassReference new_reference =
|
|
evaluate_stateless_hdiv_mass_quadrature_reference(f, gravity_gradient_true, displacement_true);
|
|
const StatelessHDivMassReference legacy_reference =
|
|
assemble_legacy_hdiv_mass_reference(f, gravity_gradient_true);
|
|
|
|
mfem::Vector new_decomposed_action(new_reference.stellar_action);
|
|
new_decomposed_action += new_reference.vacuum_action;
|
|
|
|
mfem::Vector legacy_decomposed_action(legacy_reference.stellar_action);
|
|
legacy_decomposed_action += legacy_reference.vacuum_action;
|
|
|
|
mfem::Vector total_gap(new_reference.total_action);
|
|
total_gap -= legacy_reference.total_action;
|
|
|
|
mfem::Vector stellar_gap(new_reference.stellar_action);
|
|
stellar_gap -= legacy_reference.stellar_action;
|
|
|
|
mfem::Vector vacuum_gap(new_reference.vacuum_action);
|
|
vacuum_gap -= legacy_reference.vacuum_action;
|
|
|
|
mfem::Vector decomposed_gap(stellar_gap);
|
|
decomposed_gap += vacuum_gap;
|
|
|
|
mfem::Vector unexplained_by_vacuum(total_gap);
|
|
unexplained_by_vacuum -= vacuum_gap;
|
|
|
|
const double new_operator_reference_error =
|
|
global_relative_difference(new_operator_mass_action, new_reference.total_action, communicator);
|
|
|
|
const double legacy_operator_reference_error =
|
|
global_relative_difference(legacy_operator_mass_action, legacy_reference.total_action, communicator);
|
|
|
|
const double new_decomposition_error =
|
|
global_relative_difference(new_reference.total_action, new_decomposed_action, communicator);
|
|
|
|
const double legacy_decomposition_error =
|
|
global_relative_difference(legacy_reference.total_action, legacy_decomposed_action, communicator);
|
|
|
|
const double gap_decomposition_error = global_relative_difference(total_gap, decomposed_gap, communicator);
|
|
|
|
const double stellar_relative_difference =
|
|
global_relative_difference(new_reference.stellar_action, legacy_reference.stellar_action, communicator);
|
|
|
|
const double vacuum_relative_difference =
|
|
global_relative_difference(new_reference.vacuum_action, legacy_reference.vacuum_action, communicator);
|
|
|
|
const double total_relative_difference =
|
|
global_relative_difference(new_reference.total_action, legacy_reference.total_action, communicator);
|
|
|
|
const double total_gap_norm = global_vector_norm(total_gap, communicator);
|
|
|
|
const double stellar_gap_norm = global_vector_norm(stellar_gap, communicator);
|
|
|
|
const double vacuum_gap_norm = global_vector_norm(vacuum_gap, communicator);
|
|
|
|
const double unexplained_gap_norm = global_vector_norm(unexplained_by_vacuum, communicator);
|
|
|
|
const double stellar_fraction_of_gap =
|
|
stellar_gap_norm / std::max(total_gap_norm, std::numeric_limits<double>::epsilon());
|
|
|
|
const double unexplained_fraction_of_gap =
|
|
unexplained_gap_norm / std::max(total_gap_norm, std::numeric_limits<double>::epsilon());
|
|
|
|
const double new_total_energy =
|
|
global_vector_dot(gravity_gradient_true, new_reference.total_action, communicator);
|
|
|
|
const double legacy_total_energy =
|
|
global_vector_dot(gravity_gradient_true, legacy_reference.total_action, communicator);
|
|
|
|
const double new_stellar_energy =
|
|
global_vector_dot(gravity_gradient_true, new_reference.stellar_action, communicator);
|
|
|
|
const double legacy_stellar_energy =
|
|
global_vector_dot(gravity_gradient_true, legacy_reference.stellar_action, communicator);
|
|
|
|
const double new_vacuum_energy =
|
|
global_vector_dot(gravity_gradient_true, new_reference.vacuum_action, communicator);
|
|
|
|
const double legacy_vacuum_energy =
|
|
global_vector_dot(gravity_gradient_true, legacy_reference.vacuum_action, communicator);
|
|
|
|
const double total_energy_gap = new_total_energy - legacy_total_energy;
|
|
|
|
const double stellar_energy_gap = new_stellar_energy - legacy_stellar_energy;
|
|
|
|
const double vacuum_energy_gap = new_vacuum_energy - legacy_vacuum_energy;
|
|
|
|
const double energy_gap_decomposition_error =
|
|
std::abs(total_energy_gap - stellar_energy_gap - vacuum_energy_gap) /
|
|
std::max(std::abs(total_energy_gap), std::numeric_limits<double>::epsilon());
|
|
|
|
INFO("Geometry = " << (deformed ? "deformed" : "identity"));
|
|
INFO("New operator/reference error = " << new_operator_reference_error);
|
|
INFO("Legacy operator/reference error = " << legacy_operator_reference_error);
|
|
INFO("New regional decomposition error = " << new_decomposition_error);
|
|
INFO("Legacy regional decomposition error = " << legacy_decomposition_error);
|
|
INFO("Gap decomposition error = " << gap_decomposition_error);
|
|
INFO("New/legacy total mass-action difference = " << total_relative_difference);
|
|
INFO("New/legacy stellar mass-action difference = " << stellar_relative_difference);
|
|
INFO("New/legacy vacuum mass-action difference = " << vacuum_relative_difference);
|
|
INFO("Total operator-gap norm = " << total_gap_norm);
|
|
INFO("Stellar operator-gap norm = " << stellar_gap_norm);
|
|
INFO("Vacuum operator-gap norm = " << vacuum_gap_norm);
|
|
INFO("Stellar fraction of operator gap = " << stellar_fraction_of_gap);
|
|
INFO("Operator gap unexplained by vacuum = " << unexplained_fraction_of_gap);
|
|
INFO("New total H(div) energy = " << new_total_energy);
|
|
INFO("Legacy total H(div) energy = " << legacy_total_energy);
|
|
INFO("New stellar H(div) energy = " << new_stellar_energy);
|
|
INFO("Legacy stellar H(div) energy = " << legacy_stellar_energy);
|
|
INFO("New vacuum H(div) energy = " << new_vacuum_energy);
|
|
INFO("Legacy vacuum H(div) energy = " << legacy_vacuum_energy);
|
|
INFO("Total H(div) energy gap = " << total_energy_gap);
|
|
INFO("Stellar H(div) energy gap = " << stellar_energy_gap);
|
|
INFO("Vacuum H(div) energy gap = " << vacuum_energy_gap);
|
|
INFO("Energy-gap decomposition error = " << energy_gap_decomposition_error);
|
|
|
|
REQUIRE(new_reference.stellar_elements > 0);
|
|
REQUIRE(new_reference.vacuum_elements > 0);
|
|
REQUIRE(legacy_reference.stellar_elements > 0);
|
|
REQUIRE(legacy_reference.vacuum_elements > 0);
|
|
|
|
REQUIRE(new_total_energy > 0.0);
|
|
REQUIRE(legacy_total_energy > 0.0);
|
|
REQUIRE(new_stellar_energy > 0.0);
|
|
REQUIRE(legacy_stellar_energy > 0.0);
|
|
REQUIRE(new_vacuum_energy > 0.0);
|
|
REQUIRE(legacy_vacuum_energy > 0.0);
|
|
|
|
constexpr double operator_reference_tolerance = 1.0e-11;
|
|
constexpr double decomposition_tolerance = 5.0e-13;
|
|
constexpr double stellar_parity_tolerance = 1.0e-10;
|
|
constexpr double gap_localization_tolerance = 1.0e-9;
|
|
|
|
CHECK_THAT(new_operator_reference_error, Catch::Matchers::WithinAbs(0.0, operator_reference_tolerance));
|
|
|
|
CHECK_THAT(legacy_operator_reference_error, Catch::Matchers::WithinAbs(0.0, operator_reference_tolerance));
|
|
|
|
CHECK_THAT(new_decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance));
|
|
|
|
CHECK_THAT(legacy_decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance));
|
|
|
|
CHECK_THAT(gap_decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance));
|
|
|
|
CHECK_THAT(stellar_relative_difference, Catch::Matchers::WithinAbs(0.0, stellar_parity_tolerance));
|
|
|
|
/*
|
|
* The previous global comparison already showed a material
|
|
* difference. Confirm that this test continues to exercise it.
|
|
*/
|
|
REQUIRE(total_gap_norm > 1.0e-12);
|
|
REQUIRE(vacuum_gap_norm > 1.0e-12);
|
|
REQUIRE(vacuum_relative_difference > 1.0e-8);
|
|
|
|
/*
|
|
* If these pass, the old/new difference has been directly
|
|
* localized to the vacuum compactification prescription.
|
|
*/
|
|
CHECK_THAT(stellar_fraction_of_gap, Catch::Matchers::WithinAbs(0.0, gap_localization_tolerance));
|
|
|
|
CHECK_THAT(unexplained_fraction_of_gap, Catch::Matchers::WithinAbs(0.0, gap_localization_tolerance));
|
|
|
|
CHECK_THAT(energy_gap_decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance));
|
|
}
|
|
}
|
|
}
|