1140 lines
52 KiB
C++
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));
|
|
}
|
|
}
|