Files
MeanField/tests/integrators/centrifugal.cpp
2026-09-04 07:54:10 -04:00

1140 lines
52 KiB
C++

#include "profile.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
using namespace mean_field;
namespace {
struct SerialMappingData {
explicit SerialMappingData(mfem::Mesh &mesh)
: compactification_fes(
&mesh,
&compactification_fec
),
compactification_coordinate(&compactification_fes),
mapper(field_dof_test_utils::make_domain_mapper()) {
compactification_coordinate = 0.0;
}
mfem::H1_FECollection compactification_fec{1, 3};
mfem::FiniteElementSpace compactification_fes;
mfem::GridFunction compactification_coordinate;
mapping::DomainMapper mapper;
};
double compute_roche_surface_scale(
const double rotation_fraction,
const double sine_theta_squared
) {
const double eta = (8.0 / 27.0) * rotation_fraction * rotation_fraction;
double surface_scale = 1.0;
for (int iteration = 0; iteration < 20; ++iteration) {
const double residual =
1.0 / surface_scale + 0.5 * eta * surface_scale * surface_scale * sine_theta_squared - 1.0;
const double derivative = -1.0 / (surface_scale * surface_scale) + eta * surface_scale * sine_theta_squared;
surface_scale -= residual / derivative;
}
return surface_scale;
}
} // namespace
TEST_CASE(
"Centrifugal Integrator Matches Manufactured Cartesian Load",
tags::rotation_integrator_unit
) {
constexpr int dim = 3;
constexpr double density = 1.7;
constexpr double omega_value = 2.3;
constexpr double tolerance = 1.0e-12;
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0);
mfem::H1_FECollection velocity_fec(1, dim);
mfem::L2_FECollection density_fec(0, dim);
mfem::H1_FECollection displacement_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0;
SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim);
omega = 0.0;
omega(2) = omega_value;
integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0);
quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production);
quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
position_order, utils::DOMAINS::STELLAR, mapping_kind
);
const int velocity_dofs_count = velocity_element->GetDof();
const int density_dofs_count = density_element->GetDof();
mfem::Vector velocity_dofs(dim * velocity_dofs_count);
mfem::Vector density_dofs(density_dofs_count);
velocity_dofs = 0.0;
density_dofs = density;
mfem::Array<const mfem::FiniteElement *> elements(2);
elements[0] = velocity_element;
elements[1] = density_element;
mfem::Array<const mfem::Vector *> element_state(2);
element_state[0] = &velocity_dofs;
element_state[1] = &density_dofs;
mfem::Vector velocity_residual;
mfem::Vector density_residual;
mfem::Array<mfem::Vector *> element_residual(2);
element_residual[0] = &velocity_residual;
element_residual[1] = &density_residual;
integrator.AssembleElementVector(elements, *transformation, element_state, element_residual);
auto residual_action = [&](const int component, const int coordinate_weight) {
mfem::Vector test_dofs(dim * velocity_dofs_count);
mfem::Vector x_physical(dim);
test_dofs = 0.0;
const mfem::IntegrationRule &nodes = velocity_element->GetNodes();
for (int i = 0; i < velocity_dofs_count; ++i) {
transformation->Transform(nodes.IntPoint(i), x_physical);
test_dofs(i + component * velocity_dofs_count) =
coordinate_weight < 0 ? 1.0 : x_physical(coordinate_weight);
}
return test_dofs * velocity_residual;
};
constexpr double force_scale = density * omega_value * omega_value;
CHECK_THAT(residual_action(0, -1), Catch::Matchers::WithinAbs(-0.5 * force_scale, tolerance));
CHECK_THAT(residual_action(1, -1), Catch::Matchers::WithinAbs(-0.5 * force_scale, tolerance));
CHECK_THAT(residual_action(2, -1), Catch::Matchers::WithinAbs(0.0, tolerance));
CHECK_THAT(residual_action(0, 0), Catch::Matchers::WithinAbs(-force_scale / 3.0, tolerance));
CHECK_THAT(residual_action(1, 1), Catch::Matchers::WithinAbs(-force_scale / 3.0, tolerance));
}
TEST_CASE(
"Centrifugal Integrator Jacobian Matches Residual Linearization",
tags::rotation_integrator_unit
) {
constexpr int dim = 3;
constexpr double step = 1.0e-6;
constexpr double finite_difference_tolerance = 1.0e-9;
constexpr double exact_tolerance = 1.0e-12;
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0);
mfem::H1_FECollection velocity_fec(1, dim);
mfem::L2_FECollection density_fec(1, dim);
mfem::H1_FECollection displacement_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0;
SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim);
omega(0) = 0.7;
omega(1) = -1.1;
omega(2) = 1.6;
integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0);
quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production);
quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
position_order, utils::DOMAINS::STELLAR, mapping_kind
);
const int velocity_size = dim * velocity_element->GetDof();
const int density_size = density_element->GetDof();
mfem::Vector velocity_dofs(velocity_size);
mfem::Vector density_dofs(density_size);
mfem::Vector velocity_direction(velocity_size);
mfem::Vector density_direction(density_size);
for (int i = 0; i < velocity_size; ++i) {
velocity_dofs(i) = 0.03 * static_cast<double>(i + 1);
velocity_direction(i) = (i % 2 == 0 ? 0.04 : -0.02) * static_cast<double>(i + 1);
}
for (int i = 0; i < density_size; ++i) {
density_dofs(i) = 1.0 + 0.08 * static_cast<double>(i + 1);
density_direction(i) = (i % 2 == 0 ? 0.05 : -0.03) * static_cast<double>(i + 1);
}
mfem::Array<const mfem::FiniteElement *> elements(2);
elements[0] = velocity_element;
elements[1] = density_element;
auto assemble_velocity_residual = [&](const mfem::Vector &velocity, const mfem::Vector &density) {
mfem::Array<const mfem::Vector *> element_state(2);
element_state[0] = &velocity;
element_state[1] = &density;
mfem::Vector velocity_residual;
mfem::Vector density_residual;
mfem::Array<mfem::Vector *> element_residual(2);
element_residual[0] = &velocity_residual;
element_residual[1] = &density_residual;
integrator.AssembleElementVector(elements, *transformation, element_state, element_residual);
return velocity_residual;
};
mfem::Array<const mfem::Vector *> element_state(2);
element_state[0] = &velocity_dofs;
element_state[1] = &density_dofs;
mfem::DenseMatrix dv_dv(velocity_size, velocity_size);
mfem::DenseMatrix dv_drho(velocity_size, density_size);
mfem::DenseMatrix drho_dv(density_size, velocity_size);
mfem::DenseMatrix drho_drho(density_size, density_size);
mfem::Array2D<mfem::DenseMatrix *> element_jacobian(2, 2);
element_jacobian(0, 0) = &dv_dv;
element_jacobian(0, 1) = &dv_drho;
element_jacobian(1, 0) = &drho_dv;
element_jacobian(1, 1) = &drho_drho;
integrator.AssembleElementGrad(elements, *transformation, element_state, element_jacobian);
mfem::Vector velocity_plus(velocity_dofs);
mfem::Vector velocity_minus(velocity_dofs);
mfem::Vector density_plus(density_dofs);
mfem::Vector density_minus(density_dofs);
velocity_plus.Add(step, velocity_direction);
velocity_minus.Add(-step, velocity_direction);
density_plus.Add(step, density_direction);
density_minus.Add(-step, density_direction);
mfem::Vector residual_plus = assemble_velocity_residual(velocity_plus, density_plus);
mfem::Vector residual_minus = assemble_velocity_residual(velocity_minus, density_minus);
mfem::Vector finite_difference(residual_plus);
finite_difference -= residual_minus;
finite_difference /= 2.0 * step;
mfem::Vector jacobian_action(velocity_size);
mfem::Vector velocity_block_action(velocity_size);
mfem::Vector density_block_action(velocity_size);
dv_dv.Mult(velocity_direction, velocity_block_action);
dv_drho.Mult(density_direction, density_block_action);
add(velocity_block_action, density_block_action, jacobian_action);
mfem::Vector finite_difference_error(jacobian_action);
finite_difference_error -= finite_difference;
const double finite_difference_scale = std::max(1.0, finite_difference.Norml2());
const double relative_finite_difference_error = finite_difference_error.Norml2() / finite_difference_scale;
CHECK_THAT(relative_finite_difference_error, Catch::Matchers::WithinAbs(0.0, finite_difference_tolerance));
CHECK_THAT(velocity_block_action.Norml2(), Catch::Matchers::WithinAbs(0.0, exact_tolerance));
mfem::Vector density_direction_residual = assemble_velocity_residual(velocity_dofs, density_direction);
mfem::Vector density_linearity_error(density_block_action);
density_linearity_error -= density_direction_residual;
CHECK_THAT(density_linearity_error.Norml2(), Catch::Matchers::WithinAbs(0.0, exact_tolerance));
double inactive_block_maximum = 0.0;
for (int i = 0; i < drho_dv.Height(); ++i) {
for (int j = 0; j < drho_dv.Width(); ++j) {
inactive_block_maximum = std::max(inactive_block_maximum, std::abs(drho_dv(i, j)));
}
}
for (int i = 0; i < drho_drho.Height(); ++i) {
for (int j = 0; j < drho_drho.Width(); ++j) {
inactive_block_maximum = std::max(inactive_block_maximum, std::abs(drho_drho(i, j)));
}
}
CHECK_THAT(inactive_block_maximum, Catch::Matchers::WithinAbs(0.0, exact_tolerance));
}
TEST_CASE(
"Centrifugal Integrator Preserves Rotation Identities",
tags::rotation_integrator_unit
) {
constexpr int dim = 3;
constexpr double density = 1.4;
constexpr double omega_scale = 2.3;
constexpr double tolerance = 1.0e-12;
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0);
mfem::H1_FECollection velocity_fec(1, dim);
mfem::L2_FECollection density_fec(0, dim);
mfem::H1_FECollection displacement_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0;
SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim);
omega(0) = 0.7;
omega(1) = -1.1;
omega(2) = 1.6;
integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0);
quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production);
quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation,
position_order, utils::DOMAINS::STELLAR, mapping_kind
);
const int velocity_dofs_count = velocity_element->GetDof();
const int velocity_size = dim * velocity_dofs_count;
const int density_size = density_element->GetDof();
mfem::Vector velocity_dofs(velocity_size);
mfem::Vector density_dofs(density_size);
velocity_dofs = 0.0;
density_dofs = density;
mfem::Array<const mfem::FiniteElement *> elements(2);
elements[0] = velocity_element;
elements[1] = density_element;
auto assemble_velocity_residual = [&](const mfem::Vector &rotation) {
integrator.SetOmega(rotation);
mfem::Array<const mfem::Vector *> element_state(2);
element_state[0] = &velocity_dofs;
element_state[1] = &density_dofs;
mfem::Vector velocity_residual;
mfem::Vector density_residual;
mfem::Array<mfem::Vector *> element_residual(2);
element_residual[0] = &velocity_residual;
element_residual[1] = &density_residual;
integrator.AssembleElementVector(elements, *transformation, element_state, element_residual);
return velocity_residual;
};
const mfem::Vector baseline_residual = assemble_velocity_residual(omega);
mfem::Vector zero_omega(dim);
zero_omega = 0.0;
const mfem::Vector zero_residual = assemble_velocity_residual(zero_omega);
CHECK_THAT(zero_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance));
mfem::Vector negative_omega(omega);
negative_omega *= -1.0;
mfem::Vector sign_error = assemble_velocity_residual(negative_omega);
sign_error -= baseline_residual;
CHECK_THAT(sign_error.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance));
mfem::Vector scaled_omega(omega);
scaled_omega *= omega_scale;
mfem::Vector expected_scaled_residual(baseline_residual);
expected_scaled_residual *= omega_scale * omega_scale;
mfem::Vector scaling_error = assemble_velocity_residual(scaled_omega);
scaling_error -= expected_scaled_residual;
const double scaling_error_relative = scaling_error.Norml2() / std::max(1.0, expected_scaled_residual.Norml2());
CHECK_THAT(scaling_error_relative, Catch::Matchers::WithinAbs(0.0, tolerance));
mfem::Vector axis_test_dofs(velocity_size);
mfem::Vector torque_test_dofs(velocity_size);
mfem::Vector x_physical(dim);
mfem::Vector azimuthal_direction(dim);
axis_test_dofs = 0.0;
torque_test_dofs = 0.0;
const mfem::IntegrationRule &nodes = velocity_element->GetNodes();
for (int i = 0; i < velocity_dofs_count; ++i) {
transformation->Transform(nodes.IntPoint(i), x_physical);
azimuthal_direction(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1);
azimuthal_direction(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2);
azimuthal_direction(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0);
for (int c = 0; c < dim; ++c) {
axis_test_dofs(i + c * velocity_dofs_count) = omega(c);
torque_test_dofs(i + c * velocity_dofs_count) = azimuthal_direction(c);
}
}
const double axial_force = axis_test_dofs * baseline_residual;
const double axial_torque = torque_test_dofs * baseline_residual;
CHECK_THAT(axial_force, Catch::Matchers::WithinAbs(0.0, tolerance));
CHECK_THAT(axial_torque, Catch::Matchers::WithinAbs(0.0, tolerance));
}
TEST_CASE(
"Centrifugal Integrator Matches Rotational Virial On Roche Mappings",
tags::rotation_integrator_integration
) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
constexpr int dim = 3;
constexpr double concentration = 4.0;
constexpr double assembly_tolerance = 1.0e-7;
constexpr double position_tolerance = 1.0e-6;
const double radius = utils::RADIUS;
auto reference_density = [radius](const mfem::Vector &x) {
const double normalized_radius_squared = (x * x) / (radius * radius);
if (normalized_radius_squared >= 1.0) {
return 0.0;
}
const double denominator = 1.0 + concentration * normalized_radius_squared;
return (1.0 - normalized_radius_squared) / (denominator * denominator);
};
mfem::FunctionCoefficient density_coefficient(reference_density);
mfem::ParGridFunction density(f.densityFes.get());
density.ProjectCoefficient(density_coefficient);
mfem::ParGridFunction displacement(f.displacementFes.get());
const mfem::FiniteElement &representative_velocity_element = *f.displacementFes->GetTypicalFE();
const mfem::FiniteElement &representative_density_element = *f.densityFes->GetTypicalFE();
mfem::ElementTransformation &representative_transformation = *f.mesh->GetElementTransformation(0);
const int position_order = f.displacementFes->GetMaxElementOrder();
for (constexpr std::array<double, 8> rotation_fractions = {0.0001, 0.1, 0.25, 0.50, 0.70, 0.85, 0.95, 0.99};
const double rotation_fraction : rotation_fractions) {
CAPTURE(rotation_fraction);
auto rotation_displacement = [radius,
rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) {
displacement_value.SetSize(dim);
const double radius_squared = x * x;
if (radius_squared <= 1.0e-28) {
displacement_value = 0.0;
return;
}
const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1);
const double sine_theta_squared = cylindrical_radius_squared / radius_squared;
const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared);
const double radial_weight = std::min(radius_squared / (radius * radius), 1.0);
const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0);
for (int d = 0; d < dim; ++d) {
displacement_value(d) = (mapped_scale - 1.0) * x(d);
}
};
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
displacement.ProjectCoefficient(displacement_coefficient);
*f.displacement = displacement;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim);
omega = 0.0;
omega(2) = rotation_fraction;
integrators::CentrifugalForceIntegrator integrator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate, omega
);
const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
f.quadratureFactory->configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, representative_density_element,
representative_velocity_element, representative_transformation, position_order, utils::DOMAINS::STELLAR,
mapping_kind
);
const int reference_order =
2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16;
double local_residual_action = 0.0;
double local_discrete_reference_action = 0.0;
double local_continuous_reference_action = 0.0;
double local_minimum_map_determinant = std::numeric_limits<double>::infinity();
double local_maximum_map_determinant = std::numeric_limits<double>::lowest();
for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) {
if (f.mesh->GetAttribute(elem_id) == 3) {
continue;
}
const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id);
const mfem::FiniteElement *density_element = f.densityFes->GetFE(elem_id);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id);
const int velocity_dofs_count = velocity_element->GetDof();
const int velocity_size = dim * velocity_dofs_count;
const int density_size = density_element->GetDof();
mfem::Array<int> density_dof_indices;
mfem::Vector density_dofs;
f.densityFes->GetElementDofs(elem_id, density_dof_indices);
density.GetSubVector(density_dof_indices, density_dofs);
mfem::Vector velocity_dofs(velocity_size);
velocity_dofs = 0.0;
mfem::Array<const mfem::FiniteElement *> elements(2);
elements[0] = velocity_element;
elements[1] = density_element;
mfem::Array<const mfem::Vector *> element_state(2);
element_state[0] = &velocity_dofs;
element_state[1] = &density_dofs;
mfem::Vector velocity_residual(velocity_size);
mfem::Vector density_residual(density_size);
velocity_residual = 0.0;
density_residual = 0.0;
mfem::Array<mfem::Vector *> element_residual(2);
element_residual[0] = &velocity_residual;
element_residual[1] = &density_residual;
integrator.AssembleElementVector(elements, *transformation, element_state, element_residual);
mfem::Vector position_test_dofs(velocity_size);
mapping::MappingPointContext point_context;
mapping::VolumeMappingContext volume_context;
position_test_dofs = 0.0;
const mfem::IntegrationRule &velocity_nodes = velocity_element->GetNodes();
for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node);
MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*transformation, node, point_context) ==
mapping::MappingStatus::valid,
"Centrifugal residual reference encountered an invalid nodal mapping."
);
for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = point_context.physical_position(d);
}
}
local_residual_action += position_test_dofs * velocity_residual;
mfem::Vector velocity_shape(velocity_dofs_count);
mfem::Vector position_test_value(dim);
mfem::Vector omega_cross_position(dim);
mfem::Vector centrifugal_acceleration(dim);
const mfem::IntegrationRule &reference_rule =
mfem::IntRules.Get(transformation->GetGeometryType(), reference_order);
for (int q = 0; q < reference_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*transformation, integration_point, volume_context) ==
mapping::MappingStatus::valid,
"Centrifugal residual reference encountered an invalid volume mapping."
);
const double signed_map_determinant = volume_context.quadrature.detJ;
local_minimum_map_determinant = std::min(local_minimum_map_determinant, signed_map_determinant);
local_maximum_map_determinant = std::max(local_maximum_map_determinant, signed_map_determinant);
const mfem::Vector &x_physical = volume_context.mapping.physical_position;
velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0;
for (int i = 0; i < velocity_dofs_count; ++i) {
for (int d = 0; d < dim; ++d) {
position_test_value(d) += position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i);
}
}
omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1);
omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2);
omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0);
centrifugal_acceleration(0) = omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1);
centrifugal_acceleration(1) = omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2);
centrifugal_acceleration(2) = omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0);
const double density_value = density.GetValue(elem_id, integration_point);
local_discrete_reference_action +=
density_value * (position_test_value * centrifugal_acceleration) * volume_context.quadrature.weight;
local_continuous_reference_action +=
density_value * (x_physical * centrifugal_acceleration) * volume_context.quadrature.weight;
}
}
double global_residual_action = 0.0;
double global_discrete_reference_action = 0.0;
double global_continuous_reference_action = 0.0;
double global_minimum_map_determinant = 0.0;
double global_maximum_map_determinant = 0.0;
MPI_Comm communicator = f.mesh->GetComm();
MPI_Allreduce(&local_residual_action, &global_residual_action, 1, MPI_DOUBLE, MPI_SUM, communicator);
MPI_Allreduce(
&local_discrete_reference_action, &global_discrete_reference_action, 1, MPI_DOUBLE, MPI_SUM, communicator
);
MPI_Allreduce(
&local_continuous_reference_action, &global_continuous_reference_action, 1, MPI_DOUBLE, MPI_SUM,
communicator
);
MPI_Allreduce(
&local_minimum_map_determinant, &global_minimum_map_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator
);
MPI_Allreduce(
&local_maximum_map_determinant, &global_maximum_map_determinant, 1, MPI_DOUBLE, MPI_MAX, communicator
);
const double relative_assembly_error = std::abs(global_residual_action - global_discrete_reference_action) /
std::abs(global_discrete_reference_action);
const double relative_position_error =
std::abs(global_discrete_reference_action - global_continuous_reference_action) /
std::abs(global_continuous_reference_action);
const double equatorial_scale = compute_roche_surface_scale(rotation_fraction, 1.0);
INFO("Rotation fraction = " << rotation_fraction);
INFO("Roche equatorial scale = " << equatorial_scale);
INFO("Minimum mapping determinant = " << global_minimum_map_determinant);
INFO("Maximum mapping determinant = " << global_maximum_map_determinant);
INFO("Assembled centrifugal virial = " << global_residual_action);
INFO("Discrete reference virial = " << global_discrete_reference_action);
INFO("Continuous reference virial = " << global_continuous_reference_action);
INFO("Relative assembly error = " << relative_assembly_error);
INFO("Relative position representation error = " << relative_position_error);
REQUIRE(equatorial_scale > 1.0);
REQUIRE(global_minimum_map_determinant > 0.0);
CHECK_THAT(relative_assembly_error, Catch::Matchers::WithinAbs(0.0, assembly_tolerance));
CHECK_THAT(relative_position_error, Catch::Matchers::WithinAbs(0.0, position_tolerance));
}
*f.displacement = 0.0;
}
TEST_CASE(
"Centrifugal Virial Position Representation Is Consistent At The "
"Registered Order",
tags::rotation_integrator_integration
) {
constexpr int dim = 3;
constexpr double concentration = 4.0;
constexpr std::array<int, 1> velocity_orders = {field::Displacement::Vector::familyOrder};
constexpr std::array<double, 2> rotation_fractions = {0.70, 0.99};
std::array<std::array<double, velocity_orders.size()>, rotation_fractions.size()> position_errors{};
std::array<std::array<double, velocity_orders.size()>, rotation_fractions.size()> minimum_determinants{};
for (std::size_t order_index = 0; order_index < velocity_orders.size(); ++order_index) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
constexpr double radius = utils::RADIUS;
auto reference_density = [radius](const mfem::Vector &x) {
const double normalized_radius_squared = (x * x) / (radius * radius);
if (normalized_radius_squared >= 1.0) {
return 0.0;
}
const double denominator = 1.0 + concentration * normalized_radius_squared;
return (1.0 - normalized_radius_squared) / (denominator * denominator);
};
mfem::FunctionCoefficient density_coefficient(reference_density);
mfem::ParGridFunction density(f.densityFes.get());
density.ProjectCoefficient(density_coefficient);
mfem::ParGridFunction displacement(f.displacementFes.get());
for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) {
const double rotation_fraction = rotation_fractions[rotation_index];
auto rotation_displacement = [radius,
rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) {
displacement_value.SetSize(dim);
const double radius_squared = x * x;
if (radius_squared <= 1.0e-28) {
displacement_value = 0.0;
return;
}
const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1);
const double sine_theta_squared = cylindrical_radius_squared / radius_squared;
const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared);
const double radial_weight = std::min(radius_squared / (radius * radius), 1.0);
const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0);
for (int d = 0; d < dim; ++d) {
displacement_value(d) = (mapped_scale - 1.0) * x(d);
}
};
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
displacement.ProjectCoefficient(displacement_coefficient);
*f.displacement = displacement;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim);
omega = 0.0;
omega(2) = rotation_fraction;
const int reference_order =
2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16;
double local_discrete_action = 0.0;
double local_continuous_action = 0.0;
double local_minimum_determinant = std::numeric_limits<double>::infinity();
for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) {
if (f.mesh->GetAttribute(elem_id) == 3) {
continue;
}
const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id);
const int velocity_dofs_count = velocity_element->GetDof();
const int velocity_size = dim * velocity_dofs_count;
mfem::Vector position_test_dofs(velocity_size);
mapping::MappingPointContext point_context;
mapping::VolumeMappingContext volume_context;
position_test_dofs = 0.0;
const mfem::IntegrationRule &velocity_nodes = velocity_element->GetNodes();
for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node);
MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*transformation, node, point_context) ==
mapping::MappingStatus::valid,
"Centrifugal p-refinement reference encountered an invalid nodal mapping."
);
for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = point_context.physical_position(d);
}
}
mfem::Vector velocity_shape(velocity_dofs_count);
mfem::Vector position_test_value(dim);
mfem::Vector omega_cross_position(dim);
mfem::Vector centrifugal_acceleration(dim);
const mfem::IntegrationRule &reference_rule =
mfem::IntRules.Get(transformation->GetGeometryType(), reference_order);
for (int q = 0; q < reference_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*transformation, integration_point, volume_context) ==
mapping::MappingStatus::valid,
"Centrifugal p-refinement reference encountered an invalid volume mapping."
);
const double signed_map_determinant = volume_context.quadrature.detJ;
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
const mfem::Vector &x_physical = volume_context.mapping.physical_position;
velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0;
for (int i = 0; i < velocity_dofs_count; ++i) {
for (int d = 0; d < dim; ++d) {
position_test_value(d) +=
position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i);
}
}
omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1);
omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2);
omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0);
centrifugal_acceleration(0) =
omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1);
centrifugal_acceleration(1) =
omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2);
centrifugal_acceleration(2) =
omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0);
const double density_value = density.GetValue(elem_id, integration_point);
local_discrete_action += density_value * (position_test_value * centrifugal_acceleration) *
volume_context.quadrature.weight;
local_continuous_action +=
density_value * (x_physical * centrifugal_acceleration) * volume_context.quadrature.weight;
}
}
double global_discrete_action = 0.0;
double global_continuous_action = 0.0;
double global_minimum_determinant = 0.0;
MPI_Comm communicator = f.mesh->GetComm();
MPI_Allreduce(&local_discrete_action, &global_discrete_action, 1, MPI_DOUBLE, MPI_SUM, communicator);
MPI_Allreduce(&local_continuous_action, &global_continuous_action, 1, MPI_DOUBLE, MPI_SUM, communicator);
MPI_Allreduce(
&local_minimum_determinant, &global_minimum_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator
);
position_errors[rotation_index][order_index] =
std::abs(global_discrete_action - global_continuous_action) / std::abs(global_continuous_action);
minimum_determinants[rotation_index][order_index] = global_minimum_determinant;
}
*f.displacement = 0.0;
}
for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) {
constexpr double consistency_tolerance = 1.0e-1;
const double registered_order_error = position_errors[rotation_index][0];
CAPTURE(rotation_fractions[rotation_index]);
INFO("Registered displacement family order = " << velocity_orders.front());
INFO("Position error = " << registered_order_error);
INFO("Minimum determinant = " << minimum_determinants[rotation_index][0]);
REQUIRE(minimum_determinants[rotation_index][0] > 0.0);
REQUIRE(std::isfinite(registered_order_error));
CHECK(registered_order_error < consistency_tolerance);
}
}
TEST_CASE(
"Centrifugal Virial Position Representation Converges Under H Refinement",
tags::rotation_integrator_convergence
) {
MEAN_FIELD_PROFILE_RESET();
constexpr int dim = 3;
constexpr double concentration = 4.0;
constexpr double minimum_rate = 1.5;
constexpr double finest_level_tolerance = 1.0e-5;
constexpr std::array<int, 3> refinement_levels = {0, 1, 2};
constexpr std::array<double, 2> rotation_fractions = {0.70, 0.85};
std::array<std::array<double, refinement_levels.size()>, rotation_fractions.size()> position_errors{};
std::array<std::array<double, refinement_levels.size()>, rotation_fractions.size()> minimum_determinants{};
for (std::size_t refinement_index = 0; refinement_index < refinement_levels.size(); ++refinement_index) {
auto args = test_utils::setup_args();
fem::FEM f = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"centrifugal virial: FEM setup", 0,
fem::setup_fem(args.mesh_file, args, refinement_levels[refinement_index])
);
const double radius = utils::RADIUS;
auto reference_density = [radius](const mfem::Vector &x) {
const double normalized_radius_squared = (x * x) / (radius * radius);
if (normalized_radius_squared >= 1.0) {
return 0.0;
}
const double denominator = 1.0 + concentration * normalized_radius_squared;
return (1.0 - normalized_radius_squared) / (denominator * denominator);
};
mfem::FunctionCoefficient density_coefficient(reference_density);
mfem::ParGridFunction density(f.densityFes.get());
density.ProjectCoefficient(density_coefficient);
mfem::ParGridFunction displacement(f.displacementFes.get());
for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) {
const double rotation_fraction = rotation_fractions[rotation_index];
auto rotation_displacement = [radius,
rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) {
displacement_value.SetSize(dim);
const double radius_squared = x * x;
if (radius_squared <= 1.0e-28) {
displacement_value = 0.0;
return;
}
const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1);
const double sine_theta_squared = cylindrical_radius_squared / radius_squared;
const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared);
const double radial_weight = std::min(radius_squared / (radius * radius), 1.0);
const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0);
for (int d = 0; d < dim; ++d) {
displacement_value(d) = (mapped_scale - 1.0) * x(d);
}
};
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
MEAN_FIELD_PROFILE_CALL_WARMUP(
"centrifugal virial: displacement projection", 0,
displacement.ProjectCoefficient(displacement_coefficient);
*f.displacement = displacement
);
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim);
omega = 0.0;
omega(2) = rotation_fraction;
const int reference_order =
2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16;
double local_discrete_action = 0.0;
double local_continuous_action = 0.0;
double local_minimum_determinant = std::numeric_limits<double>::infinity();
std::uint64_t nodal_mapping_evaluations = 0;
std::uint64_t quadrature_mapping_evaluations = 0;
MEAN_FIELD_PROFILE_SCOPE_WARMUP("centrifugal virial: integration traversal", 0);
for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) {
if (f.mesh->GetAttribute(elem_id) == 3) {
continue;
}
const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id);
mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id);
const int velocity_dofs_count = velocity_element->GetDof();
const int velocity_size = dim * velocity_dofs_count;
mfem::Vector position_test_dofs(velocity_size);
mapping::MappingPointContext point_context;
mapping::VolumeMappingContext volume_context;
position_test_dofs = 0.0;
const mfem::IntegrationRule &velocity_nodes = velocity_element->GetNodes();
for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node);
MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*transformation, node, point_context) ==
mapping::MappingStatus::valid,
"Centrifugal h-refinement reference encountered an invalid nodal mapping."
);
++nodal_mapping_evaluations;
for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = point_context.physical_position(d);
}
}
mfem::Vector velocity_shape(velocity_dofs_count);
mfem::Vector position_test_value(dim);
mfem::Vector omega_cross_position(dim);
mfem::Vector centrifugal_acceleration(dim);
const mfem::IntegrationRule &reference_rule =
mfem::IntRules.Get(transformation->GetGeometryType(), reference_order);
for (int q = 0; q < reference_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*transformation, integration_point, volume_context) ==
mapping::MappingStatus::valid,
"Centrifugal h-refinement reference encountered an invalid volume mapping."
);
++quadrature_mapping_evaluations;
const double signed_map_determinant = volume_context.quadrature.detJ;
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
const mfem::Vector &x_physical = volume_context.mapping.physical_position;
velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0;
for (int i = 0; i < velocity_dofs_count; ++i) {
for (int d = 0; d < dim; ++d) {
position_test_value(d) +=
position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i);
}
}
omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1);
omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2);
omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0);
centrifugal_acceleration(0) =
omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1);
centrifugal_acceleration(1) =
omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2);
centrifugal_acceleration(2) =
omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0);
const double density_value = density.GetValue(elem_id, integration_point);
local_discrete_action += density_value * (position_test_value * centrifugal_acceleration) *
volume_context.quadrature.weight;
local_continuous_action +=
density_value * (x_physical * centrifugal_acceleration) * volume_context.quadrature.weight;
}
}
MEAN_FIELD_PROFILE_COUNT("centrifugal virial: nodal mapping evaluations", nodal_mapping_evaluations);
MEAN_FIELD_PROFILE_COUNT(
"centrifugal virial: quadrature mapping evaluations", quadrature_mapping_evaluations
);
double global_discrete_action = 0.0;
double global_continuous_action = 0.0;
double global_minimum_determinant = 0.0;
MPI_Comm communicator = f.mesh->GetComm();
MPI_Allreduce(&local_discrete_action, &global_discrete_action, 1, MPI_DOUBLE, MPI_SUM, communicator);
MPI_Allreduce(&local_continuous_action, &global_continuous_action, 1, MPI_DOUBLE, MPI_SUM, communicator);
MPI_Allreduce(
&local_minimum_determinant, &global_minimum_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator
);
position_errors[rotation_index][refinement_index] =
std::abs(global_discrete_action - global_continuous_action) / std::abs(global_continuous_action);
minimum_determinants[rotation_index][refinement_index] = global_minimum_determinant;
}
*f.displacement = 0.0;
}
MEAN_FIELD_PROFILE_PRINT(MPI_COMM_WORLD);
for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) {
const double error_h = position_errors[rotation_index][0];
const double error_h2 = position_errors[rotation_index][1];
const double error_h4 = position_errors[rotation_index][2];
REQUIRE(error_h > 0.0);
REQUIRE(error_h2 > 0.0);
REQUIRE(error_h4 > 0.0);
const double rate_h_h2 = std::log(error_h / error_h2) / std::log(2.0);
const double rate_h2_h4 = std::log(error_h2 / error_h4) / std::log(2.0);
CAPTURE(rotation_fractions[rotation_index]);
INFO("Level 0 position error = " << error_h);
INFO("Level 1 position error = " << error_h2);
INFO("Level 2 position error = " << error_h4);
INFO("Level 0 to 1 reduction = " << error_h / error_h2);
INFO("Level 1 to 2 reduction = " << error_h2 / error_h4);
INFO("Observed level 0 to 1 rate = " << rate_h_h2);
INFO("Observed level 1 to 2 rate = " << rate_h2_h4);
INFO("Level 0 minimum determinant = " << minimum_determinants[rotation_index][0]);
INFO("Level 1 minimum determinant = " << minimum_determinants[rotation_index][1]);
INFO("Level 2 minimum determinant = " << minimum_determinants[rotation_index][2]);
REQUIRE(minimum_determinants[rotation_index][0] > 0.0);
REQUIRE(minimum_determinants[rotation_index][1] > 0.0);
REQUIRE(minimum_determinants[rotation_index][2] > 0.0);
CHECK(error_h2 < error_h);
CHECK(error_h4 < error_h2);
CHECK(rate_h_h2 > minimum_rate);
CHECK(rate_h2_h4 > minimum_rate);
CHECK_THAT(error_h4, Catch::Matchers::WithinAbs(0.0, finest_level_tolerance));
}
}