currently the barotope and the pressure force operator are migrated to the new support system
1171 lines
49 KiB
C++
1171 lines
49 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <sstream>
|
|
|
|
#include <mfem.hpp>
|
|
#include <mpi.h>
|
|
|
|
import mean_field;
|
|
import test_helpers;
|
|
|
|
namespace {
|
|
constexpr std::array<double, 6> shell_boundaries{0.0, 0.25, 0.50, 0.75, 0.90, 1.0};
|
|
constexpr int shell_count = static_cast<int>(shell_boundaries.size()) - 1;
|
|
|
|
enum class MappingPath { legacy, stateless };
|
|
|
|
struct ShellAccumulator {
|
|
long long points{0};
|
|
double weight{0.0};
|
|
double minimum_radius{std::numeric_limits<double>::infinity()};
|
|
double maximum_radius{0.0};
|
|
double potential_error_squared{0.0};
|
|
double radial_error_squared{0.0};
|
|
double tangential_squared{0.0};
|
|
};
|
|
|
|
struct ShellMetrics {
|
|
long long points{0};
|
|
double minimum_radius{0.0};
|
|
double maximum_radius{0.0};
|
|
double potential_rms_error{0.0};
|
|
double radial_rms_error{0.0};
|
|
double tangential_rms{0.0};
|
|
};
|
|
|
|
struct ShellMeasurement {
|
|
std::array<ShellMetrics, shell_count> shells{};
|
|
long long invalid_points{0};
|
|
};
|
|
|
|
constexpr int mapping_status_count = 8;
|
|
|
|
struct GravitationalEnergies {
|
|
double binding{0.0};
|
|
double virial{0.0};
|
|
double minimum_mapping_determinant{std::numeric_limits<double>::infinity()};
|
|
double maximum_mapping_determinant{-std::numeric_limits<double>::infinity()};
|
|
long long invalid_points{0};
|
|
std::array<long long, mapping_status_count> mapping_status_counts{};
|
|
int first_invalid_element{-1};
|
|
int first_invalid_attribute{-1};
|
|
int first_invalid_quadrature_point{-1};
|
|
double first_invalid_determinant{std::numeric_limits<double>::quiet_NaN()};
|
|
};
|
|
|
|
constexpr int mapping_status_index(const mean_field::mapping::MappingStatus status) {
|
|
return static_cast<int>(status);
|
|
}
|
|
|
|
void zero_vacuum_density(
|
|
const mean_field::fem::FEM &f,
|
|
mfem::GridFunction &density
|
|
) {
|
|
for (int i = 0; i < f.vacuumDensityTdofs.Size(); ++i) {
|
|
density(f.vacuumDensityTdofs[i]) = 0.0;
|
|
}
|
|
}
|
|
|
|
double global_norm(
|
|
const mfem::Vector &vector,
|
|
MPI_Comm communicator
|
|
) {
|
|
const double local_norm_squared = vector * vector;
|
|
double global_norm_squared = 0.0;
|
|
MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
|
return std::sqrt(global_norm_squared);
|
|
}
|
|
|
|
double global_dot(
|
|
const mfem::Vector &lhs,
|
|
const mfem::Vector &rhs,
|
|
MPI_Comm communicator
|
|
) {
|
|
const double local_dot = lhs * rhs;
|
|
double result = 0.0;
|
|
MPI_Allreduce(&local_dot, &result, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
|
return result;
|
|
}
|
|
|
|
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), std::numeric_limits<double>::epsilon());
|
|
}
|
|
|
|
int get_shell(const double coordinate) {
|
|
const double clamped = std::clamp(coordinate, 0.0, std::nextafter(1.0, 0.0));
|
|
|
|
for (int shell = 0; shell < shell_count; ++shell) {
|
|
if (clamped < shell_boundaries[shell + 1]) {
|
|
return shell;
|
|
}
|
|
}
|
|
|
|
return shell_count - 1;
|
|
}
|
|
|
|
bool retryable_infinity_status(const mean_field::mapping::MappingStatus status) {
|
|
return status == mean_field::mapping::MappingStatus::at_compactified_infinity ||
|
|
status == mean_field::mapping::MappingStatus::outside_reference_domain ||
|
|
status == mean_field::mapping::MappingStatus::non_finite_result ||
|
|
status == mean_field::mapping::MappingStatus::non_positive_determinant;
|
|
}
|
|
|
|
class ProjectionGeometry {
|
|
public:
|
|
ProjectionGeometry(
|
|
const mean_field::fem::FEM &f,
|
|
const mean_field::mapping::DomainMapperStateless &mapper,
|
|
const mfem::GridFunction &displacement
|
|
)
|
|
: m_fem(f),
|
|
m_mapper(mapper),
|
|
m_displacement(displacement),
|
|
m_workspace(f.mesh->Dimension()) {
|
|
}
|
|
|
|
mean_field::mapping::MappingStatus Evaluate(
|
|
mfem::ElementTransformation &transformation,
|
|
const mfem::IntegrationPoint &integration_point,
|
|
mean_field::mapping::MappingPointContext &context,
|
|
const bool permit_infinity_limit
|
|
) {
|
|
m_used_infinity_limit = false;
|
|
|
|
const int element_id = transformation.ElementNo;
|
|
MFEM_VERIFY(element_id >= 0, "Projection coefficient received an invalid element number.");
|
|
|
|
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id);
|
|
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id);
|
|
|
|
mfem::Array<int> displacement_dofs;
|
|
mfem::Array<int> compactification_dofs;
|
|
|
|
mfem::DofTransformation *displacement_transform =
|
|
m_fem.displacementFes->GetElementVDofs(element_id, displacement_dofs);
|
|
|
|
mfem::DofTransformation *compactification_transform =
|
|
m_fem.compactificationFes->GetElementDofs(element_id, compactification_dofs);
|
|
|
|
mfem::Vector element_displacement;
|
|
mfem::Vector element_compactification;
|
|
|
|
m_displacement.GetSubVector(displacement_dofs, element_displacement);
|
|
m_fem.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
|
|
|
|
if (displacement_transform != nullptr) {
|
|
displacement_transform->InvTransformPrimal(element_displacement);
|
|
}
|
|
|
|
if (compactification_transform != nullptr) {
|
|
compactification_transform->InvTransformPrimal(element_compactification);
|
|
}
|
|
|
|
const mean_field::mapping::ElementDisplacementData displacement_data =
|
|
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
|
|
displacement_element, element_displacement
|
|
);
|
|
|
|
const mean_field::mapping::ElementCompactificationData compactification_data(
|
|
compactification_element, element_compactification
|
|
);
|
|
|
|
const mean_field::mapping::ElementMappingData mapping_data{
|
|
.displacement = displacement_data, .compactification = compactification_data
|
|
};
|
|
|
|
mfem::Vector compactification_shape(compactification_element.GetDof());
|
|
compactification_element.CalcShape(integration_point, compactification_shape);
|
|
|
|
const double coordinate = element_compactification * compactification_shape;
|
|
|
|
mean_field::mapping::MappingStatus status =
|
|
m_mapper.EvaluatePoint(mapping_data, transformation, integration_point, m_workspace, context);
|
|
|
|
if (status == mean_field::mapping::MappingStatus::valid) {
|
|
transformation.SetIntPoint(&integration_point);
|
|
return status;
|
|
}
|
|
|
|
const bool infinity_request = m_mapper.IsCompactifiedElement(transformation) && coordinate >= 1.0 - 1.0e-10;
|
|
|
|
if (!permit_infinity_limit || !infinity_request || !retryable_infinity_status(status)) {
|
|
transformation.SetIntPoint(&integration_point);
|
|
return status;
|
|
}
|
|
|
|
const mfem::IntegrationPoint ¢er = mfem::Geometries.GetCenter(transformation.GetGeometryType());
|
|
|
|
constexpr std::array<double, 11> inward_fractions{1.0e-12, 1.0e-11, 1.0e-10, 1.0e-9, 1.0e-8, 1.0e-7,
|
|
1.0e-6, 1.0e-5, 1.0e-4, 1.0e-3, 1.0e-2};
|
|
|
|
for (const double fraction : inward_fractions) {
|
|
mfem::IntegrationPoint inward;
|
|
inward.x = (1.0 - fraction) * integration_point.x + fraction * center.x;
|
|
inward.y = (1.0 - fraction) * integration_point.y + fraction * center.y;
|
|
inward.z = (1.0 - fraction) * integration_point.z + fraction * center.z;
|
|
inward.weight = integration_point.weight;
|
|
|
|
status = m_mapper.EvaluatePoint(mapping_data, transformation, inward, m_workspace, context);
|
|
|
|
if (status == mean_field::mapping::MappingStatus::valid) {
|
|
m_used_infinity_limit = true;
|
|
transformation.SetIntPoint(&integration_point);
|
|
return status;
|
|
}
|
|
|
|
if (!retryable_infinity_status(status)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
transformation.SetIntPoint(&integration_point);
|
|
return status;
|
|
}
|
|
|
|
[[nodiscard]] bool UsedInfinityLimit() const noexcept {
|
|
return m_used_infinity_limit;
|
|
}
|
|
|
|
private:
|
|
const mean_field::fem::FEM &m_fem;
|
|
const mean_field::mapping::DomainMapperStateless &m_mapper;
|
|
const mfem::GridFunction &m_displacement;
|
|
mean_field::mapping::DomainMapperStateless::Workspace m_workspace;
|
|
bool m_used_infinity_limit{false};
|
|
};
|
|
|
|
class MonopolePotentialCoefficient final : public mfem::Coefficient {
|
|
public:
|
|
MonopolePotentialCoefficient(
|
|
const mean_field::fem::FEM &f,
|
|
const mean_field::mapping::DomainMapperStateless &mapper,
|
|
const mfem::GridFunction &displacement,
|
|
const double mass,
|
|
const double radius
|
|
)
|
|
: m_geometry(
|
|
f,
|
|
mapper,
|
|
displacement
|
|
),
|
|
m_vacuum_attribute(mapper.GetVacuumElementAttribute()),
|
|
m_mass(mass),
|
|
m_radius(radius) {
|
|
}
|
|
|
|
double Eval(
|
|
mfem::ElementTransformation &transformation,
|
|
const mfem::IntegrationPoint &integration_point
|
|
) override {
|
|
mean_field::mapping::MappingPointContext context;
|
|
|
|
const mean_field::mapping::MappingStatus status =
|
|
m_geometry.Evaluate(transformation, integration_point, context, true);
|
|
|
|
MFEM_VERIFY(
|
|
status == mean_field::mapping::MappingStatus::valid,
|
|
"Stateless monopole-potential projection failed with status "
|
|
<< static_cast<int>(status) << " on element " << transformation.ElementNo << '.'
|
|
);
|
|
|
|
if (m_geometry.UsedInfinityLimit()) {
|
|
return 0.0;
|
|
}
|
|
|
|
const double radius = context.physical_position.Norml2();
|
|
|
|
MFEM_VERIFY(std::isfinite(radius) && radius > 0.0, "Invalid monopole projection radius.");
|
|
|
|
if (transformation.Attribute == m_vacuum_attribute) {
|
|
return -mean_field::utils::G * m_mass / radius;
|
|
}
|
|
|
|
return -mean_field::utils::G * m_mass * (3.0 * m_radius * m_radius - radius * radius) /
|
|
(2.0 * m_radius * m_radius * m_radius);
|
|
}
|
|
|
|
private:
|
|
ProjectionGeometry m_geometry;
|
|
int m_vacuum_attribute;
|
|
double m_mass;
|
|
double m_radius;
|
|
};
|
|
|
|
void local_to_true(
|
|
const mfem::ParFiniteElementSpace &space,
|
|
const mfem::Vector &local,
|
|
mfem::Vector &true_vector
|
|
) {
|
|
true_vector.SetSize(space.GetTrueVSize());
|
|
true_vector = 0.0;
|
|
|
|
const mfem::Operator *prolongation = space.GetProlongationMatrix();
|
|
|
|
if (prolongation != nullptr) {
|
|
prolongation->MultTranspose(local, true_vector);
|
|
} else {
|
|
true_vector = local;
|
|
}
|
|
}
|
|
|
|
mfem::Vector assemble_monopole_projection_rhs(
|
|
mean_field::fem::FEM &f,
|
|
const mfem::GridFunction &displacement,
|
|
const double mass,
|
|
const double stellar_radius
|
|
) {
|
|
mfem::Vector local_rhs(f.gravityFluxFes->GetVSize());
|
|
local_rhs = 0.0;
|
|
|
|
mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension());
|
|
|
|
const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute();
|
|
const int quadrature_order = 2 * f.gravityFluxFes->GetMaxElementOrder() + 8;
|
|
|
|
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
|
const mfem::FiniteElement &gravity_element = *f.gravityFluxFes->GetFE(element_id);
|
|
const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id);
|
|
const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id);
|
|
|
|
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
|
|
|
|
mfem::Array<int> gravity_dofs;
|
|
mfem::Array<int> displacement_dofs;
|
|
mfem::Array<int> compactification_dofs;
|
|
|
|
mfem::DofTransformation *gravity_transform = f.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs);
|
|
mfem::DofTransformation *displacement_transform =
|
|
f.displacementFes->GetElementVDofs(element_id, displacement_dofs);
|
|
mfem::DofTransformation *compactification_transform =
|
|
f.compactificationFes->GetElementDofs(element_id, compactification_dofs);
|
|
|
|
mfem::Vector element_displacement;
|
|
mfem::Vector element_compactification;
|
|
|
|
displacement.GetSubVector(displacement_dofs, element_displacement);
|
|
f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
|
|
|
|
if (displacement_transform != nullptr) {
|
|
displacement_transform->InvTransformPrimal(element_displacement);
|
|
}
|
|
|
|
if (compactification_transform != nullptr) {
|
|
compactification_transform->InvTransformPrimal(element_compactification);
|
|
}
|
|
|
|
const mean_field::mapping::ElementDisplacementData displacement_data =
|
|
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
|
|
displacement_element, element_displacement
|
|
);
|
|
|
|
const mean_field::mapping::ElementCompactificationData compactification_data(
|
|
compactification_element, element_compactification
|
|
);
|
|
|
|
const mean_field::mapping::ElementMappingData mapping_data{
|
|
.displacement = displacement_data, .compactification = compactification_data
|
|
};
|
|
|
|
const int dof_count = gravity_element.GetDof();
|
|
const int dimension = transformation->GetSpaceDim();
|
|
|
|
mfem::Vector element_rhs(dof_count);
|
|
mfem::Vector analytic_field(dimension);
|
|
mfem::Vector pulled_rhs_field(dimension);
|
|
mfem::DenseMatrix vector_shape(dof_count, dimension);
|
|
|
|
element_rhs = 0.0;
|
|
|
|
const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order);
|
|
|
|
for (int q = 0; q < rule.GetNPoints(); ++q) {
|
|
const mfem::IntegrationPoint &point = rule.IntPoint(q);
|
|
|
|
mean_field::mapping::VolumeMappingContext context;
|
|
|
|
const mean_field::mapping::MappingStatus status =
|
|
f.domainMapperStateless->EvaluateVolume(mapping_data, *transformation, point, workspace, context);
|
|
|
|
MFEM_VERIFY(
|
|
status == mean_field::mapping::MappingStatus::valid,
|
|
"Mapped monopole projection RHS failed with status "
|
|
<< static_cast<int>(status) << " on element " << element_id << ", quadrature point " << q << '.'
|
|
);
|
|
|
|
analytic_field = context.mapping.physical_position;
|
|
|
|
const double radius = analytic_field.Norml2();
|
|
|
|
MFEM_VERIFY(std::isfinite(radius) && radius > 0.0, "Invalid physical radius in projection RHS.");
|
|
|
|
if (transformation->Attribute == vacuum_attribute) {
|
|
analytic_field *= mean_field::utils::G * mass / (radius * radius * radius);
|
|
} else {
|
|
analytic_field *= mean_field::utils::G * mass / (stellar_radius * stellar_radius * stellar_radius);
|
|
}
|
|
|
|
context.mapping.mapping_jacobian.MultTranspose(analytic_field, pulled_rhs_field);
|
|
|
|
transformation->SetIntPoint(&point);
|
|
gravity_element.CalcVShape(*transformation, vector_shape);
|
|
|
|
const double weight = point.weight * transformation->Weight();
|
|
|
|
for (int i = 0; i < dof_count; ++i) {
|
|
for (int component = 0; component < dimension; ++component) {
|
|
element_rhs(i) += weight * vector_shape(i, component) * pulled_rhs_field(component);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gravity_transform != nullptr) {
|
|
gravity_transform->TransformDual(element_rhs);
|
|
}
|
|
|
|
local_rhs.AddElementVector(gravity_dofs, element_rhs);
|
|
}
|
|
|
|
mfem::Vector true_rhs;
|
|
local_to_true(*f.gravityFluxFes, local_rhs, true_rhs);
|
|
|
|
return true_rhs;
|
|
}
|
|
|
|
mfem::Vector project_monopole_gradient(
|
|
mean_field::fem::FEM &f,
|
|
const mfem::ParGridFunction &displacement,
|
|
const double mass,
|
|
const double stellar_radius
|
|
) {
|
|
mfem::Vector displacement_true;
|
|
displacement.GetTrueDofs(displacement_true);
|
|
|
|
const mfem::Vector projection_rhs = assemble_monopole_projection_rhs(f, displacement, mass, stellar_radius);
|
|
|
|
mean_field::operators::PreparedMappedHDivMassOperator mass_operator(f, *f.domainMapperStateless);
|
|
mass_operator.Prepare(displacement_true);
|
|
|
|
mfem::Vector projected_gradient(f.gravityFluxFes->GetTrueVSize());
|
|
projected_gradient = 0.0;
|
|
|
|
mfem::CGSolver solver(f.gravityFluxFes->GetComm());
|
|
solver.SetOperator(mass_operator);
|
|
solver.SetRelTol(1.0e-9);
|
|
solver.SetAbsTol(1.0e-12);
|
|
solver.SetMaxIter(2000);
|
|
solver.SetPrintLevel(0);
|
|
solver.Mult(projection_rhs, projected_gradient);
|
|
|
|
mfem::Vector projection_residual;
|
|
mass_operator.Mult(projected_gradient, projection_residual);
|
|
projection_residual -= projection_rhs;
|
|
|
|
const double source_norm = global_norm(projection_rhs, f.gravityFluxFes->GetComm());
|
|
const double residual_norm = global_norm(projection_residual, f.gravityFluxFes->GetComm());
|
|
const double relative_residual = residual_norm / std::max(source_norm, std::numeric_limits<double>::epsilon());
|
|
|
|
INFO("Mapped H(div) projection converged = " << solver.GetConverged());
|
|
INFO("Mapped H(div) projection iterations = " << solver.GetNumIterations());
|
|
INFO("Mapped H(div) projection reported final norm = " << solver.GetFinalNorm());
|
|
INFO("Mapped H(div) projection direct residual norm = " << residual_norm);
|
|
INFO("Mapped H(div) projection direct relative residual = " << relative_residual);
|
|
|
|
REQUIRE(std::isfinite(relative_residual));
|
|
REQUIRE(relative_residual < 1.0e-8);
|
|
return projected_gradient;
|
|
}
|
|
|
|
double mapped_hdiv_relative_error(
|
|
mean_field::fem::FEM &f,
|
|
const mfem::ParGridFunction &displacement,
|
|
const mfem::Vector &computed,
|
|
const mfem::Vector &reference
|
|
) {
|
|
REQUIRE(computed.Size() == reference.Size());
|
|
|
|
mfem::Vector displacement_true;
|
|
displacement.GetTrueDofs(displacement_true);
|
|
|
|
mean_field::operators::PreparedMappedHDivMassOperator mass_operator(f, *f.domainMapperStateless);
|
|
mass_operator.Prepare(displacement_true);
|
|
|
|
mfem::Vector difference(computed);
|
|
difference -= reference;
|
|
|
|
mfem::Vector difference_action;
|
|
mfem::Vector reference_action;
|
|
|
|
mass_operator.Mult(difference, difference_action);
|
|
mass_operator.Mult(reference, reference_action);
|
|
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
const double difference_energy = global_dot(difference, difference_action, communicator);
|
|
const double reference_energy = global_dot(reference, reference_action, communicator);
|
|
|
|
REQUIRE(difference_energy >= -1.0e-12 * std::abs(reference_energy));
|
|
REQUIRE(reference_energy > 0.0);
|
|
|
|
return std::sqrt(std::max(0.0, difference_energy) / reference_energy);
|
|
}
|
|
|
|
ShellMeasurement measure_exterior_shells(
|
|
mean_field::fem::FEM &f,
|
|
const mean_field::physics::GravitySolution &solution,
|
|
const mfem::GridFunction &displacement,
|
|
const MappingPath mapping_path,
|
|
const double mass
|
|
) {
|
|
std::array<ShellAccumulator, shell_count> local{};
|
|
long long local_invalid_points = 0;
|
|
|
|
mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension());
|
|
|
|
const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute();
|
|
const int quadrature_order =
|
|
2 * std::max(f.gravityPotentialFes->GetMaxElementOrder(), f.gravityFluxFes->GetMaxElementOrder()) + 8;
|
|
|
|
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
|
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
|
|
|
|
if (transformation->Attribute != vacuum_attribute) {
|
|
continue;
|
|
}
|
|
|
|
const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id);
|
|
const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id);
|
|
|
|
mfem::Array<int> displacement_dofs;
|
|
mfem::Array<int> compactification_dofs;
|
|
|
|
mfem::DofTransformation *displacement_transform =
|
|
f.displacementFes->GetElementVDofs(element_id, displacement_dofs);
|
|
mfem::DofTransformation *compactification_transform =
|
|
f.compactificationFes->GetElementDofs(element_id, compactification_dofs);
|
|
|
|
mfem::Vector element_displacement;
|
|
mfem::Vector element_compactification;
|
|
|
|
displacement.GetSubVector(displacement_dofs, element_displacement);
|
|
f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
|
|
|
|
if (displacement_transform != nullptr) {
|
|
displacement_transform->InvTransformPrimal(element_displacement);
|
|
}
|
|
|
|
if (compactification_transform != nullptr) {
|
|
compactification_transform->InvTransformPrimal(element_compactification);
|
|
}
|
|
|
|
const mean_field::mapping::ElementDisplacementData displacement_data =
|
|
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
|
|
displacement_element, element_displacement
|
|
);
|
|
|
|
const mean_field::mapping::ElementCompactificationData compactification_data(
|
|
compactification_element, element_compactification
|
|
);
|
|
|
|
const mean_field::mapping::ElementMappingData mapping_data{
|
|
.displacement = displacement_data, .compactification = compactification_data
|
|
};
|
|
|
|
mfem::Vector compactification_shape(compactification_element.GetDof());
|
|
|
|
const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order);
|
|
|
|
for (int q = 0; q < rule.GetNPoints(); ++q) {
|
|
const mfem::IntegrationPoint &point = rule.IntPoint(q);
|
|
|
|
transformation->SetIntPoint(&point);
|
|
compactification_element.CalcShape(point, compactification_shape);
|
|
|
|
const double coordinate = element_compactification * compactification_shape;
|
|
const int shell = get_shell(coordinate);
|
|
|
|
mfem::Vector reference_field(3);
|
|
mfem::Vector physical_field(3);
|
|
mfem::Vector physical_position(3);
|
|
|
|
solution.gradPhi.GetVectorValue(element_id, point, reference_field);
|
|
|
|
if (mapping_path == MappingPath::stateless) {
|
|
mean_field::mapping::VolumeMappingContext context;
|
|
|
|
const mean_field::mapping::MappingStatus status = f.domainMapperStateless->EvaluateVolume(
|
|
mapping_data, *transformation, point, workspace, context
|
|
);
|
|
|
|
if (status != mean_field::mapping::MappingStatus::valid) {
|
|
++local_invalid_points;
|
|
continue;
|
|
}
|
|
|
|
physical_position = context.mapping.physical_position;
|
|
mean_field::mapping::MapHDivFluxToPhysical(context.mapping, reference_field, physical_field);
|
|
} else {
|
|
f.mapping->GetPhysicalPoint(*transformation, point, physical_position);
|
|
|
|
mfem::DenseMatrix jacobian(3);
|
|
f.mapping->ComputeJacobian(*transformation, jacobian);
|
|
|
|
const double determinant = jacobian.Det();
|
|
|
|
if (!std::isfinite(determinant) || determinant <= 0.0) {
|
|
++local_invalid_points;
|
|
continue;
|
|
}
|
|
|
|
jacobian.Mult(reference_field, physical_field);
|
|
physical_field /= determinant;
|
|
}
|
|
|
|
const double radius = physical_position.Norml2();
|
|
|
|
if (!std::isfinite(radius) || radius <= 0.0) {
|
|
++local_invalid_points;
|
|
continue;
|
|
}
|
|
|
|
mfem::Vector radial_unit(physical_position);
|
|
radial_unit /= radius;
|
|
|
|
const double radial_field = physical_field * radial_unit;
|
|
|
|
mfem::Vector tangential_field(physical_field);
|
|
tangential_field.Add(-radial_field, radial_unit);
|
|
|
|
const double potential = solution.phi.GetValue(element_id, point);
|
|
|
|
const double scaled_potential = -radius * potential / (mean_field::utils::G * mass);
|
|
const double scaled_radial_field = radius * radius * radial_field / (mean_field::utils::G * mass);
|
|
const double scaled_tangential_field =
|
|
radius * radius * tangential_field.Norml2() / (mean_field::utils::G * mass);
|
|
|
|
if (!std::isfinite(scaled_potential) || !std::isfinite(scaled_radial_field) ||
|
|
!std::isfinite(scaled_tangential_field)) {
|
|
++local_invalid_points;
|
|
continue;
|
|
}
|
|
|
|
const double weight = point.weight * transformation->Weight();
|
|
|
|
ShellAccumulator &accumulator = local[shell];
|
|
|
|
++accumulator.points;
|
|
accumulator.weight += weight;
|
|
|
|
accumulator.minimum_radius = std::min(accumulator.minimum_radius, radius);
|
|
accumulator.maximum_radius = std::max(accumulator.maximum_radius, radius);
|
|
|
|
accumulator.potential_error_squared += weight * (scaled_potential - 1.0) * (scaled_potential - 1.0);
|
|
accumulator.radial_error_squared += weight * (scaled_radial_field - 1.0) * (scaled_radial_field - 1.0);
|
|
accumulator.tangential_squared += weight * scaled_tangential_field * scaled_tangential_field;
|
|
}
|
|
}
|
|
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
ShellMeasurement measurement;
|
|
|
|
MPI_Allreduce(&local_invalid_points, &measurement.invalid_points, 1, MPI_LONG_LONG, MPI_SUM, communicator);
|
|
|
|
for (int shell = 0; shell < shell_count; ++shell) {
|
|
long long points = 0;
|
|
|
|
MPI_Allreduce(&local[shell].points, &points, 1, MPI_LONG_LONG, MPI_SUM, communicator);
|
|
|
|
const double local_sums[4]{
|
|
local[shell].weight, local[shell].potential_error_squared, local[shell].radial_error_squared,
|
|
local[shell].tangential_squared
|
|
};
|
|
|
|
double sums[4]{};
|
|
|
|
MPI_Allreduce(local_sums, sums, 4, MPI_DOUBLE, MPI_SUM, communicator);
|
|
|
|
double minimum_radius = 0.0;
|
|
double maximum_radius = 0.0;
|
|
|
|
MPI_Allreduce(&local[shell].minimum_radius, &minimum_radius, 1, MPI_DOUBLE, MPI_MIN, communicator);
|
|
MPI_Allreduce(&local[shell].maximum_radius, &maximum_radius, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
|
|
|
measurement.shells[shell] = {
|
|
.points = points,
|
|
.minimum_radius = minimum_radius,
|
|
.maximum_radius = maximum_radius,
|
|
.potential_rms_error =
|
|
sums[0] > 0.0 ? std::sqrt(sums[1] / sums[0]) : std::numeric_limits<double>::infinity(),
|
|
.radial_rms_error =
|
|
sums[0] > 0.0 ? std::sqrt(sums[2] / sums[0]) : std::numeric_limits<double>::infinity(),
|
|
.tangential_rms = sums[0] > 0.0 ? std::sqrt(sums[3] / sums[0]) : std::numeric_limits<double>::infinity()
|
|
};
|
|
}
|
|
|
|
return measurement;
|
|
}
|
|
|
|
GravitationalEnergies compute_stellar_energies(
|
|
mean_field::fem::FEM &f,
|
|
const mfem::GridFunction &density,
|
|
const mean_field::physics::GravitySolution &solution,
|
|
const mfem::GridFunction &displacement
|
|
) {
|
|
mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension());
|
|
|
|
double local_binding = 0.0;
|
|
double local_virial = 0.0;
|
|
long long local_invalid_points = 0;
|
|
double local_minimum_determinant = std::numeric_limits<double>::infinity();
|
|
double local_maximum_determinant = -std::numeric_limits<double>::infinity();
|
|
|
|
const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute();
|
|
|
|
const int order =
|
|
2 * std::max(f.gravityPotentialFes->GetMaxElementOrder(), f.gravityFluxFes->GetMaxElementOrder()) + 8;
|
|
std::array<long long, mapping_status_count> local_status_counts{};
|
|
|
|
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
|
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id);
|
|
if (transformation->Attribute == vacuum_attribute) {
|
|
continue;
|
|
}
|
|
|
|
const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id);
|
|
const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id);
|
|
|
|
mfem::Array<int> displacement_dofs;
|
|
mfem::Array<int> compactification_dofs;
|
|
|
|
mfem::DofTransformation *displacement_transform =
|
|
f.displacementFes->GetElementVDofs(element_id, displacement_dofs);
|
|
mfem::DofTransformation *compactification_transform =
|
|
f.compactificationFes->GetElementDofs(element_id, compactification_dofs);
|
|
|
|
mfem::Vector element_displacement;
|
|
mfem::Vector element_compactification;
|
|
|
|
displacement.GetSubVector(displacement_dofs, element_displacement);
|
|
f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification);
|
|
|
|
if (displacement_transform != nullptr) {
|
|
displacement_transform->InvTransformPrimal(element_displacement);
|
|
}
|
|
|
|
if (compactification_transform != nullptr) {
|
|
compactification_transform->InvTransformPrimal(element_compactification);
|
|
}
|
|
|
|
const mean_field::mapping::ElementDisplacementData displacement_data =
|
|
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
|
|
displacement_element, element_displacement
|
|
);
|
|
|
|
const mean_field::mapping::ElementCompactificationData compactification_data(
|
|
compactification_element, element_compactification
|
|
);
|
|
|
|
const mean_field::mapping::ElementMappingData mapping_data{
|
|
.displacement = displacement_data, .compactification = compactification_data
|
|
};
|
|
|
|
const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), order);
|
|
|
|
for (int q = 0; q < rule.GetNPoints(); ++q) {
|
|
const mfem::IntegrationPoint &point = rule.IntPoint(q);
|
|
|
|
mean_field::mapping::VolumeMappingContext context;
|
|
|
|
const mean_field::mapping::MappingStatus status =
|
|
f.domainMapperStateless->EvaluateVolume(mapping_data, *transformation, point, workspace, context);
|
|
|
|
const double mapping_determinant = context.mapping.mapping_determinant;
|
|
if (std::isfinite(mapping_determinant)) {
|
|
local_minimum_determinant = std::min(local_minimum_determinant, mapping_determinant);
|
|
local_maximum_determinant = std::max(local_maximum_determinant, mapping_determinant);
|
|
}
|
|
|
|
const int status_index = mapping_status_index(status);
|
|
MFEM_VERIFY(status_index >= 0 && status_index < mapping_status_count, "Unexpected mapping status.");
|
|
++local_status_counts[status_index];
|
|
|
|
if (status != mean_field::mapping::MappingStatus::valid) {
|
|
++local_invalid_points;
|
|
continue;
|
|
}
|
|
|
|
if (status != mean_field::mapping::MappingStatus::valid) {
|
|
++local_invalid_points;
|
|
continue;
|
|
}
|
|
|
|
mfem::Vector reference_field(3);
|
|
mfem::Vector physical_field(3);
|
|
|
|
solution.gradPhi.GetVectorValue(element_id, point, reference_field);
|
|
|
|
mean_field::mapping::MapHDivFluxToPhysical(context.mapping, reference_field, physical_field);
|
|
|
|
const double rho = density.GetValue(element_id, point);
|
|
const double phi = solution.phi.GetValue(element_id, point);
|
|
|
|
local_binding += 0.5 * rho * phi * context.quadrature.weight;
|
|
local_virial -= rho * (context.mapping.physical_position * physical_field) * context.quadrature.weight;
|
|
}
|
|
}
|
|
|
|
GravitationalEnergies energies;
|
|
|
|
MPI_Comm communicator = f.densityFes->GetComm();
|
|
|
|
MPI_Allreduce(&local_binding, &energies.binding, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
|
MPI_Allreduce(&local_virial, &energies.virial, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
|
MPI_Allreduce(&local_invalid_points, &energies.invalid_points, 1, MPI_LONG_LONG, MPI_SUM, communicator);
|
|
MPI_Allreduce(
|
|
local_status_counts.data(), energies.mapping_status_counts.data(), mapping_status_count, MPI_LONG_LONG,
|
|
MPI_SUM, communicator
|
|
);
|
|
MPI_Allreduce(
|
|
&local_minimum_determinant, &energies.minimum_mapping_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator
|
|
);
|
|
MPI_Allreduce(
|
|
&local_maximum_determinant, &energies.maximum_mapping_determinant, 1, MPI_DOUBLE, MPI_MAX, communicator
|
|
);
|
|
|
|
return energies;
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE(
|
|
"New Gravity Monopole Accuracy And Projection Floor",
|
|
tags::gravity &tags::analytic_comparison &tags::initialization &tags::integration &tags::accuracy
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
args.p.rtol = 1.0e-13;
|
|
args.p.max_iters = std::max(args.p.max_iters, 1000);
|
|
|
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.mapping != nullptr);
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
REQUIRE(f.compactificationCoordinate != nullptr);
|
|
|
|
const double radius = mean_field::utils::RADIUS;
|
|
const double mass = mean_field::utils::MASS;
|
|
|
|
const double density_value = mass / ((4.0 / 3.0) * M_PI * radius * radius * radius);
|
|
|
|
mfem::ParGridFunction displacement(f.displacementFes.get());
|
|
displacement = 0.0;
|
|
|
|
f.mapping->ResetDisplacement();
|
|
mean_field::physics::update_stiffness_matrix(f);
|
|
|
|
mfem::GridFunction density(f.densityFes.get());
|
|
density = density_value;
|
|
|
|
zero_vacuum_density(f, density);
|
|
|
|
mean_field::analysis::conserve_mass(f, density, mass);
|
|
|
|
f.com = mean_field::analysis::get_com(f, density);
|
|
f.Q = mean_field::physics::compute_quadrupole_moment_tensor(f, density, f.com);
|
|
|
|
const mean_field::physics::GravitySolution legacy_solution = mean_field::physics::grav_potential(f, args, density);
|
|
|
|
const mean_field::physics::GravitySolution numerical_solution =
|
|
mean_field::physics::grav_potential_new(f, args, density, displacement);
|
|
|
|
MonopolePotentialCoefficient potential_coefficient(f, *f.domainMapperStateless, displacement, mass, radius);
|
|
|
|
mean_field::physics::GravitySolution projected_solution(f);
|
|
projected_solution.phi = 0.0;
|
|
projected_solution.gradPhi = 0.0;
|
|
|
|
projected_solution.phi.ProjectCoefficient(potential_coefficient);
|
|
|
|
const mfem::Vector projected_gradient_true = project_monopole_gradient(f, displacement, mass, radius);
|
|
|
|
projected_solution.gradPhi.SetFromTrueDofs(projected_gradient_true);
|
|
|
|
const ShellMeasurement legacy =
|
|
measure_exterior_shells(f, legacy_solution, displacement, MappingPath::legacy, mass);
|
|
|
|
const ShellMeasurement numerical =
|
|
measure_exterior_shells(f, numerical_solution, displacement, MappingPath::stateless, mass);
|
|
|
|
const ShellMeasurement projected =
|
|
measure_exterior_shells(f, projected_solution, displacement, MappingPath::stateless, mass);
|
|
|
|
const GravitationalEnergies energies = compute_stellar_energies(f, density, numerical_solution, displacement);
|
|
|
|
mfem::Vector numerical_gradient;
|
|
mfem::Vector numerical_potential;
|
|
mfem::Vector projected_gradient;
|
|
mfem::Vector projected_potential;
|
|
|
|
numerical_solution.gradPhi.GetTrueDofs(numerical_gradient);
|
|
numerical_solution.phi.GetTrueDofs(numerical_potential);
|
|
|
|
projected_solution.gradPhi.GetTrueDofs(projected_gradient);
|
|
projected_solution.phi.GetTrueDofs(projected_potential);
|
|
|
|
MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
|
|
|
const double gradient_projection_gap =
|
|
mapped_hdiv_relative_error(f, displacement, numerical_gradient, projected_gradient);
|
|
|
|
const double potential_projection_gap =
|
|
global_relative_error(numerical_potential, projected_potential, communicator);
|
|
|
|
double maximum_numerical_potential_error = 0.0;
|
|
double maximum_projected_potential_error = 0.0;
|
|
double maximum_numerical_radial_error = 0.0;
|
|
double maximum_projected_radial_error = 0.0;
|
|
double maximum_numerical_tangential = 0.0;
|
|
double maximum_projected_tangential = 0.0;
|
|
|
|
std::ostringstream report;
|
|
|
|
for (int shell = 0; shell < shell_count; ++shell) {
|
|
const ShellMetrics &old_shell = legacy.shells[shell];
|
|
const ShellMetrics &numerical_shell = numerical.shells[shell];
|
|
const ShellMetrics &projected_shell = projected.shells[shell];
|
|
|
|
maximum_numerical_potential_error =
|
|
std::max(maximum_numerical_potential_error, numerical_shell.potential_rms_error);
|
|
|
|
maximum_projected_potential_error =
|
|
std::max(maximum_projected_potential_error, projected_shell.potential_rms_error);
|
|
|
|
maximum_numerical_radial_error = std::max(maximum_numerical_radial_error, numerical_shell.radial_rms_error);
|
|
|
|
maximum_projected_radial_error = std::max(maximum_projected_radial_error, projected_shell.radial_rms_error);
|
|
|
|
maximum_numerical_tangential = std::max(maximum_numerical_tangential, numerical_shell.tangential_rms);
|
|
|
|
maximum_projected_tangential = std::max(maximum_projected_tangential, projected_shell.tangential_rms);
|
|
|
|
report << "shell " << shell << " xi=[" << shell_boundaries[shell] << ", " << shell_boundaries[shell + 1]
|
|
<< ")\n"
|
|
<< " legacy radius=[" << old_shell.minimum_radius << ", " << old_shell.maximum_radius << "]\n"
|
|
<< " new radius=[" << numerical_shell.minimum_radius << ", " << numerical_shell.maximum_radius << "]\n"
|
|
<< " potential error: legacy=" << old_shell.potential_rms_error
|
|
<< ", solved=" << numerical_shell.potential_rms_error
|
|
<< ", projection=" << projected_shell.potential_rms_error << '\n'
|
|
<< " radial error: legacy=" << old_shell.radial_rms_error
|
|
<< ", solved=" << numerical_shell.radial_rms_error << ", projection=" << projected_shell.radial_rms_error
|
|
<< '\n'
|
|
<< " tangential amplitude: legacy=" << old_shell.tangential_rms
|
|
<< ", solved=" << numerical_shell.tangential_rms << ", projection=" << projected_shell.tangential_rms
|
|
<< '\n';
|
|
}
|
|
|
|
const double analytic_energy = -3.0 * mean_field::utils::G * mass * mass / (5.0 * radius);
|
|
|
|
const double binding_error = std::abs(energies.binding - analytic_energy) / std::abs(analytic_energy);
|
|
const double virial_error = std::abs(energies.virial - analytic_energy) / std::abs(analytic_energy);
|
|
const double consistency_error = std::abs(energies.binding - energies.virial) / std::abs(energies.binding);
|
|
|
|
INFO(report.str());
|
|
|
|
INFO("Gradient solution/projection mapped H(div) gap = " << gradient_projection_gap);
|
|
INFO("Potential solution/projection DOF gap = " << potential_projection_gap);
|
|
INFO("Analytic energy = " << analytic_energy);
|
|
INFO("Computed binding energy = " << energies.binding);
|
|
INFO("Computed virial energy = " << energies.virial);
|
|
INFO("Relative binding error = " << binding_error);
|
|
INFO("Relative virial error = " << virial_error);
|
|
INFO("Relative virial consistency error = " << consistency_error);
|
|
|
|
REQUIRE(legacy.invalid_points == 0);
|
|
REQUIRE(numerical.invalid_points == 0);
|
|
REQUIRE(projected.invalid_points == 0);
|
|
REQUIRE(energies.invalid_points == 0);
|
|
|
|
for (int shell = 0; shell < shell_count; ++shell) {
|
|
REQUIRE(legacy.shells[shell].points > 0);
|
|
REQUIRE(numerical.shells[shell].points > 0);
|
|
REQUIRE(projected.shells[shell].points > 0);
|
|
}
|
|
|
|
CHECK(maximum_numerical_potential_error < 5.0e-2);
|
|
CHECK(maximum_projected_potential_error < 5.0e-2);
|
|
CHECK(maximum_numerical_radial_error < 5.0e-3);
|
|
CHECK(maximum_projected_radial_error < 5.0e-3);
|
|
CHECK(maximum_numerical_tangential < 5.0e-3);
|
|
CHECK(maximum_projected_tangential < 5.0e-3);
|
|
CHECK(gradient_projection_gap < 5.0e-3);
|
|
CHECK(potential_projection_gap < maximum_numerical_potential_error);
|
|
|
|
constexpr double virial_target = 1.0e-5;
|
|
|
|
CHECK(binding_error < virial_target);
|
|
CHECK(virial_error < virial_target);
|
|
CHECK(consistency_error < virial_target);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"New Gravity Virial Consistency Across Volume Preserving Deformation",
|
|
tags::gravity &tags::self_consistency &tags::initialization &tags::integration &tags::accuracy
|
|
) {
|
|
auto args = test_utils::setup_args();
|
|
args.p.rtol = 1.0e-13;
|
|
args.p.max_iters = std::max(args.p.max_iters, 1000);
|
|
|
|
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
|
|
|
REQUIRE(f.mapping != nullptr);
|
|
REQUIRE(f.domainMapperStateless != nullptr);
|
|
|
|
const double radius = mean_field::utils::RADIUS;
|
|
const double mass = mean_field::utils::MASS;
|
|
|
|
const double central_density = 15.0 * mass / (8.0 * M_PI * radius * radius * radius);
|
|
|
|
auto density_function = [central_density, radius](const mfem::Vector &position) {
|
|
const double normalized_radius_squared = (position * position) / (radius * radius);
|
|
return central_density * std::max(0.0, 1.0 - normalized_radius_squared);
|
|
};
|
|
|
|
mfem::FunctionCoefficient density_coefficient(density_function);
|
|
mfem::GridFunction density(f.densityFes.get());
|
|
density.ProjectCoefficient(density_coefficient);
|
|
|
|
zero_vacuum_density(f, density);
|
|
|
|
mean_field::analysis::conserve_mass(f, density, mass);
|
|
|
|
constexpr std::array<double, 7> amplitudes{0.0, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0};
|
|
|
|
std::array<double, amplitudes.size()> consistency_errors{};
|
|
std::array<double, amplitudes.size()> normalized_quadrupoles{};
|
|
std::array<double, amplitudes.size()> binding_energies{};
|
|
std::array<double, amplitudes.size()> virial_energies{};
|
|
|
|
std::ostringstream report;
|
|
|
|
for (std::size_t index = 0; index < amplitudes.size(); ++index) {
|
|
const double amplitude = amplitudes[index];
|
|
|
|
const double x_scale = 1.0 + 0.15 * amplitude;
|
|
const double y_scale = 1.0 - 0.05 * amplitude;
|
|
const double z_scale = 1.0 / (x_scale * y_scale);
|
|
|
|
REQUIRE_THAT(x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14));
|
|
|
|
auto displacement_function = [x_scale, y_scale, z_scale](const mfem::Vector &position, mfem::Vector &value) {
|
|
value.SetSize(3);
|
|
|
|
value(0) = (x_scale - 1.0) * position(0);
|
|
value(1) = (y_scale - 1.0) * position(1);
|
|
value(2) = (z_scale - 1.0) * position(2);
|
|
};
|
|
|
|
mfem::VectorFunctionCoefficient displacement_coefficient(3, displacement_function);
|
|
mfem::ParGridFunction displacement(f.displacementFes.get());
|
|
displacement.ProjectCoefficient(displacement_coefficient);
|
|
|
|
f.mapping->SetDisplacement(displacement);
|
|
mean_field::physics::update_stiffness_matrix(f);
|
|
|
|
f.com = mean_field::analysis::get_com(f, density);
|
|
f.Q = mean_field::physics::compute_quadrupole_moment_tensor(f, density, f.com);
|
|
const mfem::FiniteElementSpace *nodal_space = f.mesh->GetNodalFESpace();
|
|
|
|
const mean_field::physics::GravitySolution solution =
|
|
mean_field::physics::grav_potential_new(f, args, density, displacement);
|
|
const GravitationalEnergies energies = compute_stellar_energies(f, density, solution, displacement);
|
|
|
|
CAPTURE(amplitude, x_scale, y_scale, z_scale);
|
|
INFO(
|
|
"Mapping status valid = "
|
|
<< energies.mapping_status_counts[mapping_status_index(mean_field::mapping::MappingStatus::valid)]
|
|
);
|
|
INFO(
|
|
"Mapping status invalid_dimension = "
|
|
<< energies
|
|
.mapping_status_counts[mapping_status_index(mean_field::mapping::MappingStatus::invalid_dimension)]
|
|
);
|
|
INFO(
|
|
"Mapping status non_finite_input = "
|
|
<< energies
|
|
.mapping_status_counts[mapping_status_index(mean_field::mapping::MappingStatus::non_finite_input)]
|
|
);
|
|
INFO(
|
|
"Mapping status invalid_reference_radius = " << energies.mapping_status_counts[mapping_status_index(
|
|
mean_field::mapping::MappingStatus::invalid_reference_radius
|
|
)]
|
|
);
|
|
INFO(
|
|
"Mapping status at_compactified_infinity = " << energies.mapping_status_counts[mapping_status_index(
|
|
mean_field::mapping::MappingStatus::at_compactified_infinity
|
|
)]
|
|
);
|
|
INFO(
|
|
"Mapping status outside_reference_domain = " << energies.mapping_status_counts[mapping_status_index(
|
|
mean_field::mapping::MappingStatus::outside_reference_domain
|
|
)]
|
|
);
|
|
INFO(
|
|
"Mapping status non_finite_result = "
|
|
<< energies
|
|
.mapping_status_counts[mapping_status_index(mean_field::mapping::MappingStatus::non_finite_result)]
|
|
);
|
|
INFO(
|
|
"Mapping status non_positive_determinant = " << energies.mapping_status_counts[mapping_status_index(
|
|
mean_field::mapping::MappingStatus::non_positive_determinant
|
|
)]
|
|
);
|
|
INFO("Total invalid mapping points = " << energies.invalid_points);
|
|
INFO("Mesh nodal order = " << (nodal_space != nullptr ? nodal_space->GetMaxElementOrder() : -1));
|
|
INFO("Displacement order = " << f.displacementFes->GetMaxElementOrder());
|
|
INFO("Minimum discrete mapping determinant = " << energies.minimum_mapping_determinant);
|
|
INFO("Maximum discrete mapping determinant = " << energies.maximum_mapping_determinant);
|
|
|
|
REQUIRE(energies.invalid_points == 0);
|
|
REQUIRE(std::isfinite(energies.binding));
|
|
REQUIRE(std::isfinite(energies.virial));
|
|
REQUIRE(energies.binding < 0.0);
|
|
REQUIRE(energies.virial < 0.0);
|
|
|
|
binding_energies[index] = energies.binding;
|
|
virial_energies[index] = energies.virial;
|
|
|
|
consistency_errors[index] = std::abs(energies.binding - energies.virial) / std::abs(energies.binding);
|
|
|
|
normalized_quadrupoles[index] = f.Q.FNorm() / (mass * radius * radius);
|
|
|
|
report << "amplitude=" << amplitude << ", scales=(" << x_scale << ", " << y_scale << ", " << z_scale
|
|
<< "), normalized quadrupole=" << normalized_quadrupoles[index]
|
|
<< ", binding=" << binding_energies[index] << ", virial=" << virial_energies[index]
|
|
<< ", consistency error=" << consistency_errors[index] << '\n';
|
|
}
|
|
|
|
INFO(report.str());
|
|
|
|
for (std::size_t index = 1; index < amplitudes.size(); ++index) {
|
|
CHECK(normalized_quadrupoles[index] > normalized_quadrupoles[index - 1]);
|
|
}
|
|
|
|
CHECK(consistency_errors[0] < 1.0e-5);
|
|
for (std::size_t index = 1; index < amplitudes.size(); ++index) {
|
|
CHECK(consistency_errors[index] < 5.0e-5);
|
|
}
|
|
}
|