feat(FieldDofMap): Completed FieldDofMap migration

also removed legacy BarotropicPolytrope implementation
This commit is contained in:
2026-08-29 08:56:36 -04:00
parent 177ae8b38a
commit 36adfa1174
104 changed files with 26967 additions and 26916 deletions

View File

@@ -44,7 +44,6 @@ target_sources(mean_field
libmeanfield/impl/analysis/integral.cpp libmeanfield/impl/analysis/integral.cpp
libmeanfield/impl/fem.cpp libmeanfield/impl/fem.cpp
libmeanfield/impl/mapping/coefficients.cpp libmeanfield/impl/mapping/coefficients.cpp
libmeanfield/impl/mapping/domain_mapper.cpp
libmeanfield/impl/mapping/compactification/kelvin.cpp libmeanfield/impl/mapping/compactification/kelvin.cpp
libmeanfield/impl/physics/gravity.cpp libmeanfield/impl/physics/gravity.cpp
libmeanfield/impl/physics/solid.cpp libmeanfield/impl/physics/solid.cpp
@@ -56,7 +55,7 @@ target_sources(mean_field
libmeanfield/impl/integrators/gravity.cpp libmeanfield/impl/integrators/gravity.cpp
libmeanfield/impl/integrators/mass_continuity.cpp libmeanfield/impl/integrators/mass_continuity.cpp
libmeanfield/impl/integrators/viscosity.cpp libmeanfield/impl/integrators/viscosity.cpp
libmeanfield/impl/mapping/domain_mapper_new.cpp libmeanfield/impl/mapping/domain_mapper.cpp
libmeanfield/impl/mapping/transformations.cpp libmeanfield/impl/mapping/transformations.cpp
libmeanfield/impl/operators/gravity_field.cpp libmeanfield/impl/operators/gravity_field.cpp
libmeanfield/impl/operators/gravity_field_jacobian.cpp libmeanfield/impl/operators/gravity_field_jacobian.cpp
@@ -98,7 +97,6 @@ target_sources(mean_field
libmeanfield/interface/mapping/compactification/compactification.cppm libmeanfield/interface/mapping/compactification/compactification.cppm
libmeanfield/interface/mapping/compactification/kelvin.cppm libmeanfield/interface/mapping/compactification/kelvin.cppm
libmeanfield/interface/mapping/compactification/options.cppm libmeanfield/interface/mapping/compactification/options.cppm
libmeanfield/interface/physics/context.cppm
libmeanfield/interface/physics/gravity.cppm libmeanfield/interface/physics/gravity.cppm
libmeanfield/interface/physics/solid.cppm libmeanfield/interface/physics/solid.cppm
libmeanfield/interface/utils/domain.cppm libmeanfield/interface/utils/domain.cppm
@@ -126,7 +124,6 @@ target_sources(mean_field
libmeanfield/interface/field/field_base.cppm libmeanfield/interface/field/field_base.cppm
libmeanfield/interface/field/field_registry.cppm libmeanfield/interface/field/field_registry.cppm
libmeanfield/interface/field/field_mfem.cppm libmeanfield/interface/field/field_mfem.cppm
libmeanfield/interface/physics/barotrope.cppm
libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm
libmeanfield/interface/operators/contexts/barotropic_closure_linearization_context.cppm libmeanfield/interface/operators/contexts/barotropic_closure_linearization_context.cppm
libmeanfield/interface/physics/rigid_rotation.cppm libmeanfield/interface/physics/rigid_rotation.cppm
@@ -256,4 +253,4 @@ catch_discover_tests(
tests tests
experiments experiments
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
) )

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,32 @@ module mean_field;
import :mapping.coefficients; import :mapping.coefficients;
namespace { namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
mfem::Array<int> make_domain_marker(
const mfem::Mesh &mesh,
const mean_field::utils::DOMAINS domain
) {
switch (domain) {
case mean_field::utils::DOMAINS::CORE:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Core, DomainSchema>(mesh);
case mean_field::utils::DOMAINS::ENVELOPE:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Envelope, DomainSchema>(mesh);
case mean_field::utils::DOMAINS::ALL:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::All, DomainSchema>(mesh);
case mean_field::utils::DOMAINS::STELLAR:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Stellar, DomainSchema>(mesh);
case mean_field::utils::DOMAINS::VACUUM:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Vacuum, DomainSchema>(mesh);
}
MFEM_ABORT("Unsupported integration domain.");
}
template <typename FormT> template <typename FormT>
const mfem::IntegrationRule &get_density_rule( const mfem::IntegrationRule &get_density_rule(
const mean_field::fem::FEM &fem, const mean_field::fem::FEM &fem,
@@ -36,14 +62,16 @@ namespace mean_field::analysis {
mfem::LinearForm lf(fem.densityFes.get()); mfem::LinearForm lf(fem.densityFes.get());
mfem::GridFunctionCoefficient gf_c(&gf); mfem::GridFunctionCoefficient gf_c(&gf);
double local_integral; double local_integral;
mfem::Array<int> elem_markers; mfem::Array<int> elem_markers = make_domain_marker(*fem.mesh, domain);
populate_element_mask(fem.mesh.get(), domain, elem_markers);
const mfem::ElementTransformation &representative_transformation = *fem.mesh->GetElementTransformation(0); const mfem::ElementTransformation &representative_transformation = *fem.mesh->GetElementTransformation(0);
const mfem::IntegrationRule &integration_rule = const mfem::IntegrationRule &integration_rule =
get_density_rule<field::Density::Form::MassConservation>(fem, representative_transformation, {}, domain); get_density_rule<field::Density::Form::MassConservation>(fem, representative_transformation, {}, domain);
if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) { if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
mapping::MappedScalarCoefficient mapped_gf_c(*fem.mapping, gf_c); mapping::MappedScalarCoefficient mapped_gf_c(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, gf_c
);
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM // ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM
// takes ownership so memory is not leaked // takes ownership so memory is not leaked
@@ -78,12 +106,17 @@ namespace mean_field::analysis {
const mfem::GridFunction &rho const mfem::GridFunction &rho
) { ) {
const int dim = fem.mesh->Dimension(); const int dim = fem.mesh->Dimension();
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
);
mfem::Vector local_com(dim); mfem::Vector local_com(dim);
local_com = 0.0; local_com = 0.0;
double local_mass = 0.0; double local_mass = 0.0;
for (int i = 0; i < fem.mesh->GetNE(); ++i) { for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3) if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(
fem.mesh->GetAttribute(i)))
continue; continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i); mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = get_density_rule<field::Density::Form::CenterOfMass>( const mfem::IntegrationRule &ir = get_density_rule<field::Density::Form::CenterOfMass>(
@@ -94,18 +127,16 @@ namespace mean_field::analysis {
const mfem::IntegrationPoint &ip = ir.IntPoint(j); const mfem::IntegrationPoint &ip = ir.IntPoint(j);
trans->SetIntPoint(&ip); trans->SetIntPoint(&ip);
double weight = trans->Weight() * ip.weight; mapping::VolumeMappingContext mapping_context;
if (fem.has_mapping()) { MFEM_VERIFY(
weight *= fem.mapping->ComputeDetJ(*trans, ip); mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) ==
} mapping::MappingStatus::valid,
"Center-of-mass integration encountered an invalid mapping."
);
const double weight = mapping_context.quadrature.weight;
double rho_val = rho.GetValue(i, ip); double rho_val = rho.GetValue(i, ip);
mfem::Vector phys_point(dim); const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
if (fem.has_mapping()) {
fem.mapping->GetPhysicalPoint(*trans, ip, phys_point);
} else {
trans->Transform(ip, phys_point);
}
const double mass_term = rho_val * weight; const double mass_term = rho_val * weight;
local_mass += mass_term; local_mass += mass_term;
@@ -151,7 +182,10 @@ namespace mean_field::analysis {
std::unique_ptr<mfem::Coefficient> s2_coeff; std::unique_ptr<mfem::Coefficient> s2_coeff;
if (fem.has_mapping()) { if (fem.has_mapping()) {
s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, s2_func); s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, s2_func
);
} else { } else {
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func); s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
} }
@@ -164,12 +198,15 @@ namespace mean_field::analysis {
const mfem::IntegrationRule &integration_rule = get_density_rule<field::Density::Form::Quadrupole>( const mfem::IntegrationRule &integration_rule = get_density_rule<field::Density::Form::Quadrupole>(
fem, representative_transformation, std::array<int, 1>{2}, utils::DOMAINS::STELLAR fem, representative_transformation, std::array<int, 1>{2}, utils::DOMAINS::STELLAR
); );
mfem::Array<int> stellar_markers; mfem::Array<int> stellar_markers =
populate_element_mask(fem.mesh.get(), utils::DOMAINS::STELLAR, stellar_markers); utils::domain::make_attribute_marker<utils::domain::Stellar, DomainSchema>(*fem.mesh);
double local_I = 0.0; double local_I = 0.0;
if (fem.has_mapping()) { if (fem.has_mapping()) {
mapping::MappedScalarCoefficient mapped_integrand(*fem.mapping, I_integrand); mapping::MappedScalarCoefficient mapped_integrand(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, I_integrand
);
auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand); auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand);
integrator->SetIntRule(&integration_rule); integrator->SetIntRule(&integration_rule);
I_lf.AddDomainIntegrator(integrator, stellar_markers); I_lf.AddDomainIntegrator(integrator, stellar_markers);
@@ -201,23 +238,21 @@ namespace mean_field::analysis {
} }
double local_volume = 0.0; double local_volume = 0.0;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
);
for (int e = 0; e < mesh.GetNE(); ++e) { for (int e = 0; e < mesh.GetNE(); ++e) {
const int attr = mesh.GetAttribute(e); const int attr = mesh.GetAttribute(e);
switch (domain) { const bool selected =
case utils::DOMAINS::ALL: domain == utils::DOMAINS::ALL ||
break; (domain == utils::DOMAINS::STELLAR &&
case utils::DOMAINS::STELLAR: DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(attr)) ||
if (attr == 3) (domain == utils::DOMAINS::VACUUM &&
continue; DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr));
break; if (!selected)
case utils::DOMAINS::VACUUM: continue;
if (attr != 3)
continue;
break;
default:
MFEM_ABORT("Unsupported domain type for volume computation.");
}
mfem::ElementTransformation *T = mesh.GetElementTransformation(e); mfem::ElementTransformation *T = mesh.GetElementTransformation(e);
const mfem::IntegrationRule &ir = const mfem::IntegrationRule &ir =
get_density_rule<field::Density::Form::MassConservation>(fem, *T, {}, domain); get_density_rule<field::Density::Form::MassConservation>(fem, *T, {}, domain);
@@ -229,7 +264,13 @@ namespace mean_field::analysis {
double dV = ip.weight * T->Weight(); double dV = ip.weight * T->Weight();
if (physical) { if (physical) {
dV *= std::fabs(fem.mapping->ComputeDetJ(*T, ip)); mapping::VolumeMappingContext context;
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*T, ip, context) ==
mapping::MappingStatus::valid,
"Mesh-volume integration encountered an invalid mapping."
);
dV = context.quadrature.weight;
} }
local_volume += dV; local_volume += dV;

View File

@@ -21,393 +21,361 @@ import :utils.misc;
import :utils.user; import :utils.user;
namespace mean_field::fem { namespace mean_field::fem {
FEM setup_fem( FEM setup_fem(const std::string &filename, const utils::Args &args,
const std::string &filename, const int extraRefine) {
const utils::Args &args, FEM fem;
const int extraRefine
) {
FEM fem;
using GravityPotential = field::Gravity::Potential; using GravityPotential = field::Gravity::Potential;
using GravityFlux = field::Gravity::Flux; using GravityFlux = field::Gravity::Flux;
using DisplacementVector = field::Displacement::Vector; using DisplacementVector = field::Displacement::Vector;
using DensityScalar = field::Density::Scalar; using DensityScalar = field::Density::Scalar;
using EnthalpyScalar = field::Enthalpy::Scalar; using EnthalpyScalar = field::Enthalpy::Scalar;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
// ===================================================================== // =====================================================================
// Section 1: Mesh construction // Section 1: Mesh construction
// ===================================================================== // =====================================================================
fem.smesh = stroid::IO::LoadStroidMesh(filename).value(); fem.smesh = stroid::IO::LoadStroidMesh(filename).value();
if (extraRefine > 0) { if (extraRefine > 0) {
stroid::refinement::UniformRefinement(fem.smesh, extraRefine); stroid::refinement::UniformRefinement(fem.smesh, extraRefine);
} }
int mpiSize = 1; int mpiSize = 1;
MPI_Comm_size(MPI_COMM_WORLD, &mpiSize); MPI_Comm_size(MPI_COMM_WORLD, &mpiSize);
const std::unique_ptr<int[]> meshPartitioning(fem.smesh.mesh->GeneratePartitioning(mpiSize, 1)); const std::unique_ptr<int[]> meshPartitioning(
fem.smesh.mesh->GeneratePartitioning(mpiSize, 1));
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1); fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh,
meshPartitioning.get(), 1);
fem.mesh->EnsureNodes(); fem.mesh->EnsureNodes();
// ===================================================================== // =====================================================================
// Section 2: Exterior compactification coordinate // Section 2: Exterior compactification coordinate
// ===================================================================== // =====================================================================
if (fem.smesh.exterior_coordinate == nullptr) { if (fem.smesh.exterior_coordinate == nullptr) {
throw std::runtime_error("Exterior coordinate not set."); throw std::runtime_error("Exterior coordinate not set.");
} }
if (fem.smesh.exterior_coordinate->space == nullptr) { if (fem.smesh.exterior_coordinate->space == nullptr) {
throw std::runtime_error("Space for exterior coordinate not set."); throw std::runtime_error("Space for exterior coordinate not set.");
} }
if (fem.smesh.exterior_coordinate->values == nullptr) { if (fem.smesh.exterior_coordinate->values == nullptr) {
throw std::runtime_error("Values for exterior coordinate not set."); throw std::runtime_error("Values for exterior coordinate not set.");
} }
const mfem::FiniteElementSpace &serialCoordinateSpace = *fem.smesh.exterior_coordinate->space; const mfem::FiniteElementSpace &serialCoordinateSpace =
*fem.smesh.exterior_coordinate->space;
const mfem::GridFunction &serialCoordinate = *fem.smesh.exterior_coordinate->values; const mfem::GridFunction &serialCoordinate =
*fem.smesh.exterior_coordinate->values;
if (serialCoordinate.FESpace() != &serialCoordinateSpace) { if (serialCoordinate.FESpace() != &serialCoordinateSpace) {
throw std::runtime_error( throw std::runtime_error(
"Exterior coordinate values are not associated with the " "Exterior coordinate values are not associated with the "
"supplied finite-element space." "supplied finite-element space.");
); }
}
if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) { if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) {
throw std::runtime_error( throw std::runtime_error(
"Exterior coordinate space is not associated with the " "Exterior coordinate space is not associated with the "
"loaded STROID mesh." "loaded STROID mesh.");
); }
}
if (serialCoordinateSpace.GetVDim() != 1) { if (serialCoordinateSpace.GetVDim() != 1) {
throw std::runtime_error("Exterior coordinate must be a scalar field."); throw std::runtime_error("Exterior coordinate must be a scalar field.");
} }
if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) { if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) {
throw std::runtime_error( throw std::runtime_error(
"Exterior coordinate value count does not match its " "Exterior coordinate value count does not match its "
"finite-element space." "finite-element space.");
); }
}
const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder(); const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder();
const int dimension = fem.mesh->Dimension(); const int dimension = fem.mesh->Dimension();
fem.compactificationFec = std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension); fem.compactificationFec =
std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension);
fem.compactificationFes = fem.compactificationFes = std::make_unique<mfem::ParFiniteElementSpace>(
std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.compactificationFec.get()); fem.mesh.get(), fem.compactificationFec.get());
mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate, meshPartitioning.get()); mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate,
meshPartitioning.get());
if (distributedCoordinate.Size() != fem.compactificationFes->GetVSize()) { if (distributedCoordinate.Size() != fem.compactificationFes->GetVSize()) {
throw std::runtime_error( throw std::runtime_error(
"Distributed exterior coordinate does not match the " "Distributed exterior coordinate does not match the "
"constructed parallel finite-element space." "constructed parallel finite-element space.");
); }
}
fem.compactificationCoordinate = std::make_unique<mfem::ParGridFunction>(fem.compactificationFes.get()); fem.compactificationCoordinate =
std::make_unique<mfem::ParGridFunction>(fem.compactificationFes.get());
*fem.compactificationCoordinate = distributedCoordinate; *fem.compactificationCoordinate = distributedCoordinate;
double localMinimum = std::numeric_limits<double>::infinity(); double localMinimum = std::numeric_limits<double>::infinity();
double localMaximum = -std::numeric_limits<double>::infinity(); double localMaximum = -std::numeric_limits<double>::infinity();
for (int index = 0; index < fem.compactificationCoordinate->Size(); ++index) { for (int index = 0; index < fem.compactificationCoordinate->Size(); ++index) {
const double value = (*fem.compactificationCoordinate)(index); const double value = (*fem.compactificationCoordinate)(index);
if (!std::isfinite(value)) { if (!std::isfinite(value)) {
throw std::runtime_error("Exterior coordinate contains a non-finite value."); throw std::runtime_error(
} "Exterior coordinate contains a non-finite value.");
localMinimum = std::min(localMinimum, value);
localMaximum = std::max(localMaximum, value);
}
double globalMinimum = 0.0;
double globalMaximum = 0.0;
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
constexpr double coordinateTolerance = 1.0e-12;
if (globalMinimum < -coordinateTolerance || globalMaximum > 1.0 + coordinateTolerance) {
throw std::runtime_error(
"Exterior coordinate lies outside the expected "
"interval [0, 1]."
);
}
// =====================================================================
// Section 3: Compile-time field realization
// =====================================================================
// ---------------------------------------------------------------------
// Gravity potential: scalar L2
// ---------------------------------------------------------------------
fem.gravityPotentialFec = GravityField::make_fec<GravityPotential>(dimension);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(*fem.mesh, *fem.gravityPotentialFec);
// ---------------------------------------------------------------------
// Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem.
// ---------------------------------------------------------------------
fem.gravityFluxFec = GravityField::make_fec<GravityFlux>(dimension);
fem.gravityFluxFes = GravityField::make_fespace<GravityFlux>(*fem.mesh, *fem.gravityFluxFec);
// ---------------------------------------------------------------------
// Displacement: vector H1. Ordering is encoded by field.mfem.
// ---------------------------------------------------------------------
fem.displacementFec = DisplacementField::make_fec<DisplacementVector>(dimension);
fem.displacementFes = DisplacementField::make_fespace<DisplacementVector>(*fem.mesh, *fem.displacementFec);
fem.displacement = std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
*fem.displacement = 0.0;
// ---------------------------------------------------------------------
// Density: scalar discontinuous L2
// ---------------------------------------------------------------------
fem.densityFec = DensityField::make_fec<DensityScalar>(dimension);
fem.densityFes = DensityField::make_fespace<DensityScalar>(*fem.mesh, *fem.densityFec);
// ---------------------------------------------------------------------
// Specific enthalpy: scalar continuous H1
// ---------------------------------------------------------------------
fem.enthalpyFec = EnthalpyField::make_fec<EnthalpyScalar>(dimension);
fem.enthalpyFes = EnthalpyField::make_fespace<EnthalpyScalar>(*fem.mesh, *fem.enthalpyFec);
// =====================================================================
// Section 4: Domain mapping
// =====================================================================
auto [stellarRadiusReference, infinityRadiusReference] =
utils::discover_bounds(fem.mesh.get(), 3)
.or_else([](const boundary::BoundsError &) -> std::expected<boundary::Bounds, boundary::BoundsError> {
throw std::runtime_error(
"Unable to determine vacuum-domain reference "
"boundaries."
);
})
.value();
fem.mapping =
std::make_unique<mapping::DomainMapper>(*fem.displacement, stellarRadiusReference, infinityRadiusReference);
// =====================================================================
// Section 5: Block offsets
//
// Legacy layouts only. New coupled operators use :utils.blocks forms.
//
// Main system: [Displacement | Density]
// Gravity system: [Flux | Potential]
// =====================================================================
fem.blockTrueOffsets.SetSize(3);
fem.blockTrueOffsets[0] = 0;
fem.blockTrueOffsets[1] = fem.displacementFes->GetTrueVSize();
fem.blockTrueOffsets[2] = fem.blockTrueOffsets[1] + fem.densityFes->GetTrueVSize();
fem.gravityBlockTrueOffsets.SetSize(3);
fem.gravityBlockTrueOffsets[0] = 0;
fem.gravityBlockTrueOffsets[1] = fem.gravityFluxFes->GetTrueVSize();
fem.gravityBlockTrueOffsets[2] = fem.gravityBlockTrueOffsets[1] + fem.gravityPotentialFes->GetTrueVSize();
// =====================================================================
// Section 6: Multipole data
// =====================================================================
fem.com.SetSize(dimension);
fem.com = 0.0;
fem.Q.SetSize(dimension, dimension);
fem.Q = 0.0;
// =====================================================================
// Section 7: Essential boundaries and domain masks
// =====================================================================
fem.essentialDisplacementTdofs.SetSize(0);
populate_element_mask(fem.mesh.get(), utils::DOMAINS::STELLAR, fem.gravityContext.stellar_mask);
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.inf_bounds = 0;
fem.boundaryContext.stellar_bounds = 0;
fem.boundaryContext.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
// =====================================================================
// Section 8: Gravity solver context
// =====================================================================
fem.gravityContext.minres = std::make_unique<mfem::MINRESSolver>(fem.mesh->GetComm());
fem.gravityContext.minres->SetRelTol(1.0e-12);
fem.gravityContext.minres->SetAbsTol(1.0e-12);
fem.gravityContext.minres->SetMaxIter(1000);
fem.gravityContext.minres->SetPrintLevel(0);
fem.gravityContext.prec_Phi = std::make_unique<mfem::HypreBoomerAMG>();
fem.gravityContext.prec_Phi->SetPrintLevel(0);
fem.gravityContext.block_prec =
std::make_unique<mfem::BlockDiagonalPreconditioner>(fem.gravityBlockTrueOffsets);
fem.gravityContext.minres->SetPreconditioner(*fem.gravityContext.block_prec);
// =====================================================================
// Section 9: Vacuum true-DOF masks
// =====================================================================
{
mfem::Array<int> vacuumMask;
utils::populate_element_mask(fem.mesh.get(), utils::DOMAINS::VACUUM, vacuumMask);
utils::populate_domain_tdofs(fem.displacementFes.get(), vacuumMask, fem.vacuumDisplacementTdofs);
utils::populate_domain_tdofs(fem.densityFes.get(), vacuumMask, fem.vacuumDensityTdofs);
utils::populate_domain_tdofs(fem.enthalpyFes.get(), vacuumMask, fem.vacuumEnthalpyTdofs);
}
// =====================================================================
// Section 10: Quadrature policy
// =====================================================================
const quadrature::QuadratureOptions &quadratureOptions = args.quadrature;
if (quadratureOptions.validation.reject_negative_boosts && quadratureOptions.global_boost < 0) {
throw std::invalid_argument("Global quadrature boost cannot be negative.");
}
quadrature::RuleSet quadratureRuleSet =
quadrature::make_rule_set(quadratureOptions.mode, quadratureOptions.global_boost);
if (quadratureOptions.fallback_fixed_order.has_value()) {
if (*quadratureOptions.fallback_fixed_order < 0) {
throw std::invalid_argument("Fallback quadrature order cannot be negative.");
}
quadratureRuleSet.fallback.fixed_order = quadratureOptions.fallback_fixed_order;
}
auto apply_quadrature_options = [&quadratureOptions](
quadrature::RuleControl &ruleControl,
const quadrature::QuadratureTermOptions &termOptions
) {
if (termOptions.fixed_order.has_value() && *termOptions.fixed_order < 0) {
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
}
if (quadratureOptions.validation.reject_negative_boosts && termOptions.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
ruleControl.boost += termOptions.additional_boost;
if (termOptions.fixed_order.has_value()) {
ruleControl.fixed_order = termOptions.fixed_order;
}
};
apply_quadrature_options(quadratureRuleSet.gravity_hdiv_mass, quadratureOptions.gravity_hdiv_mass);
apply_quadrature_options(quadratureRuleSet.gravity_divergence, quadratureOptions.gravity_divergence);
apply_quadrature_options(quadratureRuleSet.gravity_source, quadratureOptions.gravity_source);
apply_quadrature_options(quadratureRuleSet.gravity_force, quadratureOptions.gravity_force);
apply_quadrature_options(quadratureRuleSet.gravity_boundary, quadratureOptions.gravity_boundary);
apply_quadrature_options(quadratureRuleSet.centrifugal, quadratureOptions.centrifugal);
apply_quadrature_options(quadratureRuleSet.density_projection, quadratureOptions.density_projection);
apply_quadrature_options(quadratureRuleSet.eos_closure, quadratureOptions.eos_closure);
apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium, quadratureOptions.hydrostatic_equilibrium);
apply_quadrature_options(quadratureRuleSet.isobaric_surface, quadratureOptions.isobaric_surface);
apply_quadrature_options(quadratureRuleSet.mesh_extension, quadratureOptions.mesh_extension);
apply_quadrature_options(quadratureRuleSet.mass_conservation, quadratureOptions.mass_conservation);
apply_quadrature_options(quadratureRuleSet.mass_normalization, quadratureOptions.mass_normalization);
apply_quadrature_options(quadratureRuleSet.center_of_mass, quadratureOptions.center_of_mass);
apply_quadrature_options(quadratureRuleSet.quadrupole, quadratureOptions.quadrupole);
apply_quadrature_options(quadratureRuleSet.gravitational_energy, quadratureOptions.gravitational_energy);
apply_quadrature_options(quadratureRuleSet.pressure_integral, quadratureOptions.pressure_integral);
apply_quadrature_options(quadratureRuleSet.pressure_force, quadratureOptions.pressure_force);
apply_quadrature_options(quadratureRuleSet.virial, quadratureOptions.virial);
apply_quadrature_options(quadratureRuleSet.error_norm, quadratureOptions.error_norm);
apply_quadrature_options(quadratureRuleSet.roles.discretization, quadratureOptions.roles.discretization);
apply_quadrature_options(quadratureRuleSet.roles.preconditioner, quadratureOptions.roles.preconditioner);
apply_quadrature_options(quadratureRuleSet.roles.diagnostic, quadratureOptions.roles.diagnostic);
apply_quadrature_options(quadratureRuleSet.roles.projection, quadratureOptions.roles.projection);
fem.quadratureFactory =
std::make_unique<quadrature::RuleFactory>(quadrature::Policy(std::move(quadratureRuleSet)));
// =====================================================================
// Section 11: Stateless domain mapper
// =====================================================================
auto exteriorDomain =
std::make_unique<const mapping::compactification::KelvinCompactification>(args.kelvin_options);
fem.domainMapperStateless =
std::make_unique<mapping::DomainMapperStateless>(args.domain_mapper_options, std::move(exteriorDomain));
return fem;
} }
} // namespace mean_field::fem
localMinimum = std::min(localMinimum, value);
localMaximum = std::max(localMaximum, value);
}
double globalMinimum = 0.0;
double globalMaximum = 0.0;
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN,
MPI_COMM_WORLD);
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX,
MPI_COMM_WORLD);
constexpr double coordinateTolerance = 1.0e-12;
if (globalMinimum < -coordinateTolerance ||
globalMaximum > 1.0 + coordinateTolerance) {
throw std::runtime_error("Exterior coordinate lies outside the expected "
"interval [0, 1].");
}
// =====================================================================
// Section 3: Compile-time field realization
// =====================================================================
// ---------------------------------------------------------------------
// Gravity potential: scalar L2
// ---------------------------------------------------------------------
fem.gravityPotentialFec = GravityField::make_fec<GravityPotential>(dimension);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(
*fem.mesh, *fem.gravityPotentialFec);
// ---------------------------------------------------------------------
// Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem.
// ---------------------------------------------------------------------
fem.gravityFluxFec = GravityField::make_fec<GravityFlux>(dimension);
fem.gravityFluxFes =
GravityField::make_fespace<GravityFlux>(*fem.mesh, *fem.gravityFluxFec);
// ---------------------------------------------------------------------
// Displacement: vector H1. Ordering is encoded by field.mfem.
// ---------------------------------------------------------------------
fem.displacementFec =
DisplacementField::make_fec<DisplacementVector>(dimension);
fem.displacementFes = DisplacementField::make_fespace<DisplacementVector>(
*fem.mesh, *fem.displacementFec);
fem.displacement =
std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
*fem.displacement = 0.0;
// ---------------------------------------------------------------------
// Density: scalar discontinuous L2
// ---------------------------------------------------------------------
fem.densityFec = DensityField::make_fec<DensityScalar>(dimension);
fem.densityFes =
DensityField::make_fespace<DensityScalar>(*fem.mesh, *fem.densityFec);
// ---------------------------------------------------------------------
// Specific enthalpy: scalar continuous H1
// ---------------------------------------------------------------------
fem.enthalpyFec = EnthalpyField::make_fec<EnthalpyScalar>(dimension);
fem.enthalpyFes =
EnthalpyField::make_fespace<EnthalpyScalar>(*fem.mesh, *fem.enthalpyFec);
// =====================================================================
// Section 4: Multipole data
// =====================================================================
fem.com.SetSize(dimension);
fem.com = 0.0;
fem.Q.SetSize(dimension, dimension);
fem.Q = 0.0;
// =====================================================================
// Section 5: Boundary markers
// =====================================================================
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.inf_bounds = 0;
fem.boundaryContext.stellar_bounds = 0;
fem.boundaryContext
.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext
.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) -
1] = 1;
// =====================================================================
// Section 7: Quadrature policy
// =====================================================================
const quadrature::QuadratureOptions &quadratureOptions = args.quadrature;
if (quadratureOptions.validation.reject_negative_boosts &&
quadratureOptions.global_boost < 0) {
throw std::invalid_argument("Global quadrature boost cannot be negative.");
}
quadrature::RuleSet quadratureRuleSet = quadrature::make_rule_set(
quadratureOptions.mode, quadratureOptions.global_boost);
if (quadratureOptions.fallback_fixed_order.has_value()) {
if (*quadratureOptions.fallback_fixed_order < 0) {
throw std::invalid_argument(
"Fallback quadrature order cannot be negative.");
}
quadratureRuleSet.fallback.fixed_order =
quadratureOptions.fallback_fixed_order;
}
auto apply_quadrature_options = [&quadratureOptions](
quadrature::RuleControl &ruleControl,
const quadrature::QuadratureTermOptions
&termOptions) {
if (termOptions.fixed_order.has_value() && *termOptions.fixed_order < 0) {
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
}
if (quadratureOptions.validation.reject_negative_boosts &&
termOptions.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
ruleControl.boost += termOptions.additional_boost;
if (termOptions.fixed_order.has_value()) {
ruleControl.fixed_order = termOptions.fixed_order;
}
};
apply_quadrature_options(quadratureRuleSet.gravity_hdiv_mass,
quadratureOptions.gravity_hdiv_mass);
apply_quadrature_options(quadratureRuleSet.gravity_divergence,
quadratureOptions.gravity_divergence);
apply_quadrature_options(quadratureRuleSet.gravity_source,
quadratureOptions.gravity_source);
apply_quadrature_options(quadratureRuleSet.gravity_force,
quadratureOptions.gravity_force);
apply_quadrature_options(quadratureRuleSet.gravity_boundary,
quadratureOptions.gravity_boundary);
apply_quadrature_options(quadratureRuleSet.centrifugal,
quadratureOptions.centrifugal);
apply_quadrature_options(quadratureRuleSet.density_projection,
quadratureOptions.density_projection);
apply_quadrature_options(quadratureRuleSet.eos_closure,
quadratureOptions.eos_closure);
apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium,
quadratureOptions.hydrostatic_equilibrium);
apply_quadrature_options(quadratureRuleSet.isobaric_surface,
quadratureOptions.isobaric_surface);
apply_quadrature_options(quadratureRuleSet.mesh_extension,
quadratureOptions.mesh_extension);
apply_quadrature_options(quadratureRuleSet.mass_conservation,
quadratureOptions.mass_conservation);
apply_quadrature_options(quadratureRuleSet.mass_normalization,
quadratureOptions.mass_normalization);
apply_quadrature_options(quadratureRuleSet.center_of_mass,
quadratureOptions.center_of_mass);
apply_quadrature_options(quadratureRuleSet.quadrupole,
quadratureOptions.quadrupole);
apply_quadrature_options(quadratureRuleSet.gravitational_energy,
quadratureOptions.gravitational_energy);
apply_quadrature_options(quadratureRuleSet.pressure_integral,
quadratureOptions.pressure_integral);
apply_quadrature_options(quadratureRuleSet.pressure_force,
quadratureOptions.pressure_force);
apply_quadrature_options(quadratureRuleSet.virial, quadratureOptions.virial);
apply_quadrature_options(quadratureRuleSet.error_norm,
quadratureOptions.error_norm);
apply_quadrature_options(quadratureRuleSet.roles.discretization,
quadratureOptions.roles.discretization);
apply_quadrature_options(quadratureRuleSet.roles.preconditioner,
quadratureOptions.roles.preconditioner);
apply_quadrature_options(quadratureRuleSet.roles.diagnostic,
quadratureOptions.roles.diagnostic);
apply_quadrature_options(quadratureRuleSet.roles.projection,
quadratureOptions.roles.projection);
fem.quadratureFactory = std::make_unique<quadrature::RuleFactory>(
quadrature::Policy(std::move(quadratureRuleSet)));
// =====================================================================
// Section 11: Stateless domain mapper
// =====================================================================
auto exteriorDomain =
std::make_unique<const mapping::compactification::KelvinCompactification>(
args.kelvin_options);
MFEM_VERIFY(
args.domain_mapper_options.vacuum_element_attribute ==
DomainSchema::template material_attribute<utils::domain::Vacuum>(),
"The domain-mapper compactification attribute must match the vacuum "
"material registered by the "
"production domain schema.");
fem.domainMapperStateless = std::make_unique<mapping::DomainMapper>(
args.domain_mapper_options, std::move(exteriorDomain));
return fem;
}
} // namespace mean_field::fem

View File

@@ -4,7 +4,12 @@ module;
module mean_field; module mean_field;
namespace mean_field::integrators { namespace mean_field::integrators {
AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map) : m_map(map) { AdvectionIntegrator::AdvectionIntegrator(
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(mapper, displacement, compactification_coordinate) {
} }
void AdvectionIntegrator::AssembleElementVector( void AdvectionIntegrator::AssembleElementVector(
@@ -13,6 +18,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec const mfem::Array<mfem::Vector *> &elvec
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) { if (utils::is_vacuum(Tr, elvec)) {
return; return;
} }
@@ -44,7 +51,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v); fe_v->CalcShape(ip, shape_v);
fe_v->CalcDShape(ip, dshape_v_ref); fe_v->CalcDShape(ip, dshape_v_ref);
@@ -93,6 +100,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats const mfem::Array2D<mfem::DenseMatrix *> &elmats
) { ) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0]; const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1]; const mfem::FiniteElement *fe_rho = el[1];
@@ -120,7 +129,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v); fe_v->CalcShape(ip, shape_v);
fe_v->CalcDShape(ip, dshape_v_ref); fe_v->CalcDShape(ip, dshape_v_ref);
@@ -208,4 +217,4 @@ namespace mean_field::integrators {
} }
} }
} }
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -4,10 +4,12 @@ module mean_field;
namespace mean_field::integrators { namespace mean_field::integrators {
CentrifugalForceIntegrator::CentrifugalForceIntegrator( CentrifugalForceIntegrator::CentrifugalForceIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega const mfem::Vector &omega
) )
: m_map(map), : m_mapping(mapper, displacement, compactification_coordinate),
m_omega(3) { m_omega(3) {
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D"); MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
m_omega = omega; m_omega = omega;
@@ -28,6 +30,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec const mfem::Array<mfem::Vector *> &elvec
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) { if (utils::is_vacuum(Tr, elvec)) {
return; return;
} }
@@ -64,12 +68,12 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v); fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho); fe_rho->CalcShape(ip, shape_rho);
m_map.GetPhysicalPoint(Tr, ip, x_phys); m_mapping.GetPhysicalPoint(Tr, ip, x_phys);
// ω x r // ω x r
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1); a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
@@ -100,6 +104,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats const mfem::Array2D<mfem::DenseMatrix *> &elmats
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elmats)) { if (utils::is_vacuum(Tr, elmats)) {
return; return;
} }
@@ -134,12 +140,12 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v); fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho); fe_rho->CalcShape(ip, shape_rho);
m_map.GetPhysicalPoint(Tr, ip, x_phys); m_mapping.GetPhysicalPoint(Tr, ip, x_phys);
// ω x r // ω x r
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1); a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
@@ -162,4 +168,4 @@ namespace mean_field::integrators {
} }
} }
} }
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -5,10 +5,12 @@ module mean_field;
namespace mean_field::integrators { namespace mean_field::integrators {
CoriolisIntegrator::CoriolisIntegrator( CoriolisIntegrator::CoriolisIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega const mfem::Vector &omega
) )
: m_map(map), : m_mapping(mapper, displacement, compactification_coordinate),
m_omega(omega) { m_omega(omega) {
m_omega_mat.SetSize(3, 3); m_omega_mat.SetSize(3, 3);
m_omega_mat = 0.0; m_omega_mat = 0.0;
@@ -26,6 +28,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec const mfem::Array<mfem::Vector *> &elvec
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) { if (utils::is_vacuum(Tr, elvec)) {
return; return;
} }
@@ -55,7 +59,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v); fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho); fe_rho->CalcShape(ip, shape_rho);
@@ -89,6 +93,7 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats const mfem::Array2D<mfem::DenseMatrix *> &elmats
) { ) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0]; const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1]; const mfem::FiniteElement *fe_rho = el[1];
@@ -115,7 +120,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v); fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho); fe_rho->CalcShape(ip, shape_rho);
@@ -163,4 +168,4 @@ namespace mean_field::integrators {
} }
} }
} }
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -14,10 +14,12 @@ namespace {
namespace mean_field::integrators { namespace mean_field::integrators {
GravityMomentumIntegrator::GravityMomentumIntegrator( GravityMomentumIntegrator::GravityMomentumIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const GravityForceJacobianMode jacobian_mode const GravityForceJacobianMode jacobian_mode
) )
: m_map(map), : m_mapping(mapper, displacement, compactification_coordinate),
m_jacobian_mode(jacobian_mode) { m_jacobian_mode(jacobian_mode) {
} }
@@ -39,6 +41,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec const mfem::Array<mfem::Vector *> &elvec
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) { if (utils::is_vacuum(Tr, elvec)) {
return; return;
} }
@@ -123,7 +127,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q);
Tr.SetIntPoint(&integration_point); Tr.SetIntPoint(&integration_point);
const mapping::VolumeQuadratureContext context = m_map.GetQuadratureContext(Tr, integration_point); const mapping::VolumeQuadratureContext context = m_mapping.GetQuadratureContext(Tr, integration_point);
velocity_element->CalcShape(integration_point, velocity_shape); velocity_element->CalcShape(integration_point, velocity_shape);
density_element->CalcShape(integration_point, density_shape); density_element->CalcShape(integration_point, density_shape);
@@ -152,6 +156,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats const mfem::Array2D<mfem::DenseMatrix *> &elmats
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elmats)) { if (utils::is_vacuum(Tr, elmats)) {
return; return;
} }
@@ -189,8 +195,8 @@ namespace mean_field::integrators {
MFEM_ABORT( MFEM_ABORT(
"Exact GravityForceIntegrator geometry Jacobian is unavailable " "Exact GravityForceIntegrator geometry Jacobian is unavailable "
"until " "until "
"DomainMapper linearization is " "the stateless mapping variation is wired into this legacy "
"implemented." "integrator."
); );
} }
@@ -240,7 +246,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q);
Tr.SetIntPoint(&integration_point); Tr.SetIntPoint(&integration_point);
const mapping::VolumeQuadratureContext context = m_map.GetQuadratureContext(Tr, integration_point); const mapping::VolumeQuadratureContext context = m_mapping.GetQuadratureContext(Tr, integration_point);
velocity_element->CalcShape(integration_point, velocity_shape); velocity_element->CalcShape(integration_point, velocity_shape);
density_element->CalcShape(integration_point, density_shape); density_element->CalcShape(integration_point, density_shape);
@@ -286,4 +292,4 @@ namespace mean_field::integrators {
} }
} }
} }
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -4,7 +4,12 @@ module;
module mean_field; module mean_field;
namespace mean_field::integrators { namespace mean_field::integrators {
ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(const mapping::DomainMapper &map) : m_map(map) { }; ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(mapper, displacement, compactification_coordinate) { };
void ContinuityVolumeIntegrator::AssembleElementVector( void ContinuityVolumeIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el, const mfem::Array<const mfem::FiniteElement *> &el,
@@ -12,6 +17,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec const mfem::Array<mfem::Vector *> &elvec
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) { if (utils::is_vacuum(Tr, elvec)) {
return; return;
} }
@@ -46,7 +53,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v); fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho); fe_rho->CalcShape(ip, shape_rho);
@@ -82,6 +89,7 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats const mfem::Array2D<mfem::DenseMatrix *> &elmats
) { ) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0]; const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1]; const mfem::FiniteElement *fe_rho = el[1];
@@ -115,7 +123,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v); fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho); fe_rho->CalcShape(ip, shape_rho);
@@ -161,7 +169,12 @@ namespace mean_field::integrators {
} }
} }
ContinuityFaceIntegrator::ContinuityFaceIntegrator(const mapping::DomainMapper &map) : m_map(map) { ContinuityFaceIntegrator::ContinuityFaceIntegrator(
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(mapper, displacement, compactification_coordinate) {
} }
void ContinuityFaceIntegrator::AssembleFaceVector( void ContinuityFaceIntegrator::AssembleFaceVector(
@@ -171,6 +184,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvect const mfem::Array<mfem::Vector *> &elvect
) { ) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v_minus = el1[0]; const mfem::FiniteElement *fe_v_minus = el1[0];
const mfem::FiniteElement *fe_v_plus = el2[0]; const mfem::FiniteElement *fe_v_plus = el2[0];
@@ -195,9 +210,9 @@ namespace mean_field::integrators {
const int attr_minus = Tr.Elem1->Attribute; const int attr_minus = Tr.Elem1->Attribute;
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1; const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
constexpr int VACUUM_ATTR = 3; using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) ||
if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) { DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) {
return; // No flux contribution for vacuum faces return; // No flux contribution for vacuum faces
} }
@@ -228,7 +243,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint(); const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint();
const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint(); const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint();
auto [n_unit, ds, v_dot_n_scale] = m_map.GetFaceQuadratureContext(Tr, face_ip); auto [n_unit, ds, v_dot_n_scale] = m_mapping.GetFaceQuadratureContext(Tr, face_ip);
fe_v_minus->CalcShape(ip_minus, shape_v_minus); fe_v_minus->CalcShape(ip_minus, shape_v_minus);
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus); fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
@@ -281,6 +296,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats const mfem::Array2D<mfem::DenseMatrix *> &elmats
) { ) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v_minus = el1[0]; const mfem::FiniteElement *fe_v_minus = el1[0];
const mfem::FiniteElement *fe_v_plus = el2[0]; const mfem::FiniteElement *fe_v_plus = el2[0];
const mfem::FiniteElement *fe_rho_minus = el1[1]; const mfem::FiniteElement *fe_rho_minus = el1[1];
@@ -330,7 +347,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint(); const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint();
const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint(); const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint();
auto [n_unit, ds, v_dot_n_scale] = m_map.GetFaceQuadratureContext(Tr, face_ip); auto [n_unit, ds, v_dot_n_scale] = m_mapping.GetFaceQuadratureContext(Tr, face_ip);
fe_v_minus->CalcShape(ip_minus, shape_v_minus); fe_v_minus->CalcShape(ip_minus, shape_v_minus);
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus); fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
@@ -399,10 +416,11 @@ namespace mean_field::integrators {
} }
bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations &Tr) { bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations &Tr) {
constexpr int VACUUM_ATTR = 3;
const int attr_minus = Tr.Elem1->Attribute; const int attr_minus = Tr.Elem1->Attribute;
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1; const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) { using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) ||
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) {
return true; // No flux contribution for vacuum faces return true; // No flux contribution for vacuum faces
} }
if (Tr.Elem2 == nullptr) { if (Tr.Elem2 == nullptr) {

View File

@@ -4,11 +4,13 @@ module mean_field;
namespace mean_field::integrators { namespace mean_field::integrators {
ViscosityIntegrator::ViscosityIntegrator( ViscosityIntegrator::ViscosityIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const double mu, const double mu,
const int quad_boost const int quad_boost
) )
: m_map(map), : m_mapping(mapper, displacement, compactification_coordinate),
m_mu(mu), m_mu(mu),
m_quad_boost(quad_boost) { m_quad_boost(quad_boost) {
} }
@@ -23,6 +25,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec const mfem::Array<mfem::Vector *> &elvec
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) { if (utils::is_vacuum(Tr, elvec)) {
return; return;
} }
@@ -56,7 +60,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcDShape(ip, dshape_v_ref); fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys); mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
@@ -102,6 +106,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats const mfem::Array2D<mfem::DenseMatrix *> &elmats
) { ) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0]; const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1]; const mfem::FiniteElement *fe_rho = el[1];
@@ -130,7 +136,7 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcDShape(ip, dshape_v_ref); fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys); mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
@@ -164,4 +170,4 @@ namespace mean_field::integrators {
} }
} }
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -9,11 +9,13 @@ namespace mean_field::mapping {
/// MappedScalarCoefficient /// /// MappedScalarCoefficient ///
////////////////////////////// //////////////////////////////
MappedScalarCoefficient::MappedScalarCoefficient( MappedScalarCoefficient::MappedScalarCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
Coefficient &coeff, Coefficient &coeff,
const COORDINATE_SPACE coord_space const COORDINATE_SPACE coord_space
) )
: m_map(map), : m_mapping(mapper, displacement, compactification_coordinate),
m_coeff(coeff), m_coeff(coeff),
m_coord_space(coord_space) { }; m_coord_space(coord_space) { };
@@ -27,8 +29,12 @@ namespace mean_field::mapping {
switch (m_coord_space) { switch (m_coord_space) {
case COORDINATE_SPACE::PHYSICAL: { case COORDINATE_SPACE::PHYSICAL: {
f_val = eval_at_point(m_coeff, T, ip); f_val = eval_at_point(m_coeff, T, ip);
const double detJ = m_map.ComputeDetJ(T, ip); VolumeMappingContext context;
return f_val * fabs(detJ); MFEM_VERIFY(
m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid,
"Mapped scalar coefficient encountered an invalid mapping."
);
return f_val * std::abs(context.mapping.mapping_determinant);
} }
case COORDINATE_SPACE::REFERENCE: { case COORDINATE_SPACE::REFERENCE: {
f_val = m_coeff.Eval(T, ip); f_val = m_coeff.Eval(T, ip);
@@ -50,21 +56,25 @@ namespace mean_field::mapping {
////////////////////////////////// //////////////////////////////////
MappedDiffusionCoefficient::MappedDiffusionCoefficient( MappedDiffusionCoefficient::MappedDiffusionCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
mfem::Coefficient &sigma, mfem::Coefficient &sigma,
const int dim const int dim
) )
: MatrixCoefficient(dim), : MatrixCoefficient(dim),
m_map(map), m_mapping(mapper, displacement, compactification_coordinate),
m_scalar(&sigma), m_scalar(&sigma),
m_tensor(nullptr) { }; m_tensor(nullptr) { };
MappedDiffusionCoefficient::MappedDiffusionCoefficient( MappedDiffusionCoefficient::MappedDiffusionCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
MatrixCoefficient &sigma MatrixCoefficient &sigma
) )
: MatrixCoefficient(sigma.GetHeight()), : MatrixCoefficient(sigma.GetHeight()),
m_map(map), m_mapping(mapper, displacement, compactification_coordinate),
m_scalar(nullptr), m_scalar(nullptr),
m_tensor(&sigma) { }; m_tensor(&sigma) { };
@@ -76,10 +86,13 @@ namespace mean_field::mapping {
const int dim = height; const int dim = height;
T.SetIntPoint(&ip); T.SetIntPoint(&ip);
mfem::DenseMatrix J(dim, dim), JInv(dim, dim); VolumeMappingContext context;
m_map.ComputeJacobian(T, J); MFEM_VERIFY(
const double detJ = J.Det(); m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid,
mfem::CalcInverse(J, JInv); "Mapped diffusion coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian;
const double detJ = context.mapping.mapping_determinant;
if (m_scalar) { if (m_scalar) {
const double sig_val = m_scalar->Eval(T, ip); const double sig_val = m_scalar->Eval(T, ip);
@@ -101,11 +114,13 @@ namespace mean_field::mapping {
/// MappedVectorCoefficient /// /// MappedVectorCoefficient ///
/////////////////////////////// ///////////////////////////////
MappedVectorCoefficient::MappedVectorCoefficient( MappedVectorCoefficient::MappedVectorCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
VectorCoefficient &coeff VectorCoefficient &coeff
) )
: VectorCoefficient(coeff.GetVDim()), : VectorCoefficient(coeff.GetVDim()),
m_map(map), m_mapping(mapper, displacement, compactification_coordinate),
m_coeff(coeff) { }; m_coeff(coeff) { };
void MappedVectorCoefficient::Eval( void MappedVectorCoefficient::Eval(
@@ -116,9 +131,13 @@ namespace mean_field::mapping {
const int dim = vdim; const int dim = vdim;
T.SetIntPoint(&ip); T.SetIntPoint(&ip);
mfem::DenseMatrix JInv(dim, dim); VolumeMappingContext context;
m_map.ComputeInverseJacobian(T, JInv); MFEM_VERIFY(
double detJ = m_map.ComputeDetJ(T, ip); m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid,
"Mapped vector coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian;
const double detJ = context.mapping.mapping_determinant;
mfem::Vector C_phys(dim); mfem::Vector C_phys(dim);
m_coeff.Eval(C_phys, T, ip); m_coeff.Eval(C_phys, T, ip);
@@ -132,28 +151,35 @@ namespace mean_field::mapping {
/// PhysicalPositionFunctionCoefficient /// /// PhysicalPositionFunctionCoefficient ///
/////////////////////////////////////////// ///////////////////////////////////////////
PhysicalPositionFunctionCoefficient::PhysicalPositionFunctionCoefficient( PhysicalPositionFunctionCoefficient::PhysicalPositionFunctionCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
Func f // std::function<double(const mfem::Vector&)> Func f // std::function<double(const mfem::Vector&)>
) )
: m_f(std::move(f)), : m_f(std::move(f)),
m_map(map) { }; m_mapping(mapper, displacement, compactification_coordinate) { };
double PhysicalPositionFunctionCoefficient::Eval( double PhysicalPositionFunctionCoefficient::Eval(
mfem::ElementTransformation &T, mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip const mfem::IntegrationPoint &ip
) { ) {
T.SetIntPoint(&ip); T.SetIntPoint(&ip);
mfem::Vector x; MappingPointContext context;
m_map.GetPhysicalPoint(T, ip, x); MFEM_VERIFY(
return m_f(x); m_mapping.EvaluatePoint(T, ip, context) == MappingStatus::valid,
"Physical-position coefficient encountered an invalid mapping."
);
return m_f(context.physical_position);
} }
MappedHDivMassCoefficient::MappedHDivMassCoefficient( MappedHDivMassCoefficient::MappedHDivMassCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const int dim const int dim
) )
: MatrixCoefficient(dim), : MatrixCoefficient(dim),
m_map(map) { m_mapping(mapper, displacement, compactification_coordinate) {
} }
void MappedHDivMassCoefficient::Eval( void MappedHDivMassCoefficient::Eval(
@@ -163,10 +189,13 @@ namespace mean_field::mapping {
) { ) {
transformation.SetIntPoint(&integration_point); transformation.SetIntPoint(&integration_point);
mfem::DenseMatrix map_jacobian(height, height); VolumeMappingContext context;
m_map.ComputeJacobian(transformation, map_jacobian); MFEM_VERIFY(
m_mapping.EvaluateVolume(transformation, integration_point, context) == MappingStatus::valid,
const double map_determinant = map_jacobian.Det(); "Mapped H(div) coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &map_jacobian = context.mapping.mapping_jacobian;
const double map_determinant = context.mapping.mapping_determinant;
MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant."); MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant.");

File diff suppressed because it is too large Load Diff

View File

@@ -1,770 +0,0 @@
module;
#include <cmath>
#include <memory>
#include <mfem.hpp>
#include <stdexcept>
#include <utility>
module mean_field;
import :mapping.types;
import :mapping.compactification;
import :utils.user;
namespace {
bool vector_is_finite(const mfem::Vector &vector) {
for (int i = 0; i < vector.Size(); ++i) {
if (!std::isfinite(vector(i)))
return false;
}
return true;
}
bool matrix_is_finite(const mfem::DenseMatrix &matrix) {
for (int i = 0; i < matrix.Height(); ++i) {
for (int j = 0; j < matrix.Width(); ++j) {
if (!std::isfinite(matrix(i, j)))
return false;
}
}
return true;
}
} // namespace
namespace mean_field::mapping {
ElementCompactificationData::ElementCompactificationData(
const mfem::FiniteElement &element,
const mfem::Vector &dofs
)
: m_element(&element),
m_dofs(dofs) {
if (element.GetRangeType() != mfem::FiniteElement::SCALAR) {
throw std::invalid_argument("Compactification coordinate requires a scalar finite element.");
}
if (element.GetMapType() != mfem::FiniteElement::VALUE) {
throw std::invalid_argument(
"Compactification coordinate requires a value-mapped scalar "
"finite "
"element."
);
}
if (element.GetDerivType() != mfem::FiniteElement::GRAD) {
throw std::invalid_argument(
"Compactification coordinate finite element must provide a "
"gradient."
);
}
if (element.GetDof() <= 0) {
throw std::invalid_argument(
"Compactification coordinate finite element has no degrees of "
"freedom."
);
}
if (dofs.Size() != element.GetDof()) {
throw std::invalid_argument(
"Compactification coordinate DOF count does not match its "
"finite "
"element."
);
}
}
const mfem::FiniteElement &ElementCompactificationData::GetElement() const noexcept {
return *m_element;
}
const mfem::Vector &ElementCompactificationData::GetDofs() const noexcept {
return m_dofs;
}
int ElementCompactificationData::GetDofCount() const noexcept {
return m_dofs.Size();
}
ElementDisplacementData::ElementDisplacementData(
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs,
const mfem::Ordering::Type ordering
)
: m_element(&element),
m_dimension(0),
m_ordering(ordering) {
const int dof_count = element.GetDof();
if (dof_count <= 0)
throw std::invalid_argument(
"The displacement element must have at least one degree of "
"freedom."
);
if (displacement_dofs.Size() <= 0 || displacement_dofs.Size() % dof_count != 0) {
throw std::invalid_argument(
"The displacement vector size must be a positive multiple of "
"the "
"element degree-of-freedom count."
);
}
m_dimension = displacement_dofs.Size() / dof_count;
m_dof_matrix.SetSize(dof_count, m_dimension);
if (ordering == mfem::Ordering::byNODES) {
for (int component = 0; component < m_dimension; ++component) {
for (int i = 0; i < dof_count; ++i) {
m_dof_matrix(i, component) = displacement_dofs(i + component * dof_count);
}
}
} else if (ordering == mfem::Ordering::byVDIM) {
for (int i = 0; i < dof_count; ++i) {
for (int component = 0; component < m_dimension; ++component) {
m_dof_matrix(i, component) = displacement_dofs(component + i * m_dimension);
}
}
} else {
throw std::invalid_argument("Unsupported MFEM displacement ordering.");
}
}
const mfem::FiniteElement &ElementDisplacementData::GetElement() const noexcept {
return *m_element;
}
const mfem::DenseMatrix &ElementDisplacementData::GetDofMatrix() const noexcept {
return m_dof_matrix;
}
int ElementDisplacementData::GetDimension() const noexcept {
return m_dimension;
}
int ElementDisplacementData::GetDofCount() const noexcept {
return m_element->GetDof();
}
mfem::Ordering::Type ElementDisplacementData::GetOrdering() const noexcept {
return m_ordering;
}
ElementDisplacementData ElementDisplacementDataFromElementVDofs(
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs
) {
return ElementDisplacementData(element, displacement_dofs, mfem::Ordering::byNODES);
}
DomainMapperStateless::Workspace::Workspace(const int dimension) {
SetDimension(dimension);
}
void DomainMapperStateless::Workspace::SetDimension(const int dimension) {
if (dimension <= 0) {
throw std::invalid_argument("Domain mapping workspace dimension must be positive.");
}
m_dimension = dimension;
m_field_value.SetSize(dimension);
m_field_jacobian.SetSize(dimension, dimension);
m_compactification_point.coordinate = 0.0;
m_compactification_point.coordinate_gradient.SetSize(dimension);
m_reference_normal.SetSize(dimension);
m_mapped_normal.SetSize(dimension);
m_full_element_jacobian.SetSize(dimension, dimension);
m_vector_temp.SetSize(dimension);
m_matrix_temp_1.SetSize(dimension, dimension);
m_matrix_temp_2.SetSize(dimension, dimension);
m_exterior_result.physical_position.SetSize(dimension);
m_exterior_result.mapping_jacobian.SetSize(dimension, dimension);
m_exterior_variation.physical_position_variation.SetSize(dimension);
m_exterior_variation.mapping_jacobian_variation.SetSize(dimension, dimension);
}
int DomainMapperStateless::Workspace::GetDimension() const noexcept {
return m_dimension;
}
DomainMapperStateless::DomainMapperStateless(
const utils::DomainMapperStatelessOptions options,
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map
)
: m_options(options),
m_exterior_map(std::move(exterior_map)) {
if (m_options.dimension <= 0)
throw std::invalid_argument("The domain-mapping dimension must be positive.");
if (m_options.vacuum_element_attribute <= 0)
throw std::invalid_argument("The vacuum element attribute must be positive.");
if (!m_exterior_map)
throw std::invalid_argument("DomainMapperStateless requires an exterior-domain mapping.");
}
bool
DomainMapperStateless::IsCompactifiedElement(const mfem::ElementTransformation &transformation) const noexcept {
return transformation.Attribute == m_options.vacuum_element_attribute;
}
int DomainMapperStateless::GetDimension() const noexcept {
return m_options.dimension;
}
int DomainMapperStateless::GetVacuumElementAttribute() const noexcept {
return m_options.vacuum_element_attribute;
}
const compactification::ExteriorDomainMap &DomainMapperStateless::GetExteriorMap() const noexcept {
return *m_exterior_map;
}
void DomainMapperStateless::ValidateElementData(const ElementMappingData &element_data) const {
const ElementDisplacementData &displacement = element_data.displacement;
const ElementCompactificationData &compactification = element_data.compactification;
if (displacement.GetDimension() != m_options.dimension) {
throw std::invalid_argument(
"Displacement field dimension does not match the domain mapper "
"dimension."
);
}
if (displacement.GetElement().GetDim() != m_options.dimension) {
throw std::invalid_argument(
"Displacement finite element dimension does not match the "
"domain "
"mapper dimension."
);
}
if (compactification.GetElement().GetDim() != m_options.dimension) {
throw std::invalid_argument(
"Compactification finite element dimension does not match the "
"domain "
"mapper dimension."
);
}
if (displacement.GetElement().GetGeomType() != compactification.GetElement().GetGeomType()) {
throw std::invalid_argument(
"Displacement and compactification finite elements have "
"different "
"geometries."
);
}
if (compactification.GetElement().GetRangeType() != mfem::FiniteElement::SCALAR) {
throw std::invalid_argument("Compactification coordinate requires a scalar finite element.");
}
if (compactification.GetElement().GetMapType() != mfem::FiniteElement::VALUE) {
throw std::invalid_argument(
"Compactification coordinate requires a value-mapped finite "
"element."
);
}
if (compactification.GetElement().GetDerivType() != mfem::FiniteElement::GRAD) {
throw std::invalid_argument(
"Compactification coordinate finite element does not provide a "
"gradient."
);
}
if (compactification.GetDofCount() != compactification.GetElement().GetDof()) {
throw std::invalid_argument(
"Compactification coordinate DOF count does not match its "
"finite "
"element."
);
}
}
MappingStatus DomainMapperStateless::EvaluateCompactificationCoordinate(
const ElementCompactificationData &compactification,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
CompactificationPointData &point_data
) const {
const mfem::FiniteElement &element = compactification.GetElement();
const mfem::Vector &dofs = compactification.GetDofs();
const int dof_count = element.GetDof();
if (workspace.GetDimension() != m_options.dimension || transformation.GetSpaceDim() != m_options.dimension ||
element.GetDim() != m_options.dimension) {
return MappingStatus::invalid_dimension;
}
if (dofs.Size() != dof_count) {
return MappingStatus::invalid_dimension;
}
for (int i = 0; i < dofs.Size(); ++i) {
if (!std::isfinite(dofs(i)))
return MappingStatus::non_finite_input;
}
transformation.SetIntPoint(&integration_point);
workspace.m_compactification_shape.SetSize(dof_count);
workspace.m_compactification_dshape.SetSize(dof_count, m_options.dimension);
element.CalcShape(integration_point, workspace.m_compactification_shape);
element.CalcPhysDShape(transformation, workspace.m_compactification_dshape);
point_data.coordinate = dofs * workspace.m_compactification_shape;
point_data.coordinate_gradient.SetSize(m_options.dimension);
workspace.m_compactification_dshape.MultTranspose(dofs, point_data.coordinate_gradient);
if (!std::isfinite(point_data.coordinate)) {
return MappingStatus::non_finite_result;
}
for (int d = 0; d < point_data.coordinate_gradient.Size(); ++d) {
if (!std::isfinite(point_data.coordinate_gradient(d)))
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
void DomainMapperStateless::EvaluateField(
const ElementDisplacementData &field,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
mfem::Vector &value,
mfem::DenseMatrix &jacobian
) const {
transformation.SetIntPoint(&integration_point);
const mfem::FiniteElement &element = field.GetElement();
const mfem::DenseMatrix &dof_matrix = field.GetDofMatrix();
workspace.m_shape.SetSize(element.GetDof());
workspace.m_mesh_dshape.SetSize(element.GetDof(), m_options.dimension);
element.CalcShape(integration_point, workspace.m_shape);
element.CalcPhysDShape(transformation, workspace.m_mesh_dshape);
value.SetSize(m_options.dimension);
dof_matrix.MultTranspose(workspace.m_shape, value);
jacobian.SetSize(m_options.dimension, m_options.dimension);
mfem::MultAtB(dof_matrix, workspace.m_mesh_dshape, jacobian);
}
MappingStatus DomainMapperStateless::EvaluatePoint(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
MappingPointContext &context
) const {
ValidateElementData(element_data);
if (workspace.GetDimension() != m_options.dimension)
throw std::invalid_argument("The mapping workspace has the wrong dimension.");
if (transformation.GetSpaceDim() != m_options.dimension)
throw std::invalid_argument("The element transformation has the wrong spatial dimension.");
if (transformation.GetGeometryType() != element_data.displacement.GetElement().GetGeomType())
throw std::invalid_argument(
"The element transformation geometry does not match the "
"supplied "
"element data."
);
transformation.SetIntPoint(&integration_point);
context.reference_position.SetSize(m_options.dimension);
transformation.Transform(integration_point, context.reference_position);
EvaluateField(
element_data.displacement, transformation, integration_point, workspace, workspace.m_field_value,
workspace.m_field_jacobian
);
if (!vector_is_finite(context.reference_position) || !vector_is_finite(workspace.m_field_value) ||
!matrix_is_finite(workspace.m_field_jacobian)) {
return MappingStatus::non_finite_input;
}
context.displaced_position.SetSize(m_options.dimension);
context.displaced_position = context.reference_position;
context.displaced_position += workspace.m_field_value;
context.displacement_jacobian.SetSize(m_options.dimension, m_options.dimension);
context.displacement_jacobian = workspace.m_field_jacobian;
for (int i = 0; i < m_options.dimension; ++i)
context.displacement_jacobian(i, i) += 1.0;
context.compactified = IsCompactifiedElement(transformation);
if (context.compactified) {
const MappingStatus coordinate_status = EvaluateCompactificationCoordinate(
element_data.compactification, transformation, integration_point, workspace,
workspace.m_compactification_point
);
if (coordinate_status != MappingStatus::valid)
return coordinate_status;
const compactification::ExteriorMapInput exterior_input{
.reference_position = context.reference_position,
.displaced_position = context.displaced_position,
.displacement_jacobian = context.displacement_jacobian,
.compactification_coordinate = workspace.m_compactification_point.coordinate,
.compactification_coordinate_gradient = workspace.m_compactification_point.coordinate_gradient
};
const MappingStatus exterior_status = m_exterior_map->Evaluate(exterior_input, workspace.m_exterior_result);
if (exterior_status != MappingStatus::valid)
return exterior_status;
context.physical_position = workspace.m_exterior_result.physical_position;
context.mapping_jacobian = workspace.m_exterior_result.mapping_jacobian;
} else {
context.physical_position = context.displaced_position;
context.mapping_jacobian = context.displacement_jacobian;
}
if (!vector_is_finite(context.physical_position) || !matrix_is_finite(context.mapping_jacobian))
return MappingStatus::non_finite_result;
context.mapping_determinant = context.mapping_jacobian.Det();
if (!std::isfinite(context.mapping_determinant))
return MappingStatus::non_finite_result;
if (context.mapping_determinant <= 0.0)
return MappingStatus::non_positive_determinant;
context.inverse_mapping_jacobian.SetSize(m_options.dimension, m_options.dimension);
mfem::CalcInverse(context.mapping_jacobian, context.inverse_mapping_jacobian);
if (!matrix_is_finite(context.inverse_mapping_jacobian))
return MappingStatus::non_finite_result;
return MappingStatus::valid;
}
MappingStatus DomainMapperStateless::EvaluateVolume(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
VolumeMappingContext &context
) const {
const MappingStatus point_status =
EvaluatePoint(element_data, transformation, integration_point, workspace, context.mapping);
if (point_status != MappingStatus::valid)
return point_status;
transformation.SetIntPoint(&integration_point);
mfem::Mult(context.mapping.mapping_jacobian, transformation.Jacobian(), workspace.m_full_element_jacobian);
context.quadrature.J_inv.SetSize(m_options.dimension, m_options.dimension);
mfem::CalcInverse(workspace.m_full_element_jacobian, context.quadrature.J_inv);
context.quadrature.detJ = context.mapping.mapping_determinant;
context.quadrature.weight =
integration_point.weight * transformation.Weight() * context.mapping.mapping_determinant;
if (!matrix_is_finite(context.quadrature.J_inv) || !std::isfinite(context.quadrature.weight))
return MappingStatus::non_finite_result;
if (context.quadrature.weight <= 0.0)
return MappingStatus::non_positive_determinant;
return MappingStatus::valid;
}
mfem::ElementTransformation &DomainMapperStateless::SelectFaceElementTransformation(
mfem::FaceElementTransformations &transformation,
const FaceElementSide side
) {
if (side == FaceElementSide::element_1) {
MFEM_VERIFY(transformation.Elem1 != nullptr, "The face does not have an element-1 transformation.");
return *transformation.Elem1;
}
MFEM_VERIFY(transformation.Elem2 != nullptr, "The face does not have an element-2 transformation.");
return *transformation.Elem2;
}
const mfem::IntegrationPoint &DomainMapperStateless::SelectFaceElementIntegrationPoint(
mfem::FaceElementTransformations &transformation,
const FaceElementSide side
) {
mfem::ElementTransformation &element_transformation = SelectFaceElementTransformation(transformation, side);
return element_transformation.GetIntPoint();
}
MappingStatus DomainMapperStateless::EvaluateFace(
const ElementMappingData &element_data,
mfem::FaceElementTransformations &transformation,
const FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
FaceMappingContext &context
) const {
transformation.SetAllIntPoints(&integration_point);
mfem::ElementTransformation &element_transformation = SelectFaceElementTransformation(transformation, side);
const mfem::IntegrationPoint &element_integration_point =
SelectFaceElementIntegrationPoint(transformation, side);
const MappingStatus point_status =
EvaluatePoint(element_data, element_transformation, element_integration_point, workspace, context.mapping);
if (point_status != MappingStatus::valid)
return point_status;
workspace.m_reference_normal.SetSize(m_options.dimension);
mfem::CalcOrtho(transformation.Jacobian(), workspace.m_reference_normal);
if (side == FaceElementSide::element_2)
workspace.m_reference_normal *= -1.0;
const double reference_normal_magnitude = workspace.m_reference_normal.Norml2();
if (!std::isfinite(reference_normal_magnitude) || reference_normal_magnitude <= 0.0)
return MappingStatus::non_finite_result;
context.reference_normal.SetSize(m_options.dimension);
context.reference_normal = workspace.m_reference_normal;
context.reference_normal /= reference_normal_magnitude;
context.mapping.inverse_mapping_jacobian.MultTranspose(workspace.m_reference_normal, workspace.m_mapped_normal);
workspace.m_mapped_normal *= context.mapping.mapping_determinant;
const double mapped_normal_magnitude = workspace.m_mapped_normal.Norml2();
if (!std::isfinite(mapped_normal_magnitude) || mapped_normal_magnitude <= 0.0)
return MappingStatus::non_finite_result;
context.quadrature.normal.SetSize(m_options.dimension);
context.quadrature.normal = workspace.m_mapped_normal;
context.quadrature.normal /= mapped_normal_magnitude;
context.reference_surface_weight = integration_point.weight * reference_normal_magnitude;
context.physical_surface_weight = integration_point.weight * mapped_normal_magnitude;
context.quadrature.ds = context.reference_surface_weight;
context.quadrature.v_dot_n_scale = mapped_normal_magnitude / reference_normal_magnitude;
if (!vector_is_finite(context.quadrature.normal) || !std::isfinite(context.reference_surface_weight) ||
!std::isfinite(context.physical_surface_weight) || !std::isfinite(context.quadrature.v_dot_n_scale)) {
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
MappingStatus DomainMapperStateless::EvaluatePointVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const MappingPointContext &base_context,
Workspace &workspace,
MappingPointVariation &variation
) const {
ValidateElementData(element_data);
const ElementMappingData direction_data{
.displacement = direction, .compactification = element_data.compactification
};
ValidateElementData(direction_data);
if (element_data.displacement.GetDofCount() != direction.GetDofCount())
throw std::invalid_argument(
"The displacement and direction elements have different "
"degree-of-freedom counts."
);
if (workspace.GetDimension() != m_options.dimension)
throw std::invalid_argument("The mapping workspace has the wrong dimension.");
if (base_context.compactified != IsCompactifiedElement(transformation))
throw std::invalid_argument(
"The base mapping context does not match the current element "
"domain."
);
EvaluateField(
direction, transformation, integration_point, workspace, workspace.m_field_value, workspace.m_field_jacobian
);
if (!vector_is_finite(workspace.m_field_value) || !matrix_is_finite(workspace.m_field_jacobian))
return MappingStatus::non_finite_input;
variation.displacement_variation = workspace.m_field_value;
variation.displacement_jacobian_variation = workspace.m_field_jacobian;
if (base_context.compactified) {
const MappingStatus coordinate_status = EvaluateCompactificationCoordinate(
element_data.compactification, transformation, integration_point, workspace,
workspace.m_compactification_point
);
if (coordinate_status != MappingStatus::valid)
return coordinate_status;
const compactification::ExteriorMapInput exterior_input{
.reference_position = base_context.reference_position,
.displaced_position = base_context.displaced_position,
.displacement_jacobian = base_context.displacement_jacobian,
.compactification_coordinate = workspace.m_compactification_point.coordinate,
.compactification_coordinate_gradient = workspace.m_compactification_point.coordinate_gradient
};
workspace.m_exterior_result.physical_position = base_context.physical_position;
workspace.m_exterior_result.mapping_jacobian = base_context.mapping_jacobian;
const compactification::ExteriorMapDirection exterior_direction{
.displaced_position_variation = variation.displacement_variation,
.displacement_jacobian_variation = variation.displacement_jacobian_variation
};
// ReSharper disable once CppTooWideScopeInitStatement
const MappingStatus exterior_status = m_exterior_map->EvaluateVariation(
exterior_input, workspace.m_exterior_result, exterior_direction, workspace.m_exterior_variation
);
if (exterior_status != MappingStatus::valid) {
return exterior_status;
}
variation.physical_position_variation = workspace.m_exterior_variation.physical_position_variation;
variation.mapping_jacobian_variation = workspace.m_exterior_variation.mapping_jacobian_variation;
} else {
variation.physical_position_variation = variation.displacement_variation;
variation.mapping_jacobian_variation = variation.displacement_jacobian_variation;
}
mfem::Mult(
base_context.inverse_mapping_jacobian, variation.mapping_jacobian_variation, workspace.m_matrix_temp_1
);
double trace = 0.0;
for (int i = 0; i < m_options.dimension; ++i)
trace += workspace.m_matrix_temp_1(i, i);
variation.mapping_determinant_variation = base_context.mapping_determinant * trace;
variation.inverse_mapping_jacobian_variation.SetSize(m_options.dimension, m_options.dimension);
mfem::Mult(
workspace.m_matrix_temp_1, base_context.inverse_mapping_jacobian,
variation.inverse_mapping_jacobian_variation
);
variation.inverse_mapping_jacobian_variation *= -1.0;
if (!vector_is_finite(variation.physical_position_variation) ||
!matrix_is_finite(variation.mapping_jacobian_variation) ||
!matrix_is_finite(variation.inverse_mapping_jacobian_variation) ||
!std::isfinite(variation.mapping_determinant_variation)) {
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
MappingStatus DomainMapperStateless::EvaluateVolumeVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const VolumeMappingContext &base_context,
Workspace &workspace,
VolumeMappingVariation &variation
) const {
const MappingStatus point_status = EvaluatePointVariation(
element_data, direction, transformation, integration_point, base_context.mapping, workspace,
variation.mapping
);
if (point_status != MappingStatus::valid)
return point_status;
transformation.SetIntPoint(&integration_point);
mfem::Mult(
variation.mapping.mapping_jacobian_variation, transformation.Jacobian(), workspace.m_full_element_jacobian
);
mfem::Mult(base_context.quadrature.J_inv, workspace.m_full_element_jacobian, workspace.m_matrix_temp_1);
variation.inverse_element_jacobian_variation.SetSize(m_options.dimension, m_options.dimension);
mfem::Mult(
workspace.m_matrix_temp_1, base_context.quadrature.J_inv, variation.inverse_element_jacobian_variation
);
variation.inverse_element_jacobian_variation *= -1.0;
variation.weight_variation =
integration_point.weight * transformation.Weight() * variation.mapping.mapping_determinant_variation;
if (!matrix_is_finite(variation.inverse_element_jacobian_variation) ||
!std::isfinite(variation.weight_variation))
return MappingStatus::non_finite_result;
return MappingStatus::valid;
}
MappingStatus DomainMapperStateless::EvaluateFaceVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::FaceElementTransformations &transformation,
const FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
const FaceMappingContext &base_context,
Workspace &workspace,
FaceMappingVariation &variation
) const {
transformation.SetAllIntPoints(&integration_point);
mfem::ElementTransformation &element_transformation = SelectFaceElementTransformation(transformation, side);
const mfem::IntegrationPoint &element_integration_point =
SelectFaceElementIntegrationPoint(transformation, side);
const MappingStatus point_status = EvaluatePointVariation(
element_data, direction, element_transformation, element_integration_point, base_context.mapping, workspace,
variation.mapping
);
if (point_status != MappingStatus::valid)
return point_status;
workspace.m_reference_normal.SetSize(m_options.dimension);
mfem::CalcOrtho(transformation.Jacobian(), workspace.m_reference_normal);
if (side == FaceElementSide::element_2)
workspace.m_reference_normal *= -1.0;
const double reference_normal_magnitude = workspace.m_reference_normal.Norml2();
if (!std::isfinite(reference_normal_magnitude) || reference_normal_magnitude <= 0.0)
return MappingStatus::non_finite_result;
base_context.mapping.inverse_mapping_jacobian.MultTranspose(
workspace.m_reference_normal, workspace.m_vector_temp
);
workspace.m_mapped_normal = workspace.m_vector_temp;
workspace.m_mapped_normal *= base_context.mapping.mapping_determinant;
variation.physical_normal_variation.SetSize(m_options.dimension);
variation.mapping.inverse_mapping_jacobian_variation.MultTranspose(
workspace.m_reference_normal, variation.physical_normal_variation
);
variation.physical_normal_variation *= base_context.mapping.mapping_determinant;
variation.physical_normal_variation.Add(
variation.mapping.mapping_determinant_variation, workspace.m_vector_temp
);
const double mapped_normal_magnitude = workspace.m_mapped_normal.Norml2();
if (!std::isfinite(mapped_normal_magnitude) || mapped_normal_magnitude <= 0.0)
return MappingStatus::non_finite_result;
const double mapped_normal_magnitude_variation =
base_context.quadrature.normal * variation.physical_normal_variation;
variation.physical_normal_variation.Add(-mapped_normal_magnitude_variation, base_context.quadrature.normal);
variation.physical_normal_variation /= mapped_normal_magnitude;
variation.physical_surface_weight_variation = integration_point.weight * mapped_normal_magnitude_variation;
variation.normal_flux_scale_variation = mapped_normal_magnitude_variation / reference_normal_magnitude;
if (!vector_is_finite(variation.physical_normal_variation) ||
!std::isfinite(variation.physical_surface_weight_variation) ||
!std::isfinite(variation.normal_flux_scale_variation)) {
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
} // namespace mean_field::mapping

View File

@@ -35,7 +35,7 @@ namespace {
namespace mean_field::operators::context::barotropic { namespace mean_field::operators::context::barotropic {
BarotropicClosureLinearizationContext::BarotropicClosureLinearizationContext( BarotropicClosureLinearizationContext::BarotropicClosureLinearizationContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const field::FieldDofMap &densityMap, const field::FieldDofMap &densityMap,
const field::FieldDofMap &enthalpyMap, const field::FieldDofMap &enthalpyMap,
const field::FieldDofMap &displacementMap const field::FieldDofMap &displacementMap

View File

@@ -9,6 +9,29 @@ import :operators.context.gravity_field;
namespace { namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] std::unique_ptr<mfem::ParMixedBilinearForm> make_divergence_operator(const mean_field::fem::FEM &f) {
auto divergence =
std::make_unique<mfem::ParMixedBilinearForm>(f.gravityFluxFes.get(), f.gravityPotentialFes.get());
divergence->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement &trialElement = *f.gravityFluxFes->GetTypicalFE();
const mfem::FiniteElement &testElement = *f.gravityPotentialFes->GetTypicalFE();
const mfem::ElementTransformation &transformation = *f.mesh->GetElementTransformation(0);
f.quadratureFactory->configure_gravity_divergence(
*integrator, mean_field::quadrature::QuadratureRole::discretization, trialElement, testElement,
transformation, mean_field::utils::DOMAINS::ALL, mean_field::quadrature::MappingKind::none
);
divergence->AddDomainIntegrator(integrator.release());
divergence->Assemble();
return divergence;
}
void validate_displacement( void validate_displacement(
const mean_field::field::FieldDofMap &displacement_map, const mean_field::field::FieldDofMap &displacement_map,
const mfem::Vector &displacement const mfem::Vector &displacement
@@ -96,7 +119,7 @@ namespace {
namespace mean_field::operators::context::gravity_field { namespace mean_field::operators::context::gravity_field {
GravityFieldGeometryContext::GravityFieldGeometryContext( GravityFieldGeometryContext::GravityFieldGeometryContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper const mapping::DomainMapper &domain_mapper
) )
: m_fem(f), : m_fem(f),
m_domain_mapper(domain_mapper), m_domain_mapper(domain_mapper),
@@ -170,18 +193,23 @@ namespace mean_field::operators::context::gravity_field {
} }
if (discretization_changed) { if (discretization_changed) {
auto mass_operator = std::make_unique<PreparedMappedHDivMassOperator>(m_fem, m_domain_mapper); auto mass_operator = std::make_unique<PreparedMappedHDivMassOperator>(m_fem, m_domain_mapper);
auto source_operator = std::make_unique<PreparedMappedGravitySourceOperator>(m_fem, m_domain_mapper); auto source_operator = std::make_unique<PreparedMappedGravitySourceOperator>(m_fem, m_domain_mapper);
auto divergence_operator = make_divergence_operator(m_fem);
auto transpose_divergence_operator = std::make_unique<mfem::TransposeOperator>(divergence_operator.get());
mass_operator->Prepare(displacement); mass_operator->Prepare(displacement);
source_operator->Prepare(displacement); source_operator->Prepare(displacement);
m_mass_operator = std::move(mass_operator); m_mass_operator = std::move(mass_operator);
m_source_operator = std::move(source_operator); m_source_operator = std::move(source_operator);
m_divergence_operator = std::move(divergence_operator);
m_transpose_divergence_operator = std::move(transpose_divergence_operator);
preparation.reconstructed_operators = true; preparation.reconstructed_operators = true;
preparation.rebuilt_mass_operator = true; preparation.rebuilt_mass_operator = true;
preparation.rebuilt_source_operator = true; preparation.rebuilt_source_operator = true;
preparation.rebuilt_divergence_operator = true;
} else { } else {
MFEM_VERIFY( MFEM_VERIFY(
m_mass_operator != nullptr, "GravityFieldGeometryContext has " m_mass_operator != nullptr, "GravityFieldGeometryContext has "
@@ -231,6 +259,23 @@ namespace mean_field::operators::context::gravity_field {
return *m_source_operator; return *m_source_operator;
} }
const mfem::Operator &GravityFieldGeometryContext::GetDivergenceOperator() const {
MFEM_VERIFY(m_is_prepared, "GravityFieldGeometryContext must be prepared before accessing divergence.");
MFEM_VERIFY(m_divergence_operator != nullptr, "GravityFieldGeometryContext has no divergence operator.");
return *m_divergence_operator;
}
const mfem::Operator &GravityFieldGeometryContext::GetTransposeDivergenceOperator() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldGeometryContext must be prepared before accessing transpose divergence."
);
MFEM_VERIFY(
m_transpose_divergence_operator != nullptr,
"GravityFieldGeometryContext has no transpose-divergence operator."
);
return *m_transpose_divergence_operator;
}
const mfem::Vector &GravityFieldGeometryContext::GetDisplacementTrue() const { const mfem::Vector &GravityFieldGeometryContext::GetDisplacementTrue() const {
MFEM_VERIFY( MFEM_VERIFY(
m_is_prepared, "GravityFieldGeometryContext must be prepared before " m_is_prepared, "GravityFieldGeometryContext must be prepared before "
@@ -257,7 +302,7 @@ namespace mean_field::operators::context::gravity_field {
GravityFieldLinearizationContext::GravityFieldLinearizationContext( GravityFieldLinearizationContext::GravityFieldLinearizationContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper const mapping::DomainMapper &domain_mapper
) )
: m_fem(f), : m_fem(f),
m_geometry_context( m_geometry_context(

View File

@@ -75,7 +75,7 @@ namespace {
namespace mean_field::operators::context::hydrostatic { namespace mean_field::operators::context::hydrostatic {
HydrostaticEquilibriumContext::HydrostaticEquilibriumContext( HydrostaticEquilibriumContext::HydrostaticEquilibriumContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper const mapping::DomainMapper &domainMapper
) )
: m_f(f), : m_f(f),
m_domainMapper(domainMapper), m_domainMapper(domainMapper),

View File

@@ -37,7 +37,7 @@ namespace {
namespace mean_field::operators::context::pressure_force { namespace mean_field::operators::context::pressure_force {
PressureForceLinearizationContext::PressureForceLinearizationContext( PressureForceLinearizationContext::PressureForceLinearizationContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const field::FieldDofMap &enthalpyMap, const field::FieldDofMap &enthalpyMap,
const field::FieldDofMap &displacementMap const field::FieldDofMap &displacementMap
) )

View File

@@ -33,7 +33,7 @@ namespace {
namespace mean_field::operators::context::rotational_displacement_force { namespace mean_field::operators::context::rotational_displacement_force {
RotationalDisplacementForceLinearizationContext::RotationalDisplacementForceLinearizationContext( RotationalDisplacementForceLinearizationContext::RotationalDisplacementForceLinearizationContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper const mapping::DomainMapper &domainMapper
) )
: m_f(f), : m_f(f),
m_densityMap( m_densityMap(

File diff suppressed because it is too large Load Diff

View File

@@ -129,7 +129,7 @@ namespace {
namespace mean_field::operators { namespace mean_field::operators {
GravityFieldJacobianOperator::GravityFieldJacobianOperator( GravityFieldJacobianOperator::GravityFieldJacobianOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper, const mapping::DomainMapper &domain_mapper,
const context::gravity_field::GravityFieldLinearizationContext &linearization_context, const context::gravity_field::GravityFieldLinearizationContext &linearization_context,
const mfem::Array<int> &state_offsets, const mfem::Array<int> &state_offsets,
const mfem::Array<int> &residual_offsets const mfem::Array<int> &residual_offsets
@@ -159,13 +159,6 @@ namespace mean_field::operators {
f.displacementFes != nullptr, "GravityFieldJacobianOperator requires the " f.displacementFes != nullptr, "GravityFieldJacobianOperator requires the "
"displacement finite-element space." "displacement finite-element space."
); );
MFEM_VERIFY(
f.gravityContext.b_form != nullptr, "GravityFieldJacobianOperator requires the divergence operator."
);
MFEM_VERIFY(
f.gravityContext.BT != nullptr, "GravityFieldJacobianOperator requires the transpose divergence "
"operator."
);
MFEM_VERIFY( MFEM_VERIFY(
f.quadratureFactory != nullptr, "GravityFieldJacobianOperator requires the quadrature-rule factory." f.quadratureFactory != nullptr, "GravityFieldJacobianOperator requires the quadrature-rule factory."
); );
@@ -263,14 +256,16 @@ namespace mean_field::operators {
potential_map.gather(source_variation_action_true, source_variation_action); potential_map.gather(source_variation_action_true, source_variation_action);
transpose_divergence_action_true.SetSize(flux_map.full_size()); transpose_divergence_action_true.SetSize(flux_map.full_size());
m_fem.gravityContext.BT->Mult(gravity_potential_direction_true, transpose_divergence_action_true); geometry_context.GetTransposeDivergenceOperator().Mult(
gravity_potential_direction_true, transpose_divergence_action_true
);
flux_map.gather(transpose_divergence_action_true, transpose_divergence_action); flux_map.gather(transpose_divergence_action_true, transpose_divergence_action);
gravity_gradient_action += transpose_divergence_action; gravity_gradient_action += transpose_divergence_action;
gravity_gradient_action += mass_variation_action; gravity_gradient_action += mass_variation_action;
divergence_action_true.SetSize(potential_map.full_size()); divergence_action_true.SetSize(potential_map.full_size());
m_fem.gravityContext.b_form->Mult(gravity_gradient_direction_true, divergence_action_true); geometry_context.GetDivergenceOperator().Mult(gravity_gradient_direction_true, divergence_action_true);
potential_map.gather(divergence_action_true, gravity_poisson_action); potential_map.gather(divergence_action_true, gravity_poisson_action);
gravity_poisson_action -= source_action; gravity_poisson_action -= source_action;

View File

@@ -111,7 +111,7 @@ namespace {
void validate_common_inputs( void validate_common_inputs(
const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper, const mean_field::mapping::DomainMapper &domainMapper,
const mfem::Vector &displacementTrue const mfem::Vector &displacementTrue
) { ) {
MFEM_VERIFY(f.mesh != nullptr, "The EOS closure kernel requires a mesh."); MFEM_VERIFY(f.mesh != nullptr, "The EOS closure kernel requires a mesh.");
@@ -150,7 +150,7 @@ namespace {
void apply_closure_action( void apply_closure_action(
const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper, const mean_field::mapping::DomainMapper &domainMapper,
const mean_field::eos::Polytrope &barotrope, const mean_field::eos::Polytrope &barotrope,
const ClosureAction closureAction, const ClosureAction closureAction,
const mfem::Vector *densityInputTrue, const mfem::Vector *densityInputTrue,
@@ -204,7 +204,7 @@ namespace {
mfem::Vector localAction(f.densityFes->GetVSize()); mfem::Vector localAction(f.densityFes->GetVSize());
localAction = 0.0; localAction = 0.0;
mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mean_field::mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> densityDofs; mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs; mfem::Array<int> enthalpyDofs;
@@ -365,7 +365,7 @@ namespace {
namespace mean_field::operators::kernels { namespace mean_field::operators::kernels {
void apply_barotropic_closure( void apply_barotropic_closure(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const mfem::Vector &densityTrue, const mfem::Vector &densityTrue,
const mfem::Vector &enthalpyTrue, const mfem::Vector &enthalpyTrue,
@@ -380,7 +380,7 @@ namespace mean_field::operators::kernels {
void apply_barotropic_closure_density_action( void apply_barotropic_closure_density_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const mfem::Vector &densityVariationTrue, const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
@@ -394,7 +394,7 @@ namespace mean_field::operators::kernels {
void apply_barotropic_closure_enthalpy_action( void apply_barotropic_closure_enthalpy_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &enthalpyVariationTrue, const mfem::Vector &enthalpyVariationTrue,
@@ -409,7 +409,7 @@ namespace mean_field::operators::kernels {
void apply_barotropic_closure_displacement_action( void apply_barotropic_closure_displacement_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const mfem::Vector &baseDensityTrue, const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &baseEnthalpyTrue,
@@ -490,7 +490,7 @@ namespace mean_field::operators::kernels {
mfem::Vector localAction(f.densityFes->GetVSize()); mfem::Vector localAction(f.densityFes->GetVSize());
localAction = 0.0; localAction = 0.0;
mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> densityDofs; mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs; mfem::Array<int> enthalpyDofs;

File diff suppressed because it is too large Load Diff

View File

@@ -183,7 +183,7 @@ namespace mean_field::operators {
PreparedBarotropicClosureOperator::PreparedBarotropicClosureOperator( PreparedBarotropicClosureOperator::PreparedBarotropicClosureOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState const eos::Polytrope &equationOfState
) )
: PreparedBarotropicClosureOperator( : PreparedBarotropicClosureOperator(
@@ -196,7 +196,7 @@ namespace mean_field::operators {
PreparedBarotropicClosureOperator::PreparedBarotropicClosureOperator( PreparedBarotropicClosureOperator::PreparedBarotropicClosureOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
ConstructionData constructionData ConstructionData constructionData
) )
@@ -285,7 +285,7 @@ namespace mean_field::operators {
true_to_local(*m_fem.enthalpyFes, m_baseEnthalpyTrue, baseEnthalpyLocal); true_to_local(*m_fem.enthalpyFes, m_baseEnthalpyTrue, baseEnthalpyLocal);
true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal); true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal);
mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mfem::Array<int> displacementDofs; mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs; mfem::Array<int> compactificationDofs;

View File

@@ -76,7 +76,7 @@ namespace {
namespace mean_field::operators { namespace mean_field::operators {
PreparedDisplacementResidualOperator::PreparedDisplacementResidualOperator( PreparedDisplacementResidualOperator::PreparedDisplacementResidualOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext const context::gravity_field::GravityFieldLinearizationContext &gravityContext
) )

View File

@@ -20,7 +20,7 @@ namespace {
namespace mean_field::operators { namespace mean_field::operators {
PreparedGravityDisplacementForceOperator::PreparedGravityDisplacementForceOperator( PreparedGravityDisplacementForceOperator::PreparedGravityDisplacementForceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext const context::gravity_field::GravityFieldLinearizationContext &gravityContext
) )
: m_fem(f), : m_fem(f),

View File

@@ -9,533 +9,531 @@ module mean_field;
import :operators.prepared_gravity_source; import :operators.prepared_gravity_source;
namespace { namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_height(const mean_field::fem::FEM &f) { int get_operator_height(const mean_field::fem::FEM &f) {
MFEM_VERIFY( MFEM_VERIFY(f.gravityPotentialFes != nullptr,
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the " "PreparedMappedGravitySourceOperator requires the "
"gravity-potential " "gravity-potential "
"finite-element space." "finite-element space.");
); return mean_field::field::make_field_dof_map<mean_field::field::Gravity,
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes) DomainSchema>(
.reduced_size(); *f.gravityPotentialFes)
} .reduced_size();
}
int get_operator_width(const mean_field::fem::FEM &f) { int get_operator_width(const mean_field::fem::FEM &f) {
MFEM_VERIFY( MFEM_VERIFY(f.densityFes != nullptr,
f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density " "PreparedMappedGravitySourceOperator requires the density "
"finite-element space." "finite-element space.");
); return mean_field::field::make_field_dof_map<mean_field::field::Density,
return mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*f.densityFes) DomainSchema>(*f.densityFes)
.reduced_size(); .reduced_size();
} }
void true_to_local( void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::ParFiniteElementSpace &finite_element_space, const mfem::Vector &true_vector,
const mfem::Vector &true_vector, mfem::Vector &local_vector) {
mfem::Vector &local_vector local_vector.SetSize(finite_element_space.GetVSize());
) {
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix(); const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) { if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector); prolongation->Mult(true_vector, local_vector);
} else { } else {
local_vector = true_vector; local_vector = true_vector;
} }
} }
void local_to_true( void local_to_true(const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::ParFiniteElementSpace &finite_element_space, const mfem::Vector &local_vector,
const mfem::Vector &local_vector, mfem::Vector &true_vector) {
mfem::Vector &true_vector MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(),
) { "Local vector has the wrong size.");
MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(), "Local vector has the wrong size.");
true_vector.SetSize(finite_element_space.GetTrueVSize()); true_vector.SetSize(finite_element_space.GetTrueVSize());
true_vector = 0.0; true_vector = 0.0;
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix(); const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) { if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector); prolongation->MultTranspose(local_vector, true_vector);
} else { } else {
true_vector = local_vector; true_vector = local_vector;
} }
} }
const mfem::IntegrationRule &get_source_rule( const mfem::IntegrationRule &
const mean_field::fem::FEM &f, get_source_rule(const mean_field::fem::FEM &f,
const mfem::FiniteElement &density_element, const mfem::FiniteElement &density_element,
const mfem::FiniteElement &potential_element, const mfem::FiniteElement &potential_element,
const mfem::ElementTransformation &transformation const mfem::ElementTransformation &transformation) {
) { using GravityField = mean_field::field::Field<mean_field::field::Gravity>;
using GravityField = mean_field::field::Field<mean_field::field::Gravity>; MFEM_VERIFY(density_element.GetOrder() ==
MFEM_VERIFY( mean_field::field::Density::Scalar::familyOrder,
density_element.GetOrder() == mean_field::field::Density::Scalar::familyOrder, "The prepared source trial element does not match the registered "
"The prepared source trial element does not match the registered " "density field.");
"density field." MFEM_VERIFY(potential_element.GetOrder() ==
); mean_field::field::Gravity::Potential::familyOrder,
MFEM_VERIFY( "The prepared source test element does not match the registered "
potential_element.GetOrder() == mean_field::field::Gravity::Potential::familyOrder, "gravity potential.");
"The prepared source test element does not match the registered " const mean_field::quadrature::Query query = GravityField::make_query<
"gravity potential." mean_field::field::Gravity::Form::SourceProjection>(
); mean_field::quadrature::QuadratureRole::discretization,
const mean_field::quadrature::Query query = transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
GravityField::make_query<mean_field::field::Gravity::Form::SourceProjection>( mean_field::quadrature::MappingKind::general);
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
return *f.quadratureFactory->get(query, transformation.GetGeometryType()).integration_rule; return *f.quadratureFactory->get(query, transformation.GetGeometryType())
} .integration_rule;
}
class FrozenMappedGravitySourceCoefficient final : public mfem::Coefficient { class FrozenMappedGravitySourceCoefficient final : public mfem::Coefficient {
public: public:
FrozenMappedGravitySourceCoefficient( FrozenMappedGravitySourceCoefficient(
const mean_field::fem::FEM &f, const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domain_mapper, const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::Vector &displacement_true const mfem::Vector &displacement_true)
) : m_fem(f), m_domain_mapper(domain_mapper),
: m_fem(f), m_workspace(domain_mapper.GetDimension()) {
m_domain_mapper(domain_mapper), true_to_local(*m_fem.displacementFes, displacement_true,
m_workspace(domain_mapper.GetDimension()) { m_displacement_local);
true_to_local(*m_fem.displacementFes, displacement_true, m_displacement_local); }
}
double Eval( double Eval(mfem::ElementTransformation &transformation,
mfem::ElementTransformation &transformation, const mfem::IntegrationPoint &integration_point) override {
const mfem::IntegrationPoint &integration_point transformation.SetIntPoint(&integration_point);
) override {
transformation.SetIntPoint(&integration_point);
const int element_id = transformation.ElementNo; const int element_id = transformation.ElementNo;
MFEM_VERIFY( MFEM_VERIFY(element_id >= 0 && element_id < m_fem.mesh->GetNE(),
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
"Mapped gravity source coefficient received an invalid element " "Mapped gravity source coefficient received an invalid element "
"ID." "ID.");
); if (DomainSchema::template attribute_belongs_to<
if (transformation.Attribute == m_domain_mapper.GetVacuumElementAttribute()) { mean_field::utils::domain::Vacuum>(transformation.Attribute)) {
return 0.0; return 0.0;
} }
LoadElement(element_id); LoadElement(element_id);
const mean_field::mapping::ElementMappingData mapping_data{ const mean_field::mapping::ElementMappingData mapping_data{
.displacement = *m_displacement_data, .compactification = *m_compactification_data .displacement = *m_displacement_data,
}; .compactification = *m_compactification_data};
mean_field::mapping::VolumeMappingContext mapping_context; mean_field::mapping::VolumeMappingContext mapping_context;
const mean_field::mapping::MappingStatus status = m_domain_mapper.EvaluateVolume( const mean_field::mapping::MappingStatus status =
mapping_data, transformation, integration_point, m_workspace, mapping_context m_domain_mapper.EvaluateVolume(mapping_data, transformation,
); integration_point, m_workspace,
mapping_context);
if (status != mean_field::mapping::MappingStatus::valid) { if (status != mean_field::mapping::MappingStatus::valid) {
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id); const mfem::FiniteElement &displacement_element =
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id); *m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
mfem::Vector displacement_shape(displacement_element.GetDof()); mfem::Vector displacement_shape(displacement_element.GetDof());
mfem::Vector compactification_shape(compactification_element.GetDof()); mfem::Vector compactification_shape(compactification_element.GetDof());
mfem::Vector reference_position(m_domain_mapper.GetDimension()); mfem::Vector reference_position(m_domain_mapper.GetDimension());
mfem::Vector displacement_value(m_domain_mapper.GetDimension()); mfem::Vector displacement_value(m_domain_mapper.GetDimension());
displacement_element.CalcShape(integration_point, displacement_shape); displacement_element.CalcShape(integration_point, displacement_shape);
compactification_element.CalcShape(integration_point, compactification_shape); compactification_element.CalcShape(integration_point,
transformation.Transform(integration_point, reference_position); compactification_shape);
m_displacement_data->GetDofMatrix().MultTranspose(displacement_shape, displacement_value); transformation.Transform(integration_point, reference_position);
m_displacement_data->GetDofMatrix().MultTranspose(displacement_shape,
displacement_value);
const double compactification_coordinate = m_compactification_data->GetDofs() * compactification_shape; const double compactification_coordinate =
m_compactification_data->GetDofs() * compactification_shape;
MFEM_ABORT( MFEM_ABORT(
"Stateless domain mapping failed while preparing the " "Stateless domain mapping failed while preparing the "
"gravity " "gravity "
"source operator." "source operator."
<< "\nMapping status = " << static_cast<int>(status) << "\nElement ID = " << element_id << "\nMapping status = " << static_cast<int>(status)
<< "\nElement attribute = " << transformation.Attribute << "\nElement ID = " << element_id
<< "\nIntegration-point index = " << integration_point.index << "\nIntegration point = <" << "\nElement attribute = " << transformation.Attribute
<< integration_point.x << ", " << integration_point.y << ", " << integration_point.z << ">" << "\nIntegration-point index = " << integration_point.index
<< "\nReference position = <" << reference_position(0) << ", " << reference_position(1) << ", " << "\nIntegration point = <" << integration_point.x << ", "
<< reference_position(2) << ">" << integration_point.y << ", " << integration_point.z << ">"
<< "\nReference radius = " << reference_position.Norml2() << "\nDisplacement value = <" << "\nReference position = <" << reference_position(0) << ", "
<< displacement_value(0) << ", " << displacement_value(1) << ", " << displacement_value(2) << ">" << reference_position(1) << ", " << reference_position(2) << ">"
<< "\nDisplacement magnitude = " << displacement_value.Norml2() << "\nReference radius = " << reference_position.Norml2()
<< "\nCompactification coordinate = " << compactification_coordinate << "\nDisplacement value = <" << displacement_value(0) << ", "
<< "\nDisplacement ordering = " << static_cast<int>(m_fem.displacementFes->GetOrdering()) << displacement_value(1) << ", " << displacement_value(2) << ">"
); << "\nDisplacement magnitude = " << displacement_value.Norml2()
} << "\nCompactification coordinate = " << compactification_coordinate
const double mapping_determinant = mapping_context.mapping.mapping_determinant; << "\nDisplacement ordering = "
MFEM_VERIFY( << static_cast<int>(m_fem.displacementFes->GetOrdering()));
std::isfinite(mapping_determinant) && mapping_determinant > 0.0, }
const double mapping_determinant =
mapping_context.mapping.mapping_determinant;
MFEM_VERIFY(std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
"Prepared gravity source operator encountered a non-positive " "Prepared gravity source operator encountered a non-positive "
"or " "or "
"non-finite mapping determinant." "non-finite mapping determinant.");
);
return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant; return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
} }
private: private:
void LoadElement(const int element_id) { void LoadElement(const int element_id) {
if (element_id == m_cached_element_id) { if (element_id == m_cached_element_id) {
return; return;
} }
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id); const mfem::FiniteElement &displacement_element =
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id); *m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
mfem::DofTransformation *displacement_dof_transformation = mfem::DofTransformation *displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs); m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation = mfem::DofTransformation *compactification_dof_transformation =
m_fem.compactificationFes->GetElementDofs(element_id, m_compactification_dofs); m_fem.compactificationFes->GetElementDofs(element_id,
m_compactification_dofs);
m_displacement_local.GetSubVector(m_displacement_dofs, m_element_displacement); m_displacement_local.GetSubVector(m_displacement_dofs,
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs, m_element_compactification); m_element_displacement);
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs,
m_element_compactification);
if (displacement_dof_transformation != nullptr) { if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(m_element_displacement); displacement_dof_transformation->InvTransformPrimal(
} m_element_displacement);
}
if (compactification_dof_transformation != nullptr) { if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal(m_element_compactification); compactification_dof_transformation->InvTransformPrimal(
} m_element_compactification);
}
m_displacement_data = std::make_unique<mean_field::mapping::ElementDisplacementData>( m_displacement_data =
mean_field::mapping::ElementDisplacementDataFromElementVDofs( std::make_unique<mean_field::mapping::ElementDisplacementData>(
displacement_element, m_element_displacement mean_field::mapping::ElementDisplacementDataFromElementVDofs(
) displacement_element, m_element_displacement));
);
m_compactification_data = std::make_unique<mean_field::mapping::ElementCompactificationData>( m_compactification_data =
compactification_element, m_element_compactification std::make_unique<mean_field::mapping::ElementCompactificationData>(
); compactification_element, m_element_compactification);
m_cached_element_id = element_id; m_cached_element_id = element_id;
} }
const mean_field::fem::FEM &m_fem; const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapperStateless &m_domain_mapper; const mean_field::mapping::DomainMapper &m_domain_mapper;
mfem::Vector m_displacement_local; mfem::Vector m_displacement_local;
mfem::Array<int> m_displacement_dofs; mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs; mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement; mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification; mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data; std::unique_ptr<mean_field::mapping::ElementDisplacementData>
std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data; m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
mean_field::mapping::DomainMapperStateless::Workspace m_workspace; mean_field::mapping::DomainMapper::Workspace m_workspace;
int m_cached_element_id{-1}; int m_cached_element_id{-1};
}; };
} // namespace } // namespace
namespace mean_field::operators { namespace mean_field::operators {
PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator( PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator(
const fem::FEM &f, const fem::FEM &f, const mapping::DomainMapper &domain_mapper)
const mapping::DomainMapperStateless &domain_mapper : Operator(get_operator_height(f), get_operator_width(f)), m_fem(f),
) m_domain_mapper(domain_mapper),
: Operator( m_density_map(field::make_field_dof_map<field::Density, DomainSchema>(
get_operator_height(f), *f.densityFes)),
get_operator_width(f) m_potential_map(field::make_field_dof_map<field::Gravity, DomainSchema>(
), *f.gravityPotentialFes)),
m_fem(f), m_displacement_map(
m_domain_mapper(domain_mapper), field::make_field_dof_map<field::Displacement, DomainSchema>(
m_density_map( *f.displacementFes)) {
field::make_field_dof_map< MFEM_VERIFY(f.mesh != nullptr,
field::Density, "PreparedMappedGravitySourceOperator requires a mesh.");
DomainSchema>(*f.densityFes) MFEM_VERIFY(f.densityFes != nullptr,
), "PreparedMappedGravitySourceOperator requires the density "
m_potential_map( "finite-element space.");
field::make_field_dof_map< MFEM_VERIFY(f.gravityPotentialFes != nullptr,
field::Gravity, "PreparedMappedGravitySourceOperator requires the "
DomainSchema>(*f.gravityPotentialFes) "gravity-potential "
), "finite-element space.");
m_displacement_map( MFEM_VERIFY(f.displacementFes != nullptr,
field::make_field_dof_map< "PreparedMappedGravitySourceOperator requires "
field::Displacement, "the displacement finite-element space.");
DomainSchema>(*f.displacementFes) MFEM_VERIFY(
) { f.compactificationFes != nullptr,
MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedGravitySourceOperator requires a mesh."); "PreparedMappedGravitySourceOperator requires the compactification "
MFEM_VERIFY( "finite-element space.");
f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density " MFEM_VERIFY(
"finite-element space." f.compactificationCoordinate != nullptr,
); "PreparedMappedGravitySourceOperator requires the compactification "
MFEM_VERIFY( "coordinate.");
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the " MFEM_VERIFY(f.quadratureFactory != nullptr,
"gravity-potential " "PreparedMappedGravitySourceOperator "
"finite-element space." "requires the quadrature-rule factory.");
); MFEM_VERIFY(domain_mapper.GetDimension() == f.mesh->Dimension(),
MFEM_VERIFY( "The stateless domain-mapper dimension does not match the mesh "
f.displacementFes != nullptr, "PreparedMappedGravitySourceOperator requires " "dimension.");
"the displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr, "PreparedMappedGravitySourceOperator requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"PreparedMappedGravitySourceOperator requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr, "PreparedMappedGravitySourceOperator "
"requires the quadrature-rule factory."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"The stateless domain-mapper dimension does not match the mesh "
"dimension."
);
utils::populate_element_mask(f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker); m_stellar_marker =
utils::domain::make_attribute_marker<utils::domain::Stellar,
DomainSchema>(*f.mesh);
}
void PreparedMappedGravitySourceOperator::Prepare(
const mfem::Vector &displacement) {
MFEM_VERIFY(displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedGravitySourceOperator received a displacement "
"vector "
"with the wrong size.");
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(std::isfinite(displacement(i)),
"PreparedMappedGravitySourceOperator received a non-finite "
"displacement value.");
}
m_is_prepared = false;
m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
FrozenMappedGravitySourceCoefficient source_coefficient(
m_fem, m_domain_mapper, m_displacement_true);
for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) {
const int attribute = m_fem.mesh->GetAttribute(element_id);
if (attribute <= 0 || attribute > m_stellar_marker.Size() ||
m_stellar_marker[attribute - 1] == 0) {
continue;
} }
void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement) { m_elements.emplace_back();
MFEM_VERIFY( ElementPAData &data = m_elements.back();
displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedGravitySourceOperator received a displacement "
"vector "
"with the wrong size."
);
for (int i = 0; i < displacement.Size(); ++i) { data.element_id = element_id;
MFEM_VERIFY(
std::isfinite(displacement(i)), "PreparedMappedGravitySourceOperator received a non-finite "
"displacement value."
);
}
m_is_prepared = false; data.density_dof_transformation =
m_displacement_true.SetSize(m_displacement_map.full_size()); m_fem.densityFes->GetElementDofs(element_id, data.density_dofs);
m_displacement_map.scatter(displacement, m_displacement_true);
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
FrozenMappedGravitySourceCoefficient source_coefficient(m_fem, m_domain_mapper, m_displacement_true); data.potential_dof_transformation =
m_fem.gravityPotentialFes->GetElementDofs(element_id,
data.potential_dofs);
for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) { const mfem::FiniteElement &density_element =
const int attribute = m_fem.mesh->GetAttribute(element_id); *m_fem.densityFes->GetFE(element_id);
if (attribute <= 0 || attribute > m_stellar_marker.Size() || m_stellar_marker[attribute - 1] == 0) { const mfem::FiniteElement &potential_element =
continue; *m_fem.gravityPotentialFes->GetFE(element_id);
}
m_elements.emplace_back(); mfem::ElementTransformation &transformation =
ElementPAData &data = m_elements.back(); *m_fem.mesh->GetElementTransformation(element_id);
data.element_id = element_id; const mfem::IntegrationRule &integration_rule = get_source_rule(
m_fem, density_element, potential_element, transformation);
data.density_dof_transformation = m_fem.densityFes->GetElementDofs(element_id, data.density_dofs); const int quadrature_point_count = integration_rule.GetNPoints();
data.potential_dof_transformation = const int density_dof_count = density_element.GetDof();
m_fem.gravityPotentialFes->GetElementDofs(element_id, data.potential_dofs);
const mfem::FiniteElement &density_element = *m_fem.densityFes->GetFE(element_id); const int potential_dof_count = potential_element.GetDof();
const mfem::FiniteElement &potential_element = *m_fem.gravityPotentialFes->GetFE(element_id); data.density_basis.SetSize(quadrature_point_count, density_dof_count);
mfem::ElementTransformation &transformation = *m_fem.mesh->GetElementTransformation(element_id); data.potential_basis.SetSize(quadrature_point_count, potential_dof_count);
const mfem::IntegrationRule &integration_rule = data.quadrature_data.SetSize(quadrature_point_count);
get_source_rule(m_fem, density_element, potential_element, transformation);
const int quadrature_point_count = integration_rule.GetNPoints(); mfem::Vector density_shape(density_dof_count);
mfem::Vector potential_shape(potential_dof_count);
const int density_dof_count = density_element.GetDof(); for (int quadrature_point = 0; quadrature_point < quadrature_point_count;
++quadrature_point) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(quadrature_point);
const int potential_dof_count = potential_element.GetDof(); transformation.SetIntPoint(&integration_point);
data.density_basis.SetSize(quadrature_point_count, density_dof_count); // CalcPhysShape matches the scalar mixed-mass discretization,
// including the finite-element map type.
density_element.CalcPhysShape(transformation, density_shape);
data.potential_basis.SetSize(quadrature_point_count, potential_dof_count); potential_element.CalcPhysShape(transformation, potential_shape);
data.quadrature_data.SetSize(quadrature_point_count); for (int i = 0; i < density_dof_count; ++i) {
data.density_basis(quadrature_point, i) = density_shape(i);
}
mfem::Vector density_shape(density_dof_count); for (int i = 0; i < potential_dof_count; ++i) {
mfem::Vector potential_shape(potential_dof_count); data.potential_basis(quadrature_point, i) = potential_shape(i);
}
for (int quadrature_point = 0; quadrature_point < quadrature_point_count; ++quadrature_point) { const double coefficient_value =
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(quadrature_point); source_coefficient.Eval(transformation, integration_point);
transformation.SetIntPoint(&integration_point); transformation.SetIntPoint(&integration_point);
// CalcPhysShape matches the scalar mixed-mass discretization, const double quadrature_value = integration_point.weight *
// including the finite-element map type. transformation.Weight() *
density_element.CalcPhysShape(transformation, density_shape); coefficient_value;
potential_element.CalcPhysShape(transformation, potential_shape); MFEM_VERIFY(std::isfinite(quadrature_value) && quadrature_value > 0.0,
"Prepared gravity source operator encountered invalid "
"quadrature data on element "
<< element_id << ", quadrature point " << quadrature_point
<< ".");
for (int i = 0; i < density_dof_count; ++i) { data.quadrature_data(quadrature_point) = quadrature_value;
data.density_basis(quadrature_point, i) = density_shape(i);
}
for (int i = 0; i < potential_dof_count; ++i) {
data.potential_basis(quadrature_point, i) = potential_shape(i);
}
const double coefficient_value = source_coefficient.Eval(transformation, integration_point);
transformation.SetIntPoint(&integration_point);
const double quadrature_value = integration_point.weight * transformation.Weight() * coefficient_value;
MFEM_VERIFY(
std::isfinite(quadrature_value) && quadrature_value > 0.0,
"Prepared gravity source operator encountered invalid "
"quadrature data on element "
<< element_id << ", quadrature point " << quadrature_point << "."
);
data.quadrature_data(quadrature_point) = quadrature_value;
}
}
MFEM_VERIFY(!m_elements.empty(), "PreparedMappedGravitySourceOperator found no stellar elements.");
m_is_prepared = true;
++m_preparation_count;
} }
void PreparedMappedGravitySourceOperator::Mult( }
const mfem::Vector &density,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
"Mult is called."
);
MFEM_VERIFY( MFEM_VERIFY(!m_elements.empty(),
density.Size() == Width(), "PreparedMappedGravitySourceOperator received a density vector " "PreparedMappedGravitySourceOperator found no stellar elements.");
"with the wrong size."
);
m_density_true.SetSize(m_density_map.full_size()); m_is_prepared = true;
m_density_map.scatter(density, m_density_true); ++m_preparation_count;
}
void PreparedMappedGravitySourceOperator::Mult(const mfem::Vector &density,
mfem::Vector &action) const {
MFEM_VERIFY(m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"Mult is called.");
mfem::Vector density_local; MFEM_VERIFY(density.Size() == Width(),
"PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size.");
true_to_local(*m_fem.densityFes, m_density_true, density_local); m_density_true.SetSize(m_density_map.full_size());
m_density_map.scatter(density, m_density_true);
mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize()); mfem::Vector density_local;
local_action = 0.0;
mfem::Vector element_density; true_to_local(*m_fem.densityFes, m_density_true, density_local);
mfem::Vector quadrature_density;
mfem::Vector element_action;
for (const ElementPAData &data : m_elements) { mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize());
density_local.GetSubVector(data.density_dofs, element_density); local_action = 0.0;
if (data.density_dof_transformation != nullptr) { mfem::Vector element_density;
data.density_dof_transformation->InvTransformPrimal(element_density); mfem::Vector quadrature_density;
} mfem::Vector element_action;
quadrature_density.SetSize(data.quadrature_data.Size()); for (const ElementPAData &data : m_elements) {
density_local.GetSubVector(data.density_dofs, element_density);
// B_density * x_e if (data.density_dof_transformation != nullptr) {
data.density_basis.Mult(element_density, quadrature_density); data.density_dof_transformation->InvTransformPrimal(element_density);
// D * B_density * x_e
for (int q = 0; q < quadrature_density.Size(); ++q) {
quadrature_density(q) *= data.quadrature_data(q);
}
element_action.SetSize(data.potential_dofs.Size());
// B_potential^T * D * B_density * x_e
data.potential_basis.MultTranspose(quadrature_density, element_action);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->TransformDual(element_action);
}
local_action.AddElementVector(data.potential_dofs, element_action);
}
local_to_true(*m_fem.gravityPotentialFes, local_action, m_action_true);
action.SetSize(Height());
m_potential_map.gather(m_action_true, action);
} }
void PreparedMappedGravitySourceOperator::MultTranspose( quadrature_density.SetSize(data.quadrature_data.Size());
const mfem::Vector &potential,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called."
);
MFEM_VERIFY( // B_density * x_e
potential.Size() == Height(), "PreparedMappedGravitySourceOperator received a potential vector " data.density_basis.Mult(element_density, quadrature_density);
"with the wrong size."
);
m_potential_true.SetSize(m_potential_map.full_size()); // D * B_density * x_e
m_potential_map.scatter(potential, m_potential_true); for (int q = 0; q < quadrature_density.Size(); ++q) {
quadrature_density(q) *= data.quadrature_data(q);
mfem::Vector potential_local;
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, potential_local);
mfem::Vector local_action(m_fem.densityFes->GetVSize());
local_action = 0.0;
mfem::Vector element_potential;
mfem::Vector quadrature_potential;
mfem::Vector element_action;
for (const ElementPAData &data : m_elements) {
potential_local.GetSubVector(data.potential_dofs, element_potential);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->InvTransformPrimal(element_potential);
}
quadrature_potential.SetSize(data.quadrature_data.Size());
data.potential_basis.Mult(element_potential, quadrature_potential);
for (int q = 0; q < quadrature_potential.Size(); ++q) {
quadrature_potential(q) *= data.quadrature_data(q);
}
element_action.SetSize(data.density_dofs.Size());
data.density_basis.MultTranspose(quadrature_potential, element_action);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->TransformDual(element_action);
}
local_action.AddElementVector(data.density_dofs, element_action);
}
local_to_true(*m_fem.densityFes, local_action, m_action_true);
action.SetSize(Width());
m_density_map.gather(m_action_true, action);
}
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
} }
std::uint64_t PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept { element_action.SetSize(data.potential_dofs.Size());
return m_preparation_count;
// B_potential^T * D * B_density * x_e
data.potential_basis.MultTranspose(quadrature_density, element_action);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->TransformDual(element_action);
} }
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept { local_action.AddElementVector(data.potential_dofs, element_action);
return m_density_map; }
local_to_true(*m_fem.gravityPotentialFes, local_action, m_action_true);
action.SetSize(Height());
m_potential_map.gather(m_action_true, action);
}
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential, mfem::Vector &action) const {
MFEM_VERIFY(m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called.");
MFEM_VERIFY(potential.Size() == Height(),
"PreparedMappedGravitySourceOperator received a potential vector "
"with the wrong size.");
m_potential_true.SetSize(m_potential_map.full_size());
m_potential_map.scatter(potential, m_potential_true);
mfem::Vector potential_local;
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, potential_local);
mfem::Vector local_action(m_fem.densityFes->GetVSize());
local_action = 0.0;
mfem::Vector element_potential;
mfem::Vector quadrature_potential;
mfem::Vector element_action;
for (const ElementPAData &data : m_elements) {
potential_local.GetSubVector(data.potential_dofs, element_potential);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->InvTransformPrimal(element_potential);
} }
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept { quadrature_potential.SetSize(data.quadrature_data.Size());
return m_potential_map;
data.potential_basis.Mult(element_potential, quadrature_potential);
for (int q = 0; q < quadrature_potential.Size(); ++q) {
quadrature_potential(q) *= data.quadrature_data(q);
} }
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept { element_action.SetSize(data.density_dofs.Size());
return m_displacement_map;
data.density_basis.MultTranspose(quadrature_potential, element_action);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->TransformDual(element_action);
} }
local_action.AddElementVector(data.density_dofs, element_action);
}
local_to_true(*m_fem.densityFes, local_action, m_action_true);
action.SetSize(Width());
m_density_map.gather(m_action_true, action);
}
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept {
return m_density_map;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept {
return m_potential_map;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
} // namespace mean_field::operators } // namespace mean_field::operators

View File

@@ -8,379 +8,405 @@ module mean_field;
import :operators.prepared_hdiv_mass; import :operators.prepared_hdiv_mass;
namespace { namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_size(const mean_field::fem::FEM &f) { int get_operator_size(const mean_field::fem::FEM &f) {
MFEM_VERIFY( MFEM_VERIFY(f.gravityFluxFes != nullptr,
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the " "PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space." "gravity-gradient finite-element space.");
); return mean_field::field::make_field_dof_map<mean_field::field::Gravity,
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityFluxFes) DomainSchema>(*f.gravityFluxFes)
.reduced_size(); .reduced_size();
}
void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &true_vector,
mfem::Vector &local_vector) {
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
} else {
local_vector = true_vector;
}
}
int find_representative_element(const mean_field::fem::FEM &f,
const mfem::Array<int> &marker) {
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const int attribute = f.mesh->GetAttribute(element_id);
if (attribute > 0 && attribute <= marker.Size() &&
marker[attribute - 1] != 0) {
return element_id;
}
}
return -1;
}
void validate_uniform_domain_discretization(
const mean_field::fem::FEM &f, const mfem::Array<int> &marker,
const int representative_element_id) {
const mfem::FiniteElement &representative_element =
*f.gravityFluxFes->GetFE(representative_element_id);
const mfem::ElementTransformation &representative_transformation =
*f.mesh->GetElementTransformation(representative_element_id);
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const int attribute = f.mesh->GetAttribute(element_id);
if (attribute <= 0 || attribute > marker.Size() ||
marker[attribute - 1] == 0) {
continue;
} }
void true_to_local( const mfem::FiniteElement &element = *f.gravityFluxFes->GetFE(element_id);
const mfem::ParFiniteElementSpace &finite_element_space, const mfem::ElementTransformation &transformation =
const mfem::Vector &true_vector, *f.mesh->GetElementTransformation(element_id);
mfem::Vector &local_vector
) {
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix(); MFEM_VERIFY(element.GetGeomType() == representative_element.GetGeomType(),
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
} else {
local_vector = true_vector;
}
}
int find_representative_element(
const mean_field::fem::FEM &f,
const mfem::Array<int> &marker
) {
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const int attribute = f.mesh->GetAttribute(element_id);
if (attribute > 0 && attribute <= marker.Size() && marker[attribute - 1] != 0) {
return element_id;
}
}
return -1;
}
void validate_uniform_domain_discretization(
const mean_field::fem::FEM &f,
const mfem::Array<int> &marker,
const int representative_element_id
) {
const mfem::FiniteElement &representative_element = *f.gravityFluxFes->GetFE(representative_element_id);
const mfem::ElementTransformation &representative_transformation =
*f.mesh->GetElementTransformation(representative_element_id);
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const int attribute = f.mesh->GetAttribute(element_id);
if (attribute <= 0 || attribute > marker.Size() || marker[attribute - 1] == 0) {
continue;
}
const mfem::FiniteElement &element = *f.gravityFluxFes->GetFE(element_id);
const mfem::ElementTransformation &transformation = *f.mesh->GetElementTransformation(element_id);
MFEM_VERIFY(
element.GetGeomType() == representative_element.GetGeomType(),
"Prepared H(div) mass domains currently require a uniform " "Prepared H(div) mass domains currently require a uniform "
"element " "element "
"geometry." "geometry.");
); MFEM_VERIFY(element.GetOrder() == representative_element.GetOrder(),
MFEM_VERIFY(
element.GetOrder() == representative_element.GetOrder(),
"Prepared H(div) mass domains currently require a uniform " "Prepared H(div) mass domains currently require a uniform "
"finite-element order." "finite-element order.");
); MFEM_VERIFY(transformation.OrderW() ==
MFEM_VERIFY( representative_transformation.OrderW(),
transformation.OrderW() == representative_transformation.OrderW(),
"Prepared H(div) mass domains currently require a uniform " "Prepared H(div) mass domains currently require a uniform "
"geometry-weight order." "geometry-weight order.");
); }
} }
class FrozenMappedHDivMassCoefficient final : public mfem::MatrixCoefficient {
public:
FrozenMappedHDivMassCoefficient(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::Vector &displacement_true, bool elevates_vacuum)
: MatrixCoefficient(domain_mapper.GetDimension()), m_fem(f),
m_domain_mapper(domain_mapper),
m_workspace(domain_mapper.GetDimension()),
m_elevates_vacuum(elevates_vacuum) {
true_to_local(*m_fem.displacementFes, displacement_true,
m_displacement_local);
}
void Eval(mfem::DenseMatrix &mass_tensor,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point) override {
transformation.SetIntPoint(&integration_point);
const int element_id = transformation.ElementNo;
MFEM_VERIFY(
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
"Mapped H(div) mass coefficient received an invalid element ID.");
const bool element_is_vacuum = DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(transformation.Attribute);
if (element_is_vacuum != m_elevates_vacuum) {
mass_tensor.SetSize(m_domain_mapper.GetDimension());
mass_tensor = 0.0;
return;
} }
class FrozenMappedHDivMassCoefficient final : public mfem::MatrixCoefficient { LoadElement(element_id);
public:
FrozenMappedHDivMassCoefficient(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domain_mapper,
const mfem::Vector &displacement_true,
bool elevates_vacuum
)
: MatrixCoefficient(domain_mapper.GetDimension()),
m_fem(f),
m_domain_mapper(domain_mapper),
m_workspace(domain_mapper.GetDimension()),
m_elevates_vacuum(elevates_vacuum) {
true_to_local(*m_fem.displacementFes, displacement_true, m_displacement_local);
}
void Eval( const mean_field::mapping::ElementMappingData mapping_data{
mfem::DenseMatrix &mass_tensor, .displacement = *m_displacement_data,
mfem::ElementTransformation &transformation, .compactification = *m_compactification_data};
const mfem::IntegrationPoint &integration_point
) override {
transformation.SetIntPoint(&integration_point);
const int element_id = transformation.ElementNo; mean_field::mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY(
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
"Mapped H(div) mass coefficient received an invalid element ID."
);
const bool element_is_vacuum = transformation.Attribute == m_domain_mapper.GetVacuumElementAttribute(); const mean_field::mapping::MappingStatus status =
m_domain_mapper.EvaluateVolume(mapping_data, transformation,
integration_point, m_workspace,
mapping_context);
if (element_is_vacuum != m_elevates_vacuum) { MFEM_VERIFY(status == mean_field::mapping::MappingStatus::valid,
mass_tensor.SetSize(m_domain_mapper.GetDimension());
mass_tensor = 0.0;
return;
}
LoadElement(element_id);
const mean_field::mapping::ElementMappingData mapping_data{
.displacement = *m_displacement_data, .compactification = *m_compactification_data
};
mean_field::mapping::VolumeMappingContext mapping_context;
const mean_field::mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
mapping_data, transformation, integration_point, m_workspace, mapping_context
);
MFEM_VERIFY(
status == mean_field::mapping::MappingStatus::valid,
"Stateless domain mapping failed while preparing the H(div) " "Stateless domain mapping failed while preparing the H(div) "
"mass " "mass "
"operator. Mapping status = " "operator. Mapping status = "
<< static_cast<int>(status) << ", element ID = " << element_id << static_cast<int>(status)
<< ", element ID = " << element_id
<< ", element attribute = " << transformation.Attribute << ", element attribute = " << transformation.Attribute
<< ", coefficient domain = " << (m_elevates_vacuum ? "vacuum" : "stellar") << ", coefficient domain = "
); << (m_elevates_vacuum ? "vacuum" : "stellar"));
const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping.mapping_jacobian; const mfem::DenseMatrix &mapping_jacobian =
const double mapping_determinant = mapping_context.mapping.mapping_determinant; mapping_context.mapping.mapping_jacobian;
const double mapping_determinant =
mapping_context.mapping.mapping_determinant;
MFEM_VERIFY( MFEM_VERIFY(std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
"Prepared H(div) mass operator encountered a non-positive or " "Prepared H(div) mass operator encountered a non-positive or "
"non-finite mapping determinant." "non-finite mapping determinant.");
);
mfem::MultAtB(mapping_jacobian, mapping_jacobian, mass_tensor); mfem::MultAtB(mapping_jacobian, mapping_jacobian, mass_tensor);
mass_tensor *= 1.0 / mapping_determinant; mass_tensor *= 1.0 / mapping_determinant;
} }
private: private:
void LoadElement(const int element_id) { void LoadElement(const int element_id) {
if (element_id == m_cached_element_id) { if (element_id == m_cached_element_id) {
return; return;
} }
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id); const mfem::FiniteElement &displacement_element =
const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id); *m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
mfem::DofTransformation *displacement_dof_transformation = mfem::DofTransformation *displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs); m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs);
mfem::DofTransformation *compactification_dof_transformation = mfem::DofTransformation *compactification_dof_transformation =
m_fem.compactificationFes->GetElementDofs(element_id, m_compactification_dofs); m_fem.compactificationFes->GetElementDofs(element_id,
m_compactification_dofs);
m_displacement_local.GetSubVector(m_displacement_dofs, m_element_displacement); m_displacement_local.GetSubVector(m_displacement_dofs,
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs, m_element_compactification); m_element_displacement);
m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs,
m_element_compactification);
if (displacement_dof_transformation != nullptr) { if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(m_element_displacement); displacement_dof_transformation->InvTransformPrimal(
} m_element_displacement);
}
if (compactification_dof_transformation != nullptr) { if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal(m_element_compactification); compactification_dof_transformation->InvTransformPrimal(
} m_element_compactification);
}
m_displacement_data = std::make_unique<mean_field::mapping::ElementDisplacementData>( m_displacement_data =
mean_field::mapping::ElementDisplacementDataFromElementVDofs( std::make_unique<mean_field::mapping::ElementDisplacementData>(
displacement_element, m_element_displacement mean_field::mapping::ElementDisplacementDataFromElementVDofs(
) displacement_element, m_element_displacement));
);
m_compactification_data = std::make_unique<mean_field::mapping::ElementCompactificationData>( m_compactification_data =
compactification_element, m_element_compactification std::make_unique<mean_field::mapping::ElementCompactificationData>(
); compactification_element, m_element_compactification);
m_cached_element_id = element_id; m_cached_element_id = element_id;
} }
const mean_field::fem::FEM &m_fem; const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapperStateless &m_domain_mapper; const mean_field::mapping::DomainMapper &m_domain_mapper;
mfem::Vector m_displacement_local; mfem::Vector m_displacement_local;
mfem::Array<int> m_displacement_dofs; mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs; mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement; mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification; mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data; std::unique_ptr<mean_field::mapping::ElementDisplacementData>
std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data; m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
mean_field::mapping::DomainMapperStateless::Workspace m_workspace; mean_field::mapping::DomainMapper::Workspace m_workspace;
int m_cached_element_id{-1}; int m_cached_element_id{-1};
bool m_elevates_vacuum; bool m_elevates_vacuum;
}; };
} // namespace } // namespace
namespace mean_field::operators { namespace mean_field::operators {
PreparedMappedHDivMassOperator::PreparedMappedHDivMassOperator( PreparedMappedHDivMassOperator::PreparedMappedHDivMassOperator(
const fem::FEM &f, const fem::FEM &f, const mapping::DomainMapper &domain_mapper)
const mapping::DomainMapperStateless &domain_mapper : Operator(get_operator_size(f)), m_fem(f), m_domain_mapper(domain_mapper),
) m_flux_map(field::make_field_dof_map<field::Gravity, DomainSchema>(
: Operator(get_operator_size(f)), *f.gravityFluxFes)),
m_fem(f), m_displacement_map(
m_domain_mapper(domain_mapper), field::make_field_dof_map<field::Displacement, DomainSchema>(
m_flux_map( *f.displacementFes)) {
field::make_field_dof_map< MFEM_VERIFY(f.mesh != nullptr,
field::Gravity, "PreparedMappedHDivMassOperator requires a mesh.");
DomainSchema>(*f.gravityFluxFes) MFEM_VERIFY(f.gravityFluxFes != nullptr,
), "PreparedMappedHDivMassOperator requires the "
m_displacement_map( "gravity-gradient finite-element space.");
field::make_field_dof_map< MFEM_VERIFY(f.displacementFes != nullptr,
field::Displacement, "PreparedMappedHDivMassOperator requires the "
DomainSchema>(*f.displacementFes) "displacement finite-element space.");
) { MFEM_VERIFY(f.compactificationFes != nullptr,
MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh."); "PreparedMappedHDivMassOperator requires the compactification "
MFEM_VERIFY( "finite-element space.");
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the " MFEM_VERIFY(f.compactificationCoordinate != nullptr,
"gravity-gradient finite-element space." "PreparedMappedHDivMassOperator requires the compactification "
); "coordinate.");
MFEM_VERIFY( MFEM_VERIFY(f.quadratureFactory != nullptr,
f.displacementFes != nullptr, "PreparedMappedHDivMassOperator requires the " "PreparedMappedHDivMassOperator requires the quadrature-rule "
"displacement finite-element space." "factory.");
); MFEM_VERIFY(domain_mapper.GetDimension() == f.mesh->Dimension(),
MFEM_VERIFY( "The stateless domain-mapper dimension does not match the mesh "
f.compactificationFes != nullptr, "PreparedMappedHDivMassOperator requires the compactification " "dimension.");
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "PreparedMappedHDivMassOperator requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr, "PreparedMappedHDivMassOperator requires the quadrature-rule "
"factory."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"The stateless domain-mapper dimension does not match the mesh "
"dimension."
);
utils::populate_element_mask(f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker); m_stellar_marker =
utils::populate_element_mask(f.mesh.get(), utils::DOMAINS::VACUUM, m_vacuum_marker); utils::domain::make_attribute_marker<utils::domain::Stellar,
DomainSchema>(*f.mesh);
m_vacuum_marker =
utils::domain::make_attribute_marker<utils::domain::Vacuum, DomainSchema>(
*f.mesh);
const int stellar_element_id = find_representative_element(f, m_stellar_marker); const int stellar_element_id =
const int vacuum_element_id = find_representative_element(f, m_vacuum_marker); find_representative_element(f, m_stellar_marker);
const int vacuum_element_id = find_representative_element(f, m_vacuum_marker);
MFEM_VERIFY( MFEM_VERIFY(stellar_element_id >= 0,
stellar_element_id >= 0, "PreparedMappedHDivMassOperator requires " "PreparedMappedHDivMassOperator requires "
"at least one stellar element." "at least one stellar element.");
); MFEM_VERIFY(vacuum_element_id >= 0,
MFEM_VERIFY( "PreparedMappedHDivMassOperator requires at "
vacuum_element_id >= 0, "PreparedMappedHDivMassOperator requires at " "least one compactified vacuum element.");
"least one compactified vacuum element."
);
validate_uniform_domain_discretization(f, m_stellar_marker, stellar_element_id); validate_uniform_domain_discretization(f, m_stellar_marker,
validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id); stellar_element_id);
} validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id);
}
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) { void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
MFEM_VERIFY( MFEM_VERIFY(displacement.Size() == m_displacement_map.reduced_size(),
displacement.Size() == m_displacement_map.reduced_size(), "PreparedMappedHDivMassOperator received a displacement vector "
"PreparedMappedHDivMassOperator received a displacement vector " "with "
"with " "the wrong size.");
"the wrong size."
);
for (int i = 0; i < displacement.Size(); ++i) { for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY( MFEM_VERIFY(std::isfinite(displacement(i)),
std::isfinite(displacement(i)), "PreparedMappedHDivMassOperator received a non-finite " "PreparedMappedHDivMassOperator received a non-finite "
"displacement " "displacement "
"value." "value.");
); }
}
m_displacement_true.SetSize(m_displacement_map.full_size()); m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true); m_displacement_map.scatter(displacement, m_displacement_true);
const int stellar_element_id = find_representative_element(m_fem, m_stellar_marker); const int stellar_element_id =
const int vacuum_element_id = find_representative_element(m_fem, m_vacuum_marker); find_representative_element(m_fem, m_stellar_marker);
const int vacuum_element_id =
find_representative_element(m_fem, m_vacuum_marker);
const mfem::FiniteElement &stellar_element = *m_fem.gravityFluxFes->GetFE(stellar_element_id); const mfem::FiniteElement &stellar_element =
const mfem::FiniteElement &vacuum_element = *m_fem.gravityFluxFes->GetFE(vacuum_element_id); *m_fem.gravityFluxFes->GetFE(stellar_element_id);
const mfem::FiniteElement &vacuum_element =
*m_fem.gravityFluxFes->GetFE(vacuum_element_id);
mfem::ElementTransformation &stellar_transformation = *m_fem.mesh->GetElementTransformation(stellar_element_id); mfem::ElementTransformation &stellar_transformation =
mfem::ElementTransformation &vacuum_transformation = *m_fem.mesh->GetElementTransformation(vacuum_element_id); *m_fem.mesh->GetElementTransformation(stellar_element_id);
mfem::ElementTransformation &vacuum_transformation =
*m_fem.mesh->GetElementTransformation(vacuum_element_id);
m_mass_form.reset(); m_stellar_mass_form.reset();
m_stellar_mass_coefficient.reset(); m_vacuum_mass_form.reset();
m_vacuum_mass_coefficient.reset(); m_stellar_mass_coefficient.reset();
m_vacuum_mass_coefficient.reset();
m_stellar_mass_coefficient = m_stellar_mass_coefficient =
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, false); std::make_unique<FrozenMappedHDivMassCoefficient>(
m_vacuum_mass_coefficient = m_fem, m_domain_mapper, m_displacement_true, false);
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, true); m_vacuum_mass_coefficient = std::make_unique<FrozenMappedHDivMassCoefficient>(
m_fem, m_domain_mapper, m_displacement_true, true);
m_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get()); m_stellar_mass_form =
m_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL); std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_vacuum_mass_form =
std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_stellar_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
m_vacuum_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto stellar_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*m_stellar_mass_coefficient); auto stellar_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*m_vacuum_mass_coefficient); *m_stellar_mass_coefficient);
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(
*m_vacuum_mass_coefficient);
m_fem.quadratureFactory->configure_gravity_hdiv_mass( m_fem.quadratureFactory->configure_gravity_hdiv_mass(
*stellar_integrator, quadrature::QuadratureRole::discretization, stellar_element, stellar_transformation, *stellar_integrator, quadrature::QuadratureRole::discretization,
utils::DOMAINS::STELLAR, quadrature::MappingKind::general stellar_element, stellar_transformation, utils::DOMAINS::STELLAR,
); quadrature::MappingKind::general);
m_fem.quadratureFactory->configure_gravity_hdiv_mass( m_fem.quadratureFactory->configure_gravity_hdiv_mass(
*vacuum_integrator, quadrature::QuadratureRole::discretization, vacuum_element, vacuum_transformation, *vacuum_integrator, quadrature::QuadratureRole::discretization,
utils::DOMAINS::VACUUM, quadrature::MappingKind::kelvin vacuum_element, vacuum_transformation, utils::DOMAINS::VACUUM,
); quadrature::MappingKind::kelvin);
m_mass_form->AddDomainIntegrator(stellar_integrator.release(), m_stellar_marker); m_stellar_mass_form->AddDomainIntegrator(stellar_integrator.release(),
m_mass_form->AddDomainIntegrator(vacuum_integrator.release(), m_vacuum_marker); m_stellar_marker);
m_mass_form->Assemble(); m_vacuum_mass_form->AddDomainIntegrator(vacuum_integrator.release(),
m_vacuum_marker);
m_stellar_mass_form->Assemble();
m_vacuum_mass_form->Assemble();
m_is_prepared = true; m_is_prepared = true;
++m_preparation_count; ++m_preparation_count;
} }
void PreparedMappedHDivMassOperator::Mult( void PreparedMappedHDivMassOperator::Mult(const mfem::Vector &gravity_gradient,
const mfem::Vector &gravity_gradient, mfem::Vector &action) const {
mfem::Vector &action MFEM_VERIFY(m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
) const { "before Mult is called.");
MFEM_VERIFY( MFEM_VERIFY(
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared " m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr,
"before Mult is called." "PreparedMappedHDivMassOperator has incomplete domain mass forms.");
); MFEM_VERIFY(
MFEM_VERIFY( gravity_gradient.Size() == Width(),
m_mass_form != nullptr, "PreparedMappedHDivMassOperator has no " "PreparedMappedHDivMassOperator received a gravity-gradient vector "
"assembled partial-assembly form." "with the wrong size.");
);
MFEM_VERIFY(
gravity_gradient.Size() == Width(), "PreparedMappedHDivMassOperator received a gravity-gradient vector "
"with the wrong size."
);
m_flux_true.SetSize(m_flux_map.full_size()); m_flux_true.SetSize(m_flux_map.full_size());
m_action_true.SetSize(m_flux_map.full_size()); m_action_true.SetSize(m_flux_map.full_size());
m_flux_map.scatter(gravity_gradient, m_flux_true); m_domain_action_true.SetSize(m_flux_map.full_size());
m_mass_form->Mult(m_flux_true, m_action_true); m_flux_map.scatter(gravity_gradient, m_flux_true);
action.SetSize(Height()); m_stellar_mass_form->Mult(m_flux_true, m_action_true);
m_flux_map.gather(m_action_true, action); m_vacuum_mass_form->Mult(m_flux_true, m_domain_action_true);
} m_action_true += m_domain_action_true;
action.SetSize(Height());
m_flux_map.gather(m_action_true, action);
}
bool PreparedMappedHDivMassOperator::IsPrepared() const noexcept { void PreparedMappedHDivMassOperator::AssembleDiagonal(
return m_is_prepared; mfem::Vector &diagonal) const {
} mfem::Vector true_diagonal;
AssembleTrueDiagonal(true_diagonal);
diagonal.SetSize(Height());
m_flux_map.gather(true_diagonal, diagonal);
}
std::uint64_t PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept { void PreparedMappedHDivMassOperator::AssembleTrueDiagonal(
return m_preparation_count; mfem::Vector &diagonal) const {
} MFEM_VERIFY(m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before assembling its diagonal.");
MFEM_VERIFY(
m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr,
"PreparedMappedHDivMassOperator has incomplete domain mass forms.");
const field::FieldDofMap &PreparedMappedHDivMassOperator::GetFluxMap() const noexcept { diagonal.SetSize(m_flux_map.full_size());
return m_flux_map; mfem::Vector domain_diagonal(m_flux_map.full_size());
} m_stellar_mass_form->AssembleDiagonal(diagonal);
m_vacuum_mass_form->AssembleDiagonal(domain_diagonal);
diagonal += domain_diagonal;
}
const field::FieldDofMap &PreparedMappedHDivMassOperator::GetDisplacementMap() const noexcept { bool PreparedMappedHDivMassOperator::IsPrepared() const noexcept {
return m_displacement_map; return m_is_prepared;
} }
std::uint64_t
PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
const field::FieldDofMap &
PreparedMappedHDivMassOperator::GetFluxMap() const noexcept {
return m_flux_map;
}
const field::FieldDofMap &
PreparedMappedHDivMassOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
} // namespace mean_field::operators } // namespace mean_field::operators

File diff suppressed because it is too large Load Diff

View File

@@ -213,7 +213,7 @@ namespace mean_field::operators {
PreparedPressureForceOperator::PreparedPressureForceOperator( PreparedPressureForceOperator::PreparedPressureForceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState const eos::Polytrope &equationOfState
) )
: PreparedPressureForceOperator( : PreparedPressureForceOperator(
@@ -226,7 +226,7 @@ namespace mean_field::operators {
PreparedPressureForceOperator::PreparedPressureForceOperator( PreparedPressureForceOperator::PreparedPressureForceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
ConstructionData constructionData ConstructionData constructionData
) )
@@ -460,7 +460,7 @@ namespace mean_field::operators {
true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal); true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal);
mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mfem::Vector elementDisplacement; mfem::Vector elementDisplacement;
mfem::Vector elementCompactification; mfem::Vector elementCompactification;
@@ -807,7 +807,7 @@ namespace mean_field::operators {
localAction = 0.0; localAction = 0.0;
mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mfem::Vector elementDisplacementVariation; mfem::Vector elementDisplacementVariation;
mfem::Vector elementAction; mfem::Vector elementAction;

View File

@@ -10,7 +10,7 @@ import :operators.prepared_rotational_displacement_force;
namespace mean_field::operators { namespace mean_field::operators {
PreparedRotationalDisplacementForceOperator::PreparedRotationalDisplacementForceOperator( PreparedRotationalDisplacementForceOperator::PreparedRotationalDisplacementForceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper const mapping::DomainMapper &domainMapper
) )
: m_fem(f), : m_fem(f),
m_domainMapper(domainMapper), m_domainMapper(domainMapper),

View File

@@ -9,28 +9,16 @@ module;
module mean_field; module mean_field;
import :operators.prepared_stellar_equilibrium; import :operators.prepared_stellar_equilibrium;
import :physics.gravity;
namespace { namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] mean_field::fem::FEM &ensure_gravity_static_operators(mean_field::fem::FEM &f) { void verify_coupled_discretization(const mean_field::fem::FEM &f) {
MFEM_VERIFY( MFEM_VERIFY(
f.mesh != nullptr && f.densityFes != nullptr && f.displacementFes != nullptr && f.mesh != nullptr && f.densityFes != nullptr && f.displacementFes != nullptr &&
f.gravityFluxFes != nullptr && f.gravityPotentialFes != nullptr && f.enthalpyFes != nullptr, f.gravityFluxFes != nullptr && f.gravityPotentialFes != nullptr && f.enthalpyFes != nullptr,
"PreparedStellarEquilibriumOperator requires the complete coupled finite-element discretization." "PreparedStellarEquilibriumOperator requires the complete coupled finite-element discretization."
); );
if (f.gravityContext.b_form == nullptr || f.gravityContext.BT == nullptr) {
mean_field::physics::update_stiffness_matrix(f);
}
MFEM_VERIFY(
f.gravityContext.b_form != nullptr && f.gravityContext.BT != nullptr,
"PreparedStellarEquilibriumOperator could not initialize the static gravity divergence operators."
);
return f;
} }
[[nodiscard]] mean_field::operators::StellarEquilibriumLayout make_layout( [[nodiscard]] mean_field::operators::StellarEquilibriumLayout make_layout(
@@ -318,13 +306,13 @@ namespace mean_field::operators {
PreparedStellarEquilibriumOperator::ConstructionData PreparedStellarEquilibriumOperator::ConstructionData
PreparedStellarEquilibriumOperator::MakeConstructionData(fem::FEM &f) { PreparedStellarEquilibriumOperator::MakeConstructionData(fem::FEM &f) {
ensure_gravity_static_operators(f); verify_coupled_discretization(f);
return ConstructionData(f); return ConstructionData(f);
} }
PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator( PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const models::StellarModel &stellarModel const models::StellarModel &stellarModel
) )
@@ -338,7 +326,7 @@ namespace mean_field::operators {
PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator( PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const double targetMass const double targetMass
) )
@@ -353,7 +341,7 @@ namespace mean_field::operators {
PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator( PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const double targetMass, const double targetMass,
ConstructionData constructionData ConstructionData constructionData
@@ -587,8 +575,7 @@ namespace mean_field::operators {
); );
assign_residual_block( assign_residual_block(
m_cachedResidual, m_layout, enthalpyResidual, hydrostatic, m_cachedResidual, m_layout, enthalpyResidual, hydrostatic, "The hydrostatic residual has the wrong size."
"The hydrostatic residual has the wrong size."
); );
assign_residual_block( assign_residual_block(
@@ -712,8 +699,7 @@ namespace mean_field::operators {
); );
assign_residual_block( assign_residual_block(
action, m_layout, enthalpyResidual, hydrostaticAction, action, m_layout, enthalpyResidual, hydrostaticAction, "The hydrostatic Jacobian action has the wrong size."
"The hydrostatic Jacobian action has the wrong size."
); );
assign_residual_block( assign_residual_block(

View File

@@ -2,171 +2,10 @@ module;
#include "mfem.hpp" #include "mfem.hpp"
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <format>
#include <source_location>
#include <string_view>
#include <unordered_map>
module mean_field; module mean_field;
import :mapping.coefficients;
import :analysis.integral;
namespace {
double centrifugal_potential(
const mfem::Vector &phys_x,
const double omega
) {
const double s2 = std::pow(phys_x(0), 2) + std::pow(phys_x(1), 2);
return -0.5 * s2 * std::pow(omega, 2);
}
void grid_function_to_true_dofs(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::GridFunction &grid_function,
mfem::Vector &true_dofs
) {
MFEM_VERIFY(
grid_function.Size() == finite_element_space.GetVSize(),
"The grid function does not match the requested finite-element "
"space."
);
true_dofs.SetSize(finite_element_space.GetTrueVSize());
const mfem::Operator *restriction = finite_element_space.GetRestrictionMatrix();
if (restriction != nullptr) {
restriction->Mult(grid_function, true_dofs);
} else {
MFEM_VERIFY(
grid_function.Size() == true_dofs.Size(), "A finite-element space without a restriction operator must "
"have "
"matching local and true sizes."
);
true_dofs = grid_function;
}
}
} // namespace
namespace mean_field::physics { namespace mean_field::physics {
GravitySolution grav_potential(
fem::FEM &f,
const utils::Args &args,
const mfem::GridFunction &rho,
const bool phi_warm
) {
MFEM_VERIFY(
f.densityFes != nullptr && rho.FESpace() == f.densityFes.get(),
"Gravity solve requires rho to use the registered density space."
);
MFEM_VERIFY(f.gravityPotentialFes != nullptr, "Gravity solve requires the registered gravity-potential space.");
mfem::Array<int> outer_bdr_marker(f.mesh->bdr_attributes.Max());
outer_bdr_marker = 0;
outer_bdr_marker[1] = 1;
mfem::ParLinearForm g_rhs(f.gravityFluxFes.get());
// ReSharper disable once CppTooWideScope
std::unique_ptr<mfem::Coefficient> boundary_potential_coeff;
if (!f.has_mapping()) { // We only need to explicitly add a boundary
// integrator if a mapping is not being used. In
// the case where the outer domain has been
// compactified the φ=0 boundary condition is
// the natural condition and MFEM automatically
// handles this
auto boundary_potential = [&f](const mfem::Vector &x_physical) {
return l2_multipole_potential(f, utils::MASS, x_physical);
};
boundary_potential_coeff = std::make_unique<mfem::FunctionCoefficient>(boundary_potential);
auto boundary_integrator =
std::make_unique<mfem::VectorFEBoundaryFluxLFIntegrator>(*boundary_potential_coeff);
const mfem::FiniteElement &boundary_element = *f.gravityFluxFes->GetTypicalTraceElement();
f.quadratureFactory->configure_gravity_boundary(
*boundary_integrator, quadrature::QuadratureRole::discretization, boundary_element,
utils::DOMAINS::VACUUM, quadrature::MappingKind::none
);
g_rhs.AddBoundaryIntegrator(boundary_integrator.release(), outer_bdr_marker);
}
g_rhs.Assemble();
mfem::GridFunctionCoefficient rho_coeff(&rho);
mfem::ConstantCoefficient G4pi(4.0 * M_PI * utils::G);
mfem::ProductCoefficient source_coeff(G4pi, rho_coeff);
mfem::ParLinearForm f_rhs(f.gravityPotentialFes.get());
std::unique_ptr<mfem::Coefficient> mapped_source_coeff;
mfem::Coefficient *active_source_coeff = &source_coeff;
quadrature::MappingKind source_mapping_kind = quadrature::MappingKind::none;
if (f.has_mapping()) {
mapped_source_coeff = std::make_unique<mapping::MappedScalarCoefficient>(*f.mapping, source_coeff);
active_source_coeff = mapped_source_coeff.get();
source_mapping_kind = quadrature::MappingKind::general;
}
auto source_integrator = std::make_unique<mfem::DomainLFIntegrator>(*active_source_coeff);
const mfem::FiniteElement &source_test_element = *f.gravityPotentialFes->GetTypicalFE();
const mfem::ElementTransformation &source_transformation = *f.mesh->GetElementTransformation(0);
const int source_coefficient_order = f.densityFes->GetMaxElementOrder();
f.quadratureFactory->configure_gravity_source(
*source_integrator, quadrature::QuadratureRole::discretization, source_test_element, source_transformation,
source_coefficient_order, utils::DOMAINS::STELLAR, source_mapping_kind
);
f_rhs.AddDomainIntegrator(source_integrator.release(), f.gravityContext.stellar_mask);
f_rhs.Assemble();
mfem::BlockVector RHS(f.gravityBlockTrueOffsets);
RHS.GetBlock(0) = *g_rhs.ParallelAssemble();
RHS.GetBlock(1) = *f_rhs.ParallelAssemble();
mfem::BlockVector X(f.gravityBlockTrueOffsets);
X = 0.0;
f.gravityContext.minres->SetOperator(*f.gravityContext.block_A);
f.gravityContext.minres->Mult(RHS, X);
GravitySolution solution(f);
solution.gradPhi.SetFromTrueDofs(X.GetBlock(0));
solution.phi.SetFromTrueDofs(X.GetBlock(1));
return solution;
}
mfem::GridFunction get_potential(
fem::FEM &fem,
const utils::Args &args,
const mfem::GridFunction &rho,
const bool warm
) {
auto phi = grav_potential(fem, args, rho, warm);
if (args.r.enabled) {
auto rot = [&fem, &args](const mfem::Vector &x) {
mfem::Vector rel_x = x;
rel_x -= fem.com;
return centrifugal_potential(rel_x, args.r.omega);
};
std::unique_ptr<mfem::Coefficient> centrifugal_coeff;
if (fem.has_mapping()) {
centrifugal_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, rot);
} else {
centrifugal_coeff = std::make_unique<mfem::FunctionCoefficient>(rot);
}
mfem::GridFunction centrifugal_gf(fem.gravityPotentialFes.get());
centrifugal_gf.ProjectCoefficient(*centrifugal_coeff);
phi.phi += centrifugal_gf;
}
return phi.phi;
}
mfem::DenseMatrix compute_quadrupole_moment_tensor( mfem::DenseMatrix compute_quadrupole_moment_tensor(
const fem::FEM &fem, const fem::FEM &fem,
const mfem::GridFunction &rho, const mfem::GridFunction &rho,
@@ -175,9 +14,15 @@ namespace mean_field::physics {
const int dim = fem.mesh->Dimension(); const int dim = fem.mesh->Dimension();
mfem::DenseMatrix local_Q(dim, dim); mfem::DenseMatrix local_Q(dim, dim);
local_Q = 0.0; local_Q = 0.0;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
);
for (int i = 0; i < fem.mesh->GetNE(); ++i) { for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3) if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(
fem.mesh->GetAttribute(i)))
continue; continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i); mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
@@ -193,20 +38,17 @@ namespace mean_field::physics {
const mfem::IntegrationPoint &ip = ir.IntPoint(j); const mfem::IntegrationPoint &ip = ir.IntPoint(j);
trans->SetIntPoint(&ip); trans->SetIntPoint(&ip);
double weight = trans->Weight() * ip.weight; mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY(
if (fem.has_mapping()) { mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) ==
weight *= fem.mapping->ComputeDetJ(*trans, ip); mapping::MappingStatus::valid,
} "Quadrupole integration encountered an invalid mapping."
);
const double weight = mapping_context.quadrature.weight;
const double rho_val = rho.GetValue(i, ip); const double rho_val = rho.GetValue(i, ip);
mfem::Vector phys_point(dim); const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
if (fem.has_mapping()) {
fem.mapping->GetPhysicalPoint(*trans, ip, phys_point);
} else {
trans->Transform(ip, phys_point);
}
mfem::Vector x_prime(dim); mfem::Vector x_prime(dim);
double r_sq = 0.0; double r_sq = 0.0;
@@ -261,141 +103,7 @@ namespace mean_field::physics {
return l0_contrib + l2_contrib; return l0_contrib + l2_contrib;
} }
void update_stiffness_matrix(fem::FEM &f) { GravitySolution solve_gravity_field(
mfem::Array<int> empty_tdofs;
// ==========================================
// 1. Partially Assemble the High-Order Mass Block
// ==========================================
f.gravityContext.m_form = std::make_unique<mfem::ParBilinearForm>(f.gravityFluxFes.get());
f.gravityContext.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
std::unique_ptr<mfem::VectorFEMassIntegrator> hdiv_mass_integrator;
if (f.has_mapping()) {
f.gravityContext.mapped_hdiv_mass_coeff =
std::make_unique<mapping::MappedHDivMassCoefficient>(*f.mapping, f.mesh->Dimension());
hdiv_mass_integrator =
std::make_unique<mfem::VectorFEMassIntegrator>(*f.gravityContext.mapped_hdiv_mass_coeff);
} else {
f.gravityContext.mapped_hdiv_mass_coeff.reset();
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>();
}
const mfem::FiniteElement &hdiv_element = *f.gravityFluxFes->GetTypicalFE();
const mfem::ElementTransformation &hdiv_transformation = *f.mesh->GetElementTransformation(0);
const quadrature::MappingKind mapping_kind =
f.has_mapping() ? quadrature::MappingKind::general : quadrature::MappingKind::none;
f.quadratureFactory->configure_gravity_hdiv_mass(
*hdiv_mass_integrator, quadrature::QuadratureRole::discretization, hdiv_element, hdiv_transformation,
utils::DOMAINS::ALL, mapping_kind
);
f.gravityContext.m_form->AddDomainIntegrator(hdiv_mass_integrator.release());
f.gravityContext.m_form->Assemble();
// ==========================================
// 2. Partially Assemble the High-Order Divergence Block
// ==========================================
f.gravityContext.b_form =
std::make_unique<mfem::ParMixedBilinearForm>(f.gravityFluxFes.get(), f.gravityPotentialFes.get());
f.gravityContext.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto divergence_discretization_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement &divergence_discretization_test_element = *f.gravityPotentialFes->GetTypicalFE();
f.quadratureFactory->configure_gravity_divergence(
*divergence_discretization_integrator, quadrature::QuadratureRole::discretization, hdiv_element,
divergence_discretization_test_element, hdiv_transformation, utils::DOMAINS::ALL,
quadrature::MappingKind::none
);
f.gravityContext.b_form->AddDomainIntegrator(divergence_discretization_integrator.release());
f.gravityContext.b_form->Assemble();
MFEM_VERIFY(
f.domainMapperStateless != nullptr, "Gravity source partial assembly requires the stateless domain "
"mapper."
);
mfem::Vector displacement_true(f.displacementFes->GetTrueVSize());
displacement_true = 0.0;
const mfem::GridFunction *active_displacement = f.mapping->GetDisplacement();
if (active_displacement != nullptr) {
grid_function_to_true_dofs(*f.displacementFes, *active_displacement, displacement_true);
}
auto source_form =
std::make_unique<operators::PreparedMappedGravitySourceOperator>(f, *f.domainMapperStateless);
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const field::FieldDofMap displacement_map =
field::make_field_dof_map<field::Displacement, DomainSchema>(*f.displacementFes);
source_form->Prepare(displacement_map.gather(displacement_true));
f.gravityContext.source_form = std::move(source_form);
// ==========================================
// 3. Assemble Global Block Operator
// ==========================================
f.gravityContext.BT = std::make_unique<mfem::TransposeOperator>(f.gravityContext.b_form.get());
f.gravityContext.block_A = std::make_unique<mfem::BlockOperator>(f.gravityBlockTrueOffsets);
f.gravityContext.block_A->SetBlock(0, 0, f.gravityContext.m_form.get());
f.gravityContext.block_A->SetBlock(0, 1, f.gravityContext.BT.get());
f.gravityContext.block_A->SetBlock(1, 0, f.gravityContext.b_form.get());
// ==========================================
// 4. Construct a mapped Schur preconditioner
// ==========================================
mfem::Vector mass_diagonal(f.gravityFluxFes->GetTrueVSize());
f.gravityContext.m_form->AssembleDiagonal(mass_diagonal);
mfem::Vector inverse_mass_diagonal(mass_diagonal);
for (int i = 0; i < inverse_mass_diagonal.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(inverse_mass_diagonal(i)) && inverse_mass_diagonal(i) > 0.0,
"Mapped RT mass matrix has a non-positive or non-finite "
"diagonal "
"entry."
);
inverse_mass_diagonal(i) = 1.0 / inverse_mass_diagonal(i);
}
mfem::ParMixedBilinearForm b_preconditioner(f.gravityFluxFes.get(), f.gravityPotentialFes.get());
auto divergence_preconditioner_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement &divergence_trial_element = *f.gravityFluxFes->GetTypicalFE();
const mfem::FiniteElement &divergence_test_element = *f.gravityPotentialFes->GetTypicalFE();
const mfem::ElementTransformation &divergence_transformation = *f.mesh->GetElementTransformation(0);
f.quadratureFactory->configure_gravity_divergence(
*divergence_preconditioner_integrator, quadrature::QuadratureRole::preconditioner, divergence_trial_element,
divergence_test_element, divergence_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none
);
b_preconditioner.AddDomainIntegrator(divergence_preconditioner_integrator.release());
b_preconditioner.Assemble();
b_preconditioner.Finalize();
std::unique_ptr<mfem::HypreParMatrix> b_matrix(b_preconditioner.ParallelAssemble());
std::unique_ptr<mfem::HypreParMatrix> inverse_mass_b_transpose(b_matrix->Transpose());
inverse_mass_b_transpose->ScaleRows(inverse_mass_diagonal);
f.gravityContext.Schur.reset(mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get()));
// ==========================================
// 5. Wire Up the preconditioners
// ==========================================
f.gravityContext.prec_M = std::make_unique<mfem::OperatorJacobiSmoother>(mass_diagonal, empty_tdofs);
f.gravityContext.prec_Phi->SetOperator(*f.gravityContext.Schur);
f.gravityContext.block_prec->SetDiagonalBlock(0, f.gravityContext.prec_M.get());
f.gravityContext.block_prec->SetDiagonalBlock(1, f.gravityContext.prec_Phi.get());
}
GravitySolution grav_potential_new(
fem::FEM &f, fem::FEM &f,
const utils::Args &args, const utils::Args &args,
const mfem::GridFunction &rho, const mfem::GridFunction &rho,
@@ -417,13 +125,6 @@ namespace mean_field::physics {
"displacement finite-element space." "displacement finite-element space."
); );
MFEM_VERIFY(f.domainMapperStateless != nullptr, "Gravity initialization requires the stateless domain mapper."); MFEM_VERIFY(f.domainMapperStateless != nullptr, "Gravity initialization requires the stateless domain mapper.");
MFEM_VERIFY(f.gravityContext.b_form != nullptr, "Gravity initialization requires the divergence operator.");
MFEM_VERIFY(
f.gravityContext.BT != nullptr, "Gravity initialization requires the transpose divergence operator."
);
MFEM_VERIFY(
f.gravityContext.block_prec != nullptr, "Gravity initialization requires the gravity block preconditioner."
);
MFEM_VERIFY( MFEM_VERIFY(
rho.FESpace() == f.densityFes.get(), "Gravity initialization requires density to use the FEM density " rho.FESpace() == f.densityFes.get(), "Gravity initialization requires density to use the FEM density "
"space." "space."
@@ -434,6 +135,7 @@ namespace mean_field::physics {
"Vec_H1 " "Vec_H1 "
"space." "space."
); );
MFEM_VERIFY(args.p.max_iters > 0, "Gravity solve requires a positive MINRES iteration limit.");
using form = utils::blocks::gravity_field_form; using form = utils::blocks::gravity_field_form;
@@ -444,13 +146,19 @@ namespace mean_field::physics {
utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.poisson_term); utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.poisson_term);
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema; using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const field::FieldDofMap density_map = field::make_field_dof_map<field::Density, DomainSchema>(*f.densityFes); const field::FieldDofGridFunctionAdapter density_adapter =
const field::FieldDofMap displacement_map = field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*f.densityFes);
field::make_field_dof_map<field::Displacement, DomainSchema>(*f.displacementFes); const field::FieldDofGridFunctionAdapter displacement_adapter =
const field::FieldDofMap gravity_flux_map = field::make_field_dof_grid_function_adapter<field::Displacement, DomainSchema>(*f.displacementFes);
field::make_field_dof_map<field::Gravity, DomainSchema>(*f.gravityFluxFes); const field::FieldDofGridFunctionAdapter gravity_flux_adapter =
const field::FieldDofMap gravity_potential_map = field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(*f.gravityFluxFes);
field::make_field_dof_map<field::Gravity, DomainSchema>(*f.gravityPotentialFes); const field::FieldDofGridFunctionAdapter gravity_potential_adapter =
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(*f.gravityPotentialFes);
const field::FieldDofMap &density_map = density_adapter.dof_map();
const field::FieldDofMap &displacement_map = displacement_adapter.dof_map();
const field::FieldDofMap &gravity_flux_map = gravity_flux_adapter.dof_map();
const field::FieldDofMap &gravity_potential_map = gravity_potential_adapter.dof_map();
const std::array<int, form::value_block_count> value_sizes{ const std::array<int, form::value_block_count> value_sizes{
density_map.reduced_size(), displacement_map.reduced_size(), gravity_flux_map.reduced_size(), density_map.reduced_size(), displacement_map.reduced_size(), gravity_flux_map.reduced_size(),
@@ -463,14 +171,8 @@ namespace mean_field::physics {
const utils::blocks::form_layout<form> layout(value_sizes, residual_sizes); const utils::blocks::form_layout<form> layout(value_sizes, residual_sizes);
mfem::Vector density_true; const mfem::Vector density = density_adapter.gather(rho);
mfem::Vector displacement_true; const mfem::Vector reduced_displacement = displacement_adapter.gather(displacement);
grid_function_to_true_dofs(*f.densityFes, rho, density_true);
grid_function_to_true_dofs(*f.displacementFes, displacement, displacement_true);
const mfem::Vector density = density_map.gather(density_true);
const mfem::Vector reduced_displacement = displacement_map.gather(displacement_true);
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
f, *f.domainMapperStateless f, *f.domainMapperStateless
@@ -491,6 +193,7 @@ namespace mean_field::physics {
operators::ReducedGravityFieldOperator reduced_operator( operators::ReducedGravityFieldOperator reduced_operator(
gravity_operator, reduced_geometry_context, reduced_displacement gravity_operator, reduced_geometry_context, reduced_displacement
); );
operators::ReducedGravityFieldPreconditioner reduced_preconditioner(f, reduced_geometry_context);
mfem::Vector right_hand_side; mfem::Vector right_hand_side;
reduced_operator.BuildRightHandSide(density, right_hand_side); reduced_operator.BuildRightHandSide(density, right_hand_side);
@@ -505,25 +208,24 @@ namespace mean_field::physics {
mfem::MINRESSolver minres(f.mesh->GetComm()); mfem::MINRESSolver minres(f.mesh->GetComm());
minres.SetOperator(reduced_operator); minres.SetOperator(reduced_operator);
minres.SetPreconditioner(*f.gravityContext.block_prec); minres.SetPreconditioner(reduced_preconditioner);
minres.SetRelTol(args.p.rtol); minres.SetRelTol(args.p.rtol);
minres.SetAbsTol(args.p.atol); minres.SetAbsTol(args.p.atol);
minres.SetMaxIter(args.p.max_iters); minres.SetMaxIter(args.p.max_iters);
minres.SetPrintLevel(1); // minres.SetPrintLevel(args.verbose ? 1 : 0);
minres.SetPrintLevel(0);
minres.Mult(right_hand_side, gravity_state); minres.Mult(right_hand_side, gravity_state);
MFEM_VERIFY(minres.GetConverged(), "The reduced gravity solve failed to converge."); MFEM_VERIFY(minres.GetConverged(), "The reduced gravity solve failed to converge.");
GravitySolution solution(f); GravitySolution solution(f);
const mfem::Vector gravity_flux_true = gravity_flux_adapter.scatter(
gravity_flux_map.scatter(gravity_state.GetBlock(gravity_gradient_residual_block)); gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi
const mfem::Vector gravity_potential_true = );
gravity_potential_map.scatter(gravity_state.GetBlock(gravity_poisson_residual_block)); gravity_potential_adapter.scatter(
gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi
solution.gradPhi.SetFromTrueDofs(gravity_flux_true); );
solution.phi.SetFromTrueDofs(gravity_potential_true);
return solution; return solution;
} }

View File

@@ -10,9 +10,15 @@ namespace mean_field::physics {
const mfem::GridFunction &rho_ref const mfem::GridFunction &rho_ref
) { ) {
double local_I = 0.0; double local_I = 0.0;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
);
for (int i = 0; i < fem.mesh->GetNE(); i++) { for (int i = 0; i < fem.mesh->GetNE(); i++) {
if (fem.mesh->GetAttribute(i) == 3) if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(
fem.mesh->GetAttribute(i)))
continue; continue;
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i); mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i);
@@ -29,12 +35,16 @@ namespace mean_field::physics {
const double rho_hat = rho_ref.GetValue(i, ip); const double rho_hat = rho_ref.GetValue(i, ip);
mfem::Vector x_phys; mapping::VolumeMappingContext mapping_context;
fem.mapping->GetPhysicalPoint(*T, ip, x_phys); MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*T, ip, mapping_context) ==
mapping::MappingStatus::valid,
"Moment-of-inertia integration encountered an invalid mapping."
);
const mfem::Vector &x_phys = mapping_context.mapping.physical_position;
const double r_cyl_sq = x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1); const double r_cyl_sq = x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1);
const double detJ = std::fabs(fem.mapping->ComputeDetJ(*T, ip)); const double weight = mapping_context.quadrature.weight;
const double weight = T->Weight() * ip.weight * detJ;
local_I += rho_hat * r_cyl_sq * weight; local_I += rho_hat * r_cyl_sq * weight;
} }

View File

@@ -12,6 +12,10 @@ namespace mean_field::utils {
) { ) {
const int dim = fem.mesh->Dimension(); const int dim = fem.mesh->Dimension();
x_ref = x_phys_target; x_ref = x_phys_target;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
);
mfem::Array<int> init_elem; mfem::Array<int> init_elem;
mfem::Array<mfem::IntegrationPoint> init_ip; mfem::Array<mfem::IntegrationPoint> init_ip;
@@ -29,15 +33,18 @@ namespace mean_field::utils {
mfem::Array<mfem::IntegrationPoint> origin_ip; mfem::Array<mfem::IntegrationPoint> origin_ip;
fem.mesh->FindPoints(P_origin, origin_elem, origin_ip, false); fem.mesh->FindPoints(P_origin, origin_elem, origin_ip, false);
if (origin_elem.Size() > 0 && origin_elem[0] >= 0 && fem.mapping->HasDisplacementField()) { if (origin_elem.Size() > 0 && origin_elem[0] >= 0) {
mfem::ElementTransformation *T0 = fem.mesh->GetElementTransformation(origin_elem[0]); mfem::ElementTransformation *T0 = fem.mesh->GetElementTransformation(origin_elem[0]);
T0->SetIntPoint(&origin_ip[0]); T0->SetIntPoint(&origin_ip[0]);
mfem::DenseMatrix J0(dim, dim), J0_inv(dim, dim); mapping::MappingPointContext context;
fem.mapping->ComputeJacobian(*T0, J0); MFEM_VERIFY(
mfem::CalcInverse(J0, J0_inv); mapping_evaluator.EvaluatePoint(*T0, origin_ip[0], context) ==
mapping::MappingStatus::valid,
"Reference-point initialization encountered an invalid mapping."
);
J0_inv.Mult(x_phys_target, x_ref); context.inverse_mapping_jacobian.Mult(x_phys_target, x_ref);
} }
init_P.SetCol(0, x_ref); init_P.SetCol(0, x_ref);
@@ -70,9 +77,6 @@ namespace mean_field::utils {
mfem::Vector residual(dim); mfem::Vector residual(dim);
mfem::Vector step(dim); mfem::Vector step(dim);
mfem::DenseMatrix J_map(dim, dim);
mfem::DenseMatrix J_map_inv(dim, dim);
int find_failures = 0; int find_failures = 0;
for (int iter = 0; iter < max_iter; ++iter) { for (int iter = 0; iter < max_iter; ++iter) {
@@ -99,8 +103,12 @@ namespace mean_field::utils {
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(elemID); mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(elemID);
T->SetIntPoint(&ip); T->SetIntPoint(&ip);
mfem::Vector current_x_phys(dim); mapping::MappingPointContext context;
fem.mapping->GetPhysicalPoint(*T, ip, current_x_phys); if (mapping_evaluator.EvaluatePoint(*T, ip, context) !=
mapping::MappingStatus::valid) {
return false;
}
const mfem::Vector &current_x_phys = context.physical_position;
for (int i = 0; i < dim; ++i) { for (int i = 0; i < dim; ++i) {
residual(i) = current_x_phys(i) - x_phys_target(i); residual(i) = current_x_phys(i) - x_phys_target(i);
@@ -110,9 +118,7 @@ namespace mean_field::utils {
return true; return true;
} }
fem.mapping->ComputeJacobian(*T, J_map); context.inverse_mapping_jacobian.Mult(residual, step);
mfem::CalcInverse(J_map, J_map_inv);
J_map_inv.Mult(residual, step);
double alpha = 1.0; double alpha = 1.0;
mfem::Vector x_ref_candidate(dim); mfem::Vector x_ref_candidate(dim);

View File

@@ -1,123 +1,24 @@
module; module;
#include <expected>
#include <mfem.hpp> #include <mfem.hpp>
module mean_field; module mean_field;
import :boundary.contexts;
namespace mean_field::utils { namespace mean_field::utils {
DOMAINS operator|( DOMAINS operator|(DOMAINS lhs, DOMAINS rhs) {
DOMAINS lhs, return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) |
DOMAINS rhs static_cast<uint8_t>(rhs));
) { }
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
}
DOMAINS operator&( DOMAINS operator&(DOMAINS lhs, DOMAINS rhs) {
DOMAINS lhs, return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) &
DOMAINS rhs static_cast<uint8_t>(rhs));
) { }
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
}
void populate_element_mask( int get_mesh_order(const mfem::Mesh &mesh) {
const mfem::Mesh *mesh, if (mesh.GetNodes() != nullptr) {
const DOMAINS domain, return mesh.GetNodes()->FESpace()->GetMaxElementOrder();
mfem::Array<int> &mask }
) { return 1;
const int max_attr = mesh->attributes.Max(); }
mask.SetSize(max_attr);
mask = 0;
if ((domain & DOMAINS::CORE) == DOMAINS::CORE && max_attr >= 1) { } // namespace mean_field::utils
mask[0] = 1;
}
if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE && max_attr >= 2) {
mask[1] = 1;
}
if ((domain & DOMAINS::VACUUM) == DOMAINS::VACUUM && max_attr >= 3) {
mask[2] = 1;
}
}
void populate_domain_tdofs(
const mfem::ParFiniteElementSpace *fes,
const mfem::Array<int> &element_mask,
mfem::Array<int> &ess_tdof
) {
mfem::Array<int> vdof_marker(fes->GetVSize());
vdof_marker = 0;
for (int i = 0; i < fes->GetMesh()->GetNE(); i++) {
const int attr = fes->GetMesh()->GetAttribute(i);
if (element_mask[attr - 1]) {
mfem::Array<int> dofs;
fes->GetElementVDofs(i, dofs);
for (int j = 0; j < dofs.Size(); j++) {
int index = dofs[j];
if (index < 0)
index = -1 - index;
vdof_marker[index] = 1;
}
}
}
fes->MarkerToList(vdof_marker, ess_tdof);
}
std::expected<
boundary::Bounds,
boundary::BoundsError>
discover_bounds(
const mfem::Mesh *mesh,
const int vacuum_attr
) {
double local_min_r = std::numeric_limits<double>::max();
double local_max_r = -std::numeric_limits<double>::max();
bool found_vacuum = false;
for (int i = 0; i < mesh->GetNE(); ++i) {
if (mesh->GetAttribute(i) == vacuum_attr) {
found_vacuum = true;
mfem::Array<int> vertices;
mesh->GetElementVertices(i, vertices);
for (const int v : vertices) {
const double *coords = mesh->GetVertex(v);
double r = std::sqrt(coords[0] * coords[0] + coords[1] * coords[1] + coords[2] * coords[2]);
local_min_r = std::min(local_min_r, r);
local_max_r = std::max(local_max_r, r);
}
}
}
double global_min_r, global_max_r;
int global_found_vacuum;
int l_found = found_vacuum ? 1 : 0;
MPI_Comm comm = MPI_COMM_WORLD;
if (const auto *pmesh = dynamic_cast<const mfem::ParMesh *>(mesh)) {
comm = pmesh->GetComm();
}
MPI_Allreduce(&local_min_r, &global_min_r, 1, MPI_DOUBLE, MPI_MIN, comm);
MPI_Allreduce(&local_max_r, &global_max_r, 1, MPI_DOUBLE, MPI_MAX, comm);
MPI_Allreduce(&l_found, &global_found_vacuum, 1, MPI_INT, MPI_MAX, comm);
if (global_found_vacuum) {
return boundary::Bounds(global_min_r, global_max_r);
}
return std::unexpected(boundary::BoundsError::CANNOT_FIND_VACUUM);
}
int get_mesh_order(const mfem::Mesh &mesh) {
if (mesh.GetNodes() != nullptr) {
return mesh.GetNodes()->FESpace()->GetMaxElementOrder();
}
return 1;
}
} // namespace mean_field::utils

View File

@@ -121,7 +121,7 @@ export namespace mean_field::eos {
std::pow(density, 1.0 / m_polytropic_index); std::pow(density, 1.0 / m_polytropic_index);
} }
[[nodiscard]] double enthalpy_from_pressure(double pressure) const override { [[nodiscard]] double enthalpy_from_pressure(const double pressure) const override {
validate_nonnegativity(pressure, "pressure"); validate_nonnegativity(pressure, "pressure");
const double np1 = m_polytropic_index + 1; const double np1 = m_polytropic_index + 1;
return np1 * std::pow(m_polytropic_constant, m_polytropic_index / np1) * std::pow(pressure, 1.0 / np1); return np1 * std::pow(m_polytropic_constant, m_polytropic_index / np1) * std::pow(pressure, 1.0 / np1);
@@ -159,12 +159,8 @@ export namespace mean_field::eos {
); );
} }
} }
public:
private:
double m_polytropic_index; double m_polytropic_index;
double m_polytropic_constant; double m_polytropic_constant;
double m_enthalpy_scale; double m_enthalpy_scale;
}; };
} // namespace mean_field::eos } // namespace mean_field::eos

View File

@@ -8,7 +8,6 @@ module;
export module mean_field:fem; export module mean_field:fem;
export import :physics.contexts;
export import :boundary.contexts; export import :boundary.contexts;
export import :mapping.domain_mapper; export import :mapping.domain_mapper;
export import :utils.misc; export import :utils.misc;
@@ -92,38 +91,9 @@ export namespace mean_field::fem {
// ===================================================================== // =====================================================================
// Domain mapping // Domain mapping
//
// These are declared after displacement so that they are destroyed
// before the displacement grid function to which mapping may refer.
// DomainMapper is retained only for legacy integrators. New operators
// use DomainMapperStateless exclusively.
// ===================================================================== // =====================================================================
std::unique_ptr<mapping::DomainMapper> mapping; std::unique_ptr<mapping::DomainMapper> domainMapperStateless;
std::unique_ptr<mapping::DomainMapperStateless> domainMapperStateless;
// =====================================================================
// Block layouts
//
// These arrays are retained only for legacy code. Canonical operator
// layouts are defined by the compile-time forms in :utils.blocks.
//
// Main system: [Displacement | Density]
// Gravity system: [Flux | Potential]
// =====================================================================
mfem::Array<int> blockTrueOffsets;
mfem::Array<int> gravityBlockTrueOffsets;
// =====================================================================
// Boundary conditions and domain masks
// =====================================================================
mfem::Array<int> essentialDisplacementTdofs;
mfem::Array<int> vacuumDensityTdofs;
mfem::Array<int> vacuumEnthalpyTdofs;
mfem::Array<int> vacuumDisplacementTdofs;
// ===================================================================== // =====================================================================
// Global diagnostics // Global diagnostics
@@ -133,10 +103,9 @@ export namespace mean_field::fem {
mfem::DenseMatrix Q; mfem::DenseMatrix Q;
// ===================================================================== // =====================================================================
// Physics and boundary contexts // Boundary context
// ===================================================================== // =====================================================================
physics::GravityContext gravityContext;
boundary::BoundaryContext boundaryContext; boundary::BoundaryContext boundaryContext;
std::unique_ptr<quadrature::RuleFactory> quadratureFactory; std::unique_ptr<quadrature::RuleFactory> quadratureFactory;
@@ -160,13 +129,11 @@ export namespace mean_field::fem {
compactificationFec != nullptr && compactificationFes != nullptr && compactificationFec != nullptr && compactificationFes != nullptr &&
compactificationCoordinate != nullptr && compactificationCoordinate != nullptr &&
mapping != nullptr && domainMapperStateless != nullptr && quadratureFactory != nullptr && domainMapperStateless != nullptr && quadratureFactory != nullptr;
blockTrueOffsets.Size() == 3 && gravityBlockTrueOffsets.Size() == 3;
} }
[[nodiscard]] bool has_mapping() const { [[nodiscard]] bool has_mapping() const {
return mapping != nullptr; return domainMapperStateless != nullptr && displacement != nullptr && compactificationCoordinate != nullptr;
} }
}; };

View File

@@ -5,6 +5,7 @@ module;
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <utility>
#include <mfem.hpp> #include <mfem.hpp>
@@ -887,6 +888,110 @@ export namespace mean_field::field {
mfem::Array<int> m_trueToReduced; mfem::Array<int> m_trueToReduced;
}; };
/*
* Canonical adapter between an MFEM GridFunction and a reduced field
* vector.
*
* FieldDofMap deliberately contains only indexing information. This
* adapter binds that indexing to the exact finite-element space whose true
* DOFs the map describes. Consequently, a grid function from another
* finite-element space is rejected even when it happens to have the same
* vector size.
*
* The finite-element space must outlive the adapter.
*/
class FieldDofGridFunctionAdapter {
public:
FieldDofGridFunctionAdapter(
FieldDofMap dofMap,
const mfem::FiniteElementSpace &finiteElementSpace
)
: m_dofMap(std::move(dofMap)),
m_finiteElementSpace(&finiteElementSpace) {
if (m_dofMap.full_size() != finiteElementSpace.GetTrueVSize()) {
throw std::invalid_argument(
"FieldDofGridFunctionAdapter map and finite-element "
"space have incompatible true-DOF sizes."
);
}
}
[[nodiscard]]
const FieldDofMap &dof_map() const noexcept {
return m_dofMap;
}
[[nodiscard]]
const mfem::FiniteElementSpace &finite_element_space() const noexcept {
return *m_finiteElementSpace;
}
/*
* Gather the grid function's true DOFs into reduced field ordering.
* The output vector is not resized so MFEM vector views remain valid.
*/
void gather(
const mfem::GridFunction &gridFunction,
mfem::Vector &reduced
) const {
validate_grid_function(gridFunction);
mfem::Vector full;
gridFunction.GetTrueDofs(full);
m_dofMap.gather(full, reduced);
}
[[nodiscard]]
mfem::Vector gather(const mfem::GridFunction &gridFunction) const {
mfem::Vector reduced(m_dofMap.reduced_size());
gather(gridFunction, reduced);
return reduced;
}
/*
* Scatter with projection semantics. Unsupported true DOFs are zeroed
* before the complete true vector is distributed to the grid function.
*/
void scatter(
const mfem::Vector &reduced,
mfem::GridFunction &gridFunction
) const {
validate_grid_function(gridFunction);
const mfem::Vector full = m_dofMap.scatter(reduced);
gridFunction.SetFromTrueDofs(full);
}
/*
* Scatter while preserving the grid function's existing unsupported
* true DOFs.
*/
void scatter_into(
const mfem::Vector &reduced,
mfem::GridFunction &gridFunction
) const {
validate_grid_function(gridFunction);
mfem::Vector full;
gridFunction.GetTrueDofs(full);
m_dofMap.scatter_into(reduced, full);
gridFunction.SetFromTrueDofs(full);
}
private:
void validate_grid_function(const mfem::GridFunction &gridFunction) const {
if (gridFunction.FESpace() != m_finiteElementSpace) {
throw std::invalid_argument(
"FieldDofGridFunctionAdapter received a grid function "
"from a different finite-element space."
);
}
}
FieldDofMap m_dofMap;
const mfem::FiniteElementSpace *m_finiteElementSpace;
};
/* /*
* Construct the canonical solver map for a registered spatial field. * Construct the canonical solver map for a registered spatial field.
* *
@@ -903,4 +1008,16 @@ export namespace mean_field::field {
return FieldDofMap(support); return FieldDofMap(support);
} }
template <
MfemDomainField FieldT,
utils::domain::IsSchema SchemaT>
[[nodiscard]]
FieldDofGridFunctionAdapter
make_field_dof_grid_function_adapter(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return FieldDofGridFunctionAdapter(
make_field_dof_map<FieldT, SchemaT>(finiteElementSpace),
finiteElementSpace
);
}
} // namespace mean_field::field } // namespace mean_field::field

View File

@@ -6,7 +6,11 @@ import :mapping.domain_mapper;
export namespace mean_field::integrators { export namespace mean_field::integrators {
class AdvectionIntegrator : public mfem::BlockNonlinearFormIntegrator { class AdvectionIntegrator : public mfem::BlockNonlinearFormIntegrator {
public: public:
explicit AdvectionIntegrator(const mapping::DomainMapper &map); AdvectionIntegrator(
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
);
void AssembleElementVector( void AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el, const mfem::Array<const mfem::FiniteElement *> &el,
@@ -23,6 +27,6 @@ export namespace mean_field::integrators {
) override; ) override;
private: private:
const mapping::DomainMapper &m_map; mapping::GridFunctionMappingEvaluator m_mapping;
}; };
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -7,7 +7,9 @@ export namespace mean_field::integrators {
class CentrifugalForceIntegrator : public mfem::BlockNonlinearFormIntegrator { class CentrifugalForceIntegrator : public mfem::BlockNonlinearFormIntegrator {
public: public:
CentrifugalForceIntegrator( CentrifugalForceIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega const mfem::Vector &omega
); );
@@ -29,9 +31,9 @@ export namespace mean_field::integrators {
) override; ) override;
private: private:
const mapping::DomainMapper &m_map; mapping::GridFunctionMappingEvaluator m_mapping;
mfem::Vector m_omega; mfem::Vector m_omega;
const mfem::IntegrationRule *m_ir = nullptr; const mfem::IntegrationRule *m_ir = nullptr;
}; };
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -7,7 +7,9 @@ export namespace mean_field::integrators {
class CoriolisIntegrator : public mfem::BlockNonlinearFormIntegrator { class CoriolisIntegrator : public mfem::BlockNonlinearFormIntegrator {
public: public:
CoriolisIntegrator( CoriolisIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega const mfem::Vector &omega
); );
@@ -26,9 +28,9 @@ export namespace mean_field::integrators {
) override; ) override;
private: private:
const mapping::DomainMapper &m_map; mapping::GridFunctionMappingEvaluator m_mapping;
mfem::Vector m_omega; mfem::Vector m_omega;
mfem::DenseMatrix m_omega_mat; mfem::DenseMatrix m_omega_mat;
}; };
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -10,7 +10,9 @@ export namespace mean_field::integrators {
class GravityMomentumIntegrator : public mfem::BlockNonlinearFormIntegrator { class GravityMomentumIntegrator : public mfem::BlockNonlinearFormIntegrator {
public: public:
explicit GravityMomentumIntegrator( explicit GravityMomentumIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
GravityForceJacobianMode jacobian_mode = GravityForceJacobianMode::field_coupled GravityForceJacobianMode jacobian_mode = GravityForceJacobianMode::field_coupled
); );
@@ -33,8 +35,8 @@ export namespace mean_field::integrators {
) override; ) override;
private: private:
const mapping::DomainMapper &m_map; mapping::GridFunctionMappingEvaluator m_mapping;
GravityForceJacobianMode m_jacobian_mode; GravityForceJacobianMode m_jacobian_mode;
const mfem::IntegrationRule *m_integration_rule{nullptr}; const mfem::IntegrationRule *m_integration_rule{nullptr};
}; };
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -6,7 +6,11 @@ import :mapping.domain_mapper;
export namespace mean_field::integrators { export namespace mean_field::integrators {
class ContinuityVolumeIntegrator : public mfem::BlockNonlinearFormIntegrator { class ContinuityVolumeIntegrator : public mfem::BlockNonlinearFormIntegrator {
public: public:
explicit ContinuityVolumeIntegrator(const mapping::DomainMapper &map); ContinuityVolumeIntegrator(
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
);
void AssembleElementVector( void AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el, const mfem::Array<const mfem::FiniteElement *> &el,
@@ -23,12 +27,16 @@ export namespace mean_field::integrators {
) override; ) override;
private: private:
const mapping::DomainMapper &m_map; mapping::GridFunctionMappingEvaluator m_mapping;
}; };
class ContinuityFaceIntegrator : public mfem::BlockNonlinearFormIntegrator { class ContinuityFaceIntegrator : public mfem::BlockNonlinearFormIntegrator {
public: public:
explicit ContinuityFaceIntegrator(const mapping::DomainMapper &map); ContinuityFaceIntegrator(
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
);
void AssembleFaceVector( void AssembleFaceVector(
const mfem::Array<const mfem::FiniteElement *> &el1, const mfem::Array<const mfem::FiniteElement *> &el1,
@@ -58,7 +66,7 @@ export namespace mean_field::integrators {
); );
private: private:
const mapping::DomainMapper &m_map; mapping::GridFunctionMappingEvaluator m_mapping;
}; };
} // namespace mean_field::integrators } // namespace mean_field::integrators

View File

@@ -10,7 +10,9 @@ export namespace mean_field::integrators {
template <utils::is_xad EOS_T> class PressureGradientIntegrator : public mfem::BlockNonlinearFormIntegrator { template <utils::is_xad EOS_T> class PressureGradientIntegrator : public mfem::BlockNonlinearFormIntegrator {
public: public:
PressureGradientIntegrator( PressureGradientIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
utils::EOS_P<EOS_T> eos utils::EOS_P<EOS_T> eos
); );
@@ -28,16 +30,18 @@ export namespace mean_field::integrators {
) override; ) override;
private: private:
const mapping::DomainMapper &m_map; mapping::GridFunctionMappingEvaluator m_mapping;
utils::EOS_P<EOS_T> m_eos; utils::EOS_P<EOS_T> m_eos;
}; };
template <utils::is_xad EOS_T> template <utils::is_xad EOS_T>
PressureGradientIntegrator<EOS_T>::PressureGradientIntegrator( PressureGradientIntegrator<EOS_T>::PressureGradientIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
utils::EOS_P<EOS_T> eos utils::EOS_P<EOS_T> eos
) )
: m_map(map), : m_mapping(mapper, displacement, compactification_coordinate),
m_eos(std::move(eos)) { m_eos(std::move(eos)) {
} }
@@ -48,6 +52,8 @@ export namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec const mfem::Array<mfem::Vector *> &elvec
) { ) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) { if (utils::is_vacuum(Tr, elvec)) {
return; return;
} }
@@ -78,7 +84,7 @@ export namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcDShape(ip, dshape_v_ref); fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys); mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
@@ -111,6 +117,8 @@ export namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun, const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats const mfem::Array2D<mfem::DenseMatrix *> &elmats
) { ) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0]; const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1]; const mfem::FiniteElement *fe_rho = el[1];
@@ -141,7 +149,7 @@ export namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q); const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip); Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip); auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcDShape(ip, dshape_v_ref); fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys); mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);

View File

@@ -7,7 +7,9 @@ export namespace mean_field::integrators {
class ViscosityIntegrator : public mfem::BlockNonlinearFormIntegrator { class ViscosityIntegrator : public mfem::BlockNonlinearFormIntegrator {
public: public:
ViscosityIntegrator( ViscosityIntegrator(
const mapping::DomainMapper &map, const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
double mu, double mu,
int quad_boost int quad_boost
); );
@@ -29,7 +31,7 @@ export namespace mean_field::integrators {
) override; ) override;
private: private:
const mapping::DomainMapper &m_map; mapping::GridFunctionMappingEvaluator m_mapping;
double m_mu; double m_mu;
int m_quad_boost; int m_quad_boost;
}; };

View File

@@ -9,7 +9,9 @@ export namespace mean_field::mapping {
class MappedScalarCoefficient : public mfem::Coefficient { class MappedScalarCoefficient : public mfem::Coefficient {
public: public:
MappedScalarCoefficient( MappedScalarCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
Coefficient &coeff, Coefficient &coeff,
COORDINATE_SPACE coord_space = COORDINATE_SPACE::PHYSICAL COORDINATE_SPACE coord_space = COORDINATE_SPACE::PHYSICAL
); );
@@ -27,7 +29,7 @@ export namespace mean_field::mapping {
); );
private: private:
const DomainMapper &m_map; GridFunctionMappingEvaluator m_mapping;
Coefficient &m_coeff; Coefficient &m_coeff;
COORDINATE_SPACE m_coord_space; COORDINATE_SPACE m_coord_space;
}; };
@@ -35,13 +37,17 @@ export namespace mean_field::mapping {
class MappedDiffusionCoefficient : public mfem::MatrixCoefficient { class MappedDiffusionCoefficient : public mfem::MatrixCoefficient {
public: public:
MappedDiffusionCoefficient( MappedDiffusionCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
mfem::Coefficient &sigma, mfem::Coefficient &sigma,
int dim int dim
); );
MappedDiffusionCoefficient( MappedDiffusionCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
MatrixCoefficient &sigma MatrixCoefficient &sigma
); );
@@ -52,7 +58,7 @@ export namespace mean_field::mapping {
) override; ) override;
private: private:
const DomainMapper &m_map; GridFunctionMappingEvaluator m_mapping;
mfem::Coefficient *m_scalar; mfem::Coefficient *m_scalar;
MatrixCoefficient *m_tensor; MatrixCoefficient *m_tensor;
}; };
@@ -60,7 +66,9 @@ export namespace mean_field::mapping {
class MappedVectorCoefficient : public mfem::VectorCoefficient { class MappedVectorCoefficient : public mfem::VectorCoefficient {
public: public:
MappedVectorCoefficient( MappedVectorCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
VectorCoefficient &coeff VectorCoefficient &coeff
); );
@@ -71,7 +79,7 @@ export namespace mean_field::mapping {
) override; ) override;
private: private:
const DomainMapper &m_map; GridFunctionMappingEvaluator m_mapping;
VectorCoefficient &m_coeff; VectorCoefficient &m_coeff;
}; };
@@ -80,7 +88,9 @@ export namespace mean_field::mapping {
using Func = std::function<double(const mfem::Vector &x)>; using Func = std::function<double(const mfem::Vector &x)>;
PhysicalPositionFunctionCoefficient( PhysicalPositionFunctionCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
Func f Func f
); );
@@ -91,13 +101,15 @@ export namespace mean_field::mapping {
private: private:
Func m_f; Func m_f;
const DomainMapper &m_map; GridFunctionMappingEvaluator m_mapping;
}; };
class MappedHDivMassCoefficient final : public mfem::MatrixCoefficient { class MappedHDivMassCoefficient final : public mfem::MatrixCoefficient {
public: public:
MappedHDivMassCoefficient( MappedHDivMassCoefficient(
const DomainMapper &map, const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const int dim const int dim
); );
@@ -108,6 +120,6 @@ export namespace mean_field::mapping {
) override; ) override;
private: private:
const DomainMapper &m_map; GridFunctionMappingEvaluator m_mapping;
}; };
} // namespace mean_field::mapping } // namespace mean_field::mapping

View File

@@ -8,347 +8,259 @@ import :mapping.compactification;
import :utils.user; import :utils.user;
export namespace mean_field::mapping { export namespace mean_field::mapping {
enum class FaceElementSide : uint8_t { element_1, element_2 }; enum class FaceElementSide : uint8_t { element_1, element_2 };
class ElementDisplacementData { class ElementDisplacementData {
public: public:
ElementDisplacementData( ElementDisplacementData(
const mfem::FiniteElement &element, const mfem::FiniteElement &element, const mfem::Vector &displacement_dofs,
const mfem::Vector &displacement_dofs, mfem::Ordering::Type ordering = mfem::Ordering::byNODES);
mfem::Ordering::Type ordering = mfem::Ordering::byNODES
); [[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
[[nodiscard]] const mfem::DenseMatrix &GetDofMatrix() const noexcept;
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept; [[nodiscard]] int GetDimension() const noexcept;
[[nodiscard]] const mfem::DenseMatrix &GetDofMatrix() const noexcept; [[nodiscard]] int GetDofCount() const noexcept;
[[nodiscard]] int GetDimension() const noexcept; [[nodiscard]] mfem::Ordering::Type GetOrdering() const noexcept;
[[nodiscard]] int GetDofCount() const noexcept;
[[nodiscard]] mfem::Ordering::Type GetOrdering() const noexcept; private:
const mfem::FiniteElement *m_element;
private: mfem::DenseMatrix m_dof_matrix;
const mfem::FiniteElement *m_element; int m_dimension;
mfem::DenseMatrix m_dof_matrix; mfem::Ordering::Type m_ordering;
int m_dimension; };
mfem::Ordering::Type m_ordering;
}; struct CompactificationPointData {
double coordinate{0.0};
struct CompactificationPointData { mfem::Vector coordinate_gradient;
double coordinate{0.0}; };
mfem::Vector coordinate_gradient;
}; [[nodiscard]] ElementDisplacementData
ElementDisplacementDataFromElementVDofs(const mfem::FiniteElement &element,
[[nodiscard]] ElementDisplacementData ElementDisplacementDataFromElementVDofs( const mfem::Vector &displacement_dofs);
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs class ElementCompactificationData {
); public:
ElementCompactificationData(const mfem::FiniteElement &element,
class ElementCompactificationData { const mfem::Vector &dofs);
public:
ElementCompactificationData( [[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
const mfem::FiniteElement &element, [[nodiscard]] const mfem::Vector &GetDofs() const noexcept;
const mfem::Vector &dofs [[nodiscard]] int GetDofCount() const noexcept;
);
private:
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept; const mfem::FiniteElement *m_element;
[[nodiscard]] const mfem::Vector &GetDofs() const noexcept; mfem::Vector m_dofs;
[[nodiscard]] int GetDofCount() const noexcept; };
private: struct ElementMappingData {
const mfem::FiniteElement *m_element; const ElementDisplacementData &displacement;
mfem::Vector m_dofs; const ElementCompactificationData &compactification;
}; };
struct ElementMappingData { class DomainMapper {
const ElementDisplacementData &displacement; public:
const ElementCompactificationData &compactification; class Workspace {
}; public:
explicit Workspace(int dimension = 3);
class DomainMapperStateless {
public: void SetDimension(int dimension);
class Workspace {
public: [[nodiscard]] int GetDimension() const noexcept;
explicit Workspace(int dimension = 3);
private:
void SetDimension(int dimension); friend class DomainMapper;
[[nodiscard]] int GetDimension() const noexcept; int m_dimension;
private: mfem::Vector m_shape;
friend class DomainMapperStateless; mfem::DenseMatrix m_mesh_dshape;
mfem::Vector m_field_value;
int m_dimension; mfem::DenseMatrix m_field_jacobian;
mfem::Vector m_shape; mfem::Vector m_compactification_shape;
mfem::DenseMatrix m_mesh_dshape; mfem::DenseMatrix m_compactification_dshape;
mfem::Vector m_field_value; CompactificationPointData m_compactification_point;
mfem::DenseMatrix m_field_jacobian;
mfem::Vector m_reference_normal;
mfem::Vector m_compactification_shape; mfem::Vector m_mapped_normal;
mfem::DenseMatrix m_compactification_dshape; mfem::DenseMatrix m_full_element_jacobian;
CompactificationPointData m_compactification_point;
mfem::Vector m_vector_temp;
mfem::Vector m_reference_normal; mfem::DenseMatrix m_matrix_temp_1;
mfem::Vector m_mapped_normal; mfem::DenseMatrix m_matrix_temp_2;
mfem::DenseMatrix m_full_element_jacobian;
compactification::ExteriorMapResult m_exterior_result;
mfem::Vector m_vector_temp; compactification::ExteriorMapVariation m_exterior_variation;
mfem::DenseMatrix m_matrix_temp_1; };
mfem::DenseMatrix m_matrix_temp_2;
public:
compactification::ExteriorMapResult m_exterior_result; DomainMapper(
compactification::ExteriorMapVariation m_exterior_variation; utils::DomainMapperOptions options,
}; std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map);
public: DomainMapper(const DomainMapper &) = delete;
DomainMapperStateless( DomainMapper &operator=(const DomainMapper &) = delete;
utils::DomainMapperStatelessOptions options, DomainMapper(DomainMapper &&) = default;
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map DomainMapper &operator=(DomainMapper &&) = default;
);
[[nodiscard]] MappingStatus
DomainMapperStateless(const DomainMapperStateless &) = delete; EvaluatePoint(const ElementMappingData &element_data,
DomainMapperStateless &operator=(const DomainMapperStateless &) = delete; mfem::ElementTransformation &transformation,
DomainMapperStateless(DomainMapperStateless &&) = default; const mfem::IntegrationPoint &integration_point,
DomainMapperStateless &operator=(DomainMapperStateless &&) = default; Workspace &workspace, MappingPointContext &context) const;
[[nodiscard]] MappingStatus EvaluatePoint( [[nodiscard]] MappingStatus
const ElementMappingData &element_data, EvaluateVolume(const ElementMappingData &element_data,
mfem::ElementTransformation &transformation, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
Workspace &workspace, Workspace &workspace, VolumeMappingContext &context) const;
MappingPointContext &context
) const; [[nodiscard]] MappingStatus
EvaluateFace(const ElementMappingData &element_data,
[[nodiscard]] MappingStatus EvaluateVolume( mfem::FaceElementTransformations &transformation,
const ElementMappingData &element_data, FaceElementSide side,
mfem::ElementTransformation &transformation, const mfem::IntegrationPoint &integration_point,
const mfem::IntegrationPoint &integration_point, Workspace &workspace, FaceMappingContext &context) const;
Workspace &workspace,
VolumeMappingContext &context [[nodiscard]] MappingStatus
) const; EvaluatePointVariation(const ElementMappingData &element_data,
const ElementDisplacementData &direction,
[[nodiscard]] MappingStatus EvaluateFace( mfem::ElementTransformation &transformation,
const ElementMappingData &element_data, const mfem::IntegrationPoint &integration_point,
mfem::FaceElementTransformations &transformation, const MappingPointContext &base_context,
FaceElementSide side, Workspace &workspace,
const mfem::IntegrationPoint &integration_point, MappingPointVariation &variation) const;
Workspace &workspace,
FaceMappingContext &context [[nodiscard]] MappingStatus
) const; EvaluateVolumeVariation(const ElementMappingData &element_data,
const ElementDisplacementData &direction,
[[nodiscard]] MappingStatus EvaluatePointVariation( mfem::ElementTransformation &transformation,
const ElementMappingData &element_data, const mfem::IntegrationPoint &integration_point,
const ElementDisplacementData &direction, const VolumeMappingContext &base_context,
mfem::ElementTransformation &transformation, Workspace &workspace,
const mfem::IntegrationPoint &integration_point, VolumeMappingVariation &variation) const;
const MappingPointContext &base_context,
Workspace &workspace, [[nodiscard]] MappingStatus EvaluateFaceVariation(
MappingPointVariation &variation const ElementMappingData &element_data,
) const; const ElementDisplacementData &direction,
mfem::FaceElementTransformations &transformation, FaceElementSide side,
[[nodiscard]] MappingStatus EvaluateVolumeVariation( const mfem::IntegrationPoint &integration_point,
const ElementMappingData &element_data, const FaceMappingContext &base_context, Workspace &workspace,
const ElementDisplacementData &direction, FaceMappingVariation &variation) const;
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, [[nodiscard]] bool IsCompactifiedElement(
const VolumeMappingContext &base_context, const mfem::ElementTransformation &transformation) const noexcept;
Workspace &workspace, [[nodiscard]] int GetDimension() const noexcept;
VolumeMappingVariation &variation [[nodiscard]] const compactification::ExteriorDomainMap &
) const; GetExteriorMap() const noexcept;
[[nodiscard]] MappingStatus EvaluateFaceVariation( private:
const ElementMappingData &element_data, void ValidateElementData(const ElementMappingData &element_data) const;
const ElementDisplacementData &direction,
mfem::FaceElementTransformations &transformation, void EvaluateField(const ElementDisplacementData &field,
FaceElementSide side, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, const mfem::IntegrationPoint &integration_point,
const FaceMappingContext &base_context, Workspace &workspace, mfem::Vector &value,
Workspace &workspace, mfem::DenseMatrix &jacobian) const;
FaceMappingVariation &variation
) const; [[nodiscard]] MappingStatus EvaluateCompactificationCoordinate(
const ElementCompactificationData &compactification,
[[nodiscard]] bool IsCompactifiedElement(const mfem::ElementTransformation &transformation) const noexcept; mfem::ElementTransformation &transformation,
[[nodiscard]] int GetDimension() const noexcept; const mfem::IntegrationPoint &integration_point, Workspace &workspace,
[[nodiscard]] int GetVacuumElementAttribute() const noexcept; CompactificationPointData &point_data) const;
[[nodiscard]] const compactification::ExteriorDomainMap &GetExteriorMap() const noexcept;
[[nodiscard]] static mfem::ElementTransformation &
private: SelectFaceElementTransformation(
void ValidateElementData(const ElementMappingData &element_data) const; mfem::FaceElementTransformations &transformation, FaceElementSide side);
void EvaluateField( [[nodiscard]] static const mfem::IntegrationPoint &
const ElementDisplacementData &field, SelectFaceElementIntegrationPoint(
mfem::ElementTransformation &transformation, mfem::FaceElementTransformations &transformation, FaceElementSide side);
const mfem::IntegrationPoint &integration_point,
Workspace &workspace, utils::DomainMapperOptions m_options;
mfem::Vector &value, std::unique_ptr<const compactification::ExteriorDomainMap> m_exterior_map;
mfem::DenseMatrix &jacobian };
) const;
class GridFunctionMappingEvaluator {
[[nodiscard]] MappingStatus EvaluateCompactificationCoordinate( public:
const ElementCompactificationData &compactification, /*
mfem::ElementTransformation &transformation, * The evaluator references the supplied grid functions and caches copies of
const mfem::IntegrationPoint &integration_point, * their element-local DOFs. Call InvalidateCache() or Refresh() after either
Workspace &workspace, * grid function's values are modified. Finite-element-space sequence changes
CompactificationPointData &point_data * are detected automatically.
) const; *
* This object owns mutable workspace and cache state and is not thread-safe.
[[nodiscard]] static mfem::ElementTransformation &SelectFaceElementTransformation( */
mfem::FaceElementTransformations &transformation, GridFunctionMappingEvaluator(
FaceElementSide side const DomainMapper &mapper,
); const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate);
[[nodiscard]] static const mfem::IntegrationPoint &SelectFaceElementIntegrationPoint(
mfem::FaceElementTransformations &transformation, /*
FaceElementSide side * Discard all element-local field data. The next evaluation reloads its
); * requested element lazily. This operation is idempotent.
*/
utils::DomainMapperStatelessOptions m_options; void InvalidateCache() noexcept;
std::unique_ptr<const compactification::ExteriorDomainMap> m_exterior_map;
}; /*
class DomainMapper { * Reload the currently cached element immediately. If no element has been
* evaluated yet, Refresh() is a validated no-op. If either finite-element
public: * space changed sequence, the old element ID is discarded and the next
explicit DomainMapper( * evaluation reloads lazily against the updated spaces.
const double r_star_ref, */
const double r_inf_ref void Refresh();
);
[[nodiscard]] MappingStatus
explicit DomainMapper( EvaluatePoint(mfem::ElementTransformation &transformation,
const mfem::GridFunction &d, const mfem::IntegrationPoint &integration_point,
const double r_star_ref, MappingPointContext &context);
const double r_inf_ref
); [[nodiscard]] MappingStatus
EvaluateVolume(mfem::ElementTransformation &transformation,
[[nodiscard]] bool is_vacuum(const mfem::ElementTransformation &T) const; const mfem::IntegrationPoint &integration_point,
VolumeMappingContext &context);
void SetDisplacement(const mfem::GridFunction &d);
[[nodiscard]] MappingStatus
[[nodiscard]] bool HasCompactification() const noexcept; EvaluateFace(mfem::FaceElementTransformations &transformation,
[[nodiscard]] bool HasDisplacementField() const noexcept; FaceElementSide side,
[[nodiscard]] bool CalcIsIdentity() const; const mfem::IntegrationPoint &integration_point,
FaceMappingContext &context);
void ResetDisplacement();
[[nodiscard]] VolumeQuadratureContext
void ComputeJacobian( GetQuadratureContext(mfem::ElementTransformation &transformation,
mfem::ElementTransformation &T, const mfem::IntegrationPoint &integration_point);
mfem::DenseMatrix &J
) const; [[nodiscard]] FaceQuadratureContext
GetFaceQuadratureContext(
double ComputeDetJ( mfem::FaceElementTransformations &transformation,
mfem::ElementTransformation &T, const mfem::IntegrationPoint &integration_point,
const mfem::IntegrationPoint &ip FaceElementSide side = FaceElementSide::element_1);
) const;
void GetPhysicalPoint(mfem::ElementTransformation &transformation,
void ComputeMappedDiffusionTensor( const mfem::IntegrationPoint &integration_point,
mfem::ElementTransformation &T, mfem::Vector &physical_position);
mfem::DenseMatrix &D
) const; private:
void ValidateFieldBindings() const;
void ComputeInverseJacobian( [[nodiscard]] bool InvalidateForChangedSpaces();
mfem::ElementTransformation &T, void LoadElement(int element_id);
mfem::DenseMatrix &JInv
) const; const DomainMapper &m_mapper;
const mfem::GridFunction &m_displacement;
VolumeQuadratureContext GetQuadratureContext( const mfem::GridFunction &m_compactification_coordinate;
mfem::ElementTransformation &T, const mfem::FiniteElementSpace *m_displacement_space;
const mfem::IntegrationPoint &ip const mfem::FiniteElementSpace *m_compactification_space;
) const; long m_displacement_space_sequence;
long m_compactification_space_sequence;
FaceQuadratureContext GetFaceQuadratureContext( DomainMapper::Workspace m_workspace;
mfem::FaceElementTransformations &T,
const mfem::IntegrationPoint &ip mfem::Array<int> m_displacement_dofs;
) const; mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement;
void GetPhysicalPoint( mfem::Vector m_element_compactification;
mfem::ElementTransformation &T, std::unique_ptr<ElementDisplacementData> m_displacement_data;
const mfem::IntegrationPoint &ip, std::unique_ptr<ElementCompactificationData> m_compactification_data;
mfem::Vector &x_phys int m_cached_element_id{-1};
) const; };
void GetVectorValue(
const int i,
const mfem::IntegrationPoint &ip,
mfem::Vector &val
) const;
void MapHDivFluxToPhysical(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const mfem::Vector &reference_flux,
mfem::Vector &physical_flux
) const;
void MapPhysicalFluxToHDivReference(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const mfem::Vector &physical_flux,
mfem::Vector &reference_flux
) const;
void MapReferenceGradientToPhysical(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const mfem::Vector &reference_gradient,
mfem::Vector &physical_gradient
) const;
[[nodiscard]] const mfem::GridFunction *GetDisplacement() const;
[[nodiscard]] double GetPhysInfRadius() const;
[[nodiscard]] size_t GetCacheHits() const;
[[nodiscard]] size_t GetCacheMisses() const;
[[nodiscard]] double GetCacheHitRate() const;
void ResetCacheStats() const;
private:
void InitAllScratchSpaces() const;
void ApplyKelvinMapping(
const mfem::Vector &x_ref,
mfem::Vector &x_phys
) const;
void ComputeKelvinJacobian(
const mfem::Vector &x_ref,
const mfem::Vector &x_disp,
const mfem::DenseMatrix &J_D,
mfem::DenseMatrix &J
) const;
void InvalidateCache() const;
void UpdateElementCache(const mfem::ElementTransformation &T) const;
private:
const mfem::GridFunction *m_d;
std::unique_ptr<mfem::GridFunction> m_internal_d;
const int m_dim{3};
const int m_vacuum_attr{3};
const double m_r_star_ref{1.0};
const double m_r_inf_ref{2.0};
const double m_xi_clamp{0.9999};
mutable int m_cached_elem_id{-1};
mutable int m_cached_elem_type{mfem::ElementTransformation::ELEMENT};
mutable const mfem::FiniteElement *m_fe{nullptr};
mutable mfem::Vector m_elem_dofs;
mutable mfem::DenseMatrix m_dof_mat;
mutable mfem::DenseMatrix m_dshape;
mutable mfem::Vector m_shape;
mutable size_t m_cache_hits{0};
mutable size_t m_cache_misses{0};
mutable mfem::DenseMatrix m_J_D;
mutable mfem::DenseMatrix m_J_temp;
mutable mfem::DenseMatrix m_JInv_temp;
mutable mfem::Vector m_x_ref;
mutable mfem::Vector m_x_disp;
mutable mfem::Vector m_d_val;
bool m_displacement_is_identity{true};
};
} // namespace mean_field::mapping } // namespace mean_field::mapping

View File

@@ -6,8 +6,6 @@ export import :utils.user;
export import :utils.domain; export import :utils.domain;
export import :physics.gravity; export import :physics.gravity;
export import :physics.solid_body; export import :physics.solid_body;
export import :physics.barotrope;
export import :physics.contexts;
export import :boundary.contexts; export import :boundary.contexts;
export import :analysis.integral; export import :analysis.integral;
export import :mapping.domain_mapper; export import :mapping.domain_mapper;

View File

@@ -74,7 +74,7 @@ export namespace mean_field::operators::context::barotropic {
public: public:
BarotropicClosureLinearizationContext( BarotropicClosureLinearizationContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const field::FieldDofMap &densityMap, const field::FieldDofMap &densityMap,
const field::FieldDofMap &enthalpyMap, const field::FieldDofMap &enthalpyMap,
const field::FieldDofMap &displacementMap const field::FieldDofMap &displacementMap
@@ -103,7 +103,7 @@ export namespace mean_field::operators::context::barotropic {
void VerifyPrepared() const; void VerifyPrepared() const;
const fem::FEM &m_f; const fem::FEM &m_f;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
int m_densitySize{0}; int m_densitySize{0};
int m_enthalpySize{0}; int m_enthalpySize{0};

View File

@@ -49,11 +49,12 @@ export namespace mean_field::operators::context::gravity_field {
bool reconstructed_operators{false}; bool reconstructed_operators{false};
bool rebuilt_mass_operator{false}; bool rebuilt_mass_operator{false};
bool rebuilt_source_operator{false}; bool rebuilt_source_operator{false};
bool rebuilt_divergence_operator{false};
bool refreshed_variation_state{false}; bool refreshed_variation_state{false};
[[nodiscard]] bool DidAnyWork() const noexcept { [[nodiscard]] bool DidAnyWork() const noexcept {
return reconstructed_operators || rebuilt_mass_operator || rebuilt_source_operator || return reconstructed_operators || rebuilt_mass_operator || rebuilt_source_operator ||
refreshed_variation_state; rebuilt_divergence_operator || refreshed_variation_state;
} }
}; };
@@ -61,7 +62,7 @@ export namespace mean_field::operators::context::gravity_field {
public: public:
GravityFieldGeometryContext( GravityFieldGeometryContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper const mapping::DomainMapper &domain_mapper
); );
GravityFieldGeometryContext(const GravityFieldGeometryContext &) = delete; GravityFieldGeometryContext(const GravityFieldGeometryContext &) = delete;
@@ -77,6 +78,8 @@ export namespace mean_field::operators::context::gravity_field {
[[nodiscard]] const PreparedMappedHDivMassOperator &GetMassOperator() const; [[nodiscard]] const PreparedMappedHDivMassOperator &GetMassOperator() const;
[[nodiscard]] const PreparedMappedGravitySourceOperator &GetSourceOperator() const; [[nodiscard]] const PreparedMappedGravitySourceOperator &GetSourceOperator() const;
[[nodiscard]] const mfem::Operator &GetDivergenceOperator() const;
[[nodiscard]] const mfem::Operator &GetTransposeDivergenceOperator() const;
[[nodiscard]] const mfem::Vector &GetDisplacementTrue() const; [[nodiscard]] const mfem::Vector &GetDisplacementTrue() const;
[[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept; [[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept;
[[nodiscard]] DiscretizationRevision GetDiscretizationRevision() const noexcept; [[nodiscard]] DiscretizationRevision GetDiscretizationRevision() const noexcept;
@@ -85,10 +88,12 @@ export namespace mean_field::operators::context::gravity_field {
private: private:
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper; const mapping::DomainMapper &m_domain_mapper;
std::unique_ptr<PreparedMappedHDivMassOperator> m_mass_operator; std::unique_ptr<PreparedMappedHDivMassOperator> m_mass_operator;
std::unique_ptr<PreparedMappedGravitySourceOperator> m_source_operator; std::unique_ptr<PreparedMappedGravitySourceOperator> m_source_operator;
std::unique_ptr<mfem::ParMixedBilinearForm> m_divergence_operator;
std::unique_ptr<mfem::TransposeOperator> m_transpose_divergence_operator;
field::FieldDofMap m_displacement_map; field::FieldDofMap m_displacement_map;
mfem::Vector m_displacement_true; mfem::Vector m_displacement_true;
@@ -113,7 +118,7 @@ export namespace mean_field::operators::context::gravity_field {
public: public:
GravityFieldLinearizationContext( GravityFieldLinearizationContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper const mapping::DomainMapper &domain_mapper
); );
GravityFieldLinearizationContext(const GravityFieldLinearizationContext &) = delete; GravityFieldLinearizationContext(const GravityFieldLinearizationContext &) = delete;

View File

@@ -96,7 +96,7 @@ export namespace mean_field::operators::context::hydrostatic {
public: public:
HydrostaticEquilibriumContext( HydrostaticEquilibriumContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper const mapping::DomainMapper &domainMapper
); );
HydrostaticEquilibriumContext(const HydrostaticEquilibriumContext &) = delete; HydrostaticEquilibriumContext(const HydrostaticEquilibriumContext &) = delete;
@@ -138,7 +138,7 @@ export namespace mean_field::operators::context::hydrostatic {
void VerifyPrepared() const; void VerifyPrepared() const;
const fem::FEM &m_f; const fem::FEM &m_f;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
field::FieldDofMap m_enthalpyMap; field::FieldDofMap m_enthalpyMap;
field::FieldDofMap m_gravityPotentialMap; field::FieldDofMap m_gravityPotentialMap;

View File

@@ -81,7 +81,7 @@ export namespace mean_field::operators::context::pressure_force {
public: public:
PressureForceLinearizationContext( PressureForceLinearizationContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const field::FieldDofMap &enthalpyMap, const field::FieldDofMap &enthalpyMap,
const field::FieldDofMap &displacementMap const field::FieldDofMap &displacementMap
); );

View File

@@ -78,7 +78,7 @@ export namespace mean_field::operators::context::rotational_displacement_force {
public: public:
RotationalDisplacementForceLinearizationContext( RotationalDisplacementForceLinearizationContext(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper const mapping::DomainMapper &domainMapper
); );
RotationalDisplacementForceLinearizationContext(const RotationalDisplacementForceLinearizationContext &) = RotationalDisplacementForceLinearizationContext(const RotationalDisplacementForceLinearizationContext &) =

View File

@@ -13,7 +13,7 @@ export namespace mean_field::operators {
public: public:
GravityFieldOperator( GravityFieldOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper, const mapping::DomainMapper &domain_mapper,
context::gravity_field::GravityFieldLinearizationContext &linearization_context, context::gravity_field::GravityFieldLinearizationContext &linearization_context,
const mfem::Array<int> &state_offsets, const mfem::Array<int> &state_offsets,
GravityFieldJacobianOperator &jacobian GravityFieldJacobianOperator &jacobian
@@ -55,7 +55,7 @@ export namespace mean_field::operators {
private: private:
fem::FEM &m_fem; fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper; const mapping::DomainMapper &m_domain_mapper;
context::gravity_field::GravityFieldLinearizationContext &m_linearization_context; context::gravity_field::GravityFieldLinearizationContext &m_linearization_context;
mfem::Array<int> m_state_offsets; mfem::Array<int> m_state_offsets;
mfem::Array<int> m_residual_offsets; mfem::Array<int> m_residual_offsets;
@@ -112,4 +112,39 @@ export namespace mean_field::operators {
context::gravity_field::GravityFieldGeometryContext &m_gravity_field_geometry_context; context::gravity_field::GravityFieldGeometryContext &m_gravity_field_geometry_context;
mfem::Vector m_displacement; mfem::Vector m_displacement;
}; };
class ReducedGravityFieldPreconditioner final : public mfem::Solver {
public:
ReducedGravityFieldPreconditioner(
const fem::FEM &f,
const context::gravity_field::GravityFieldGeometryContext &geometry_context
);
ReducedGravityFieldPreconditioner(const ReducedGravityFieldPreconditioner &) = delete;
ReducedGravityFieldPreconditioner &operator=(const ReducedGravityFieldPreconditioner &) = delete;
ReducedGravityFieldPreconditioner(ReducedGravityFieldPreconditioner &&) = delete;
ReducedGravityFieldPreconditioner &operator=(ReducedGravityFieldPreconditioner &&) = delete;
void SetOperator(const mfem::Operator &gravity_operator) override;
void Mult(
const mfem::Vector &right_hand_side,
mfem::Vector &action
) const override;
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept;
private:
field::FieldDofMap m_flux_map;
field::FieldDofMap m_potential_map;
mfem::Array<int> m_offsets;
mfem::Array<int> m_empty_tdofs;
std::unique_ptr<mfem::OperatorJacobiSmoother> m_mass_preconditioner;
std::unique_ptr<mfem::HypreParMatrix> m_schur;
std::unique_ptr<mfem::HypreBoomerAMG> m_potential_preconditioner;
mutable mfem::Vector m_potential_rhs_true;
mutable mfem::Vector m_potential_action_true;
};
} // namespace mean_field::operators } // namespace mean_field::operators

View File

@@ -11,7 +11,7 @@ export namespace mean_field::operators {
public: public:
GravityFieldJacobianOperator( GravityFieldJacobianOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper, const mapping::DomainMapper &domain_mapper,
const context::gravity_field::GravityFieldLinearizationContext &linearization_context, const context::gravity_field::GravityFieldLinearizationContext &linearization_context,
const mfem::Array<int> &state_offsets, const mfem::Array<int> &state_offsets,
const mfem::Array<int> &residual_offsets const mfem::Array<int> &residual_offsets
@@ -27,7 +27,7 @@ export namespace mean_field::operators {
private: private:
fem::FEM &m_fem; fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper; const mapping::DomainMapper &m_domain_mapper;
const context::gravity_field::GravityFieldLinearizationContext &m_linearization_context; const context::gravity_field::GravityFieldLinearizationContext &m_linearization_context;
mfem::Array<int> m_state_offsets; mfem::Array<int> m_state_offsets;
mfem::Array<int> m_residual_offsets; mfem::Array<int> m_residual_offsets;

View File

@@ -22,7 +22,7 @@ export namespace mean_field::operators::kernels {
*/ */
void apply_barotropic_closure( void apply_barotropic_closure(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const mfem::Vector &densityTrue, const mfem::Vector &densityTrue,
const mfem::Vector &enthalpyTrue, const mfem::Vector &enthalpyTrue,
@@ -32,7 +32,7 @@ export namespace mean_field::operators::kernels {
void apply_barotropic_closure_density_action( void apply_barotropic_closure_density_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const mfem::Vector &densityVariationTrue, const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
@@ -41,7 +41,7 @@ export namespace mean_field::operators::kernels {
void apply_barotropic_closure_enthalpy_action( void apply_barotropic_closure_enthalpy_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &enthalpyVariationTrue, const mfem::Vector &enthalpyVariationTrue,
@@ -51,7 +51,7 @@ export namespace mean_field::operators::kernels {
void apply_barotropic_closure_displacement_action( void apply_barotropic_closure_displacement_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const mfem::Vector &baseDensityTrue, const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &baseEnthalpyTrue,

View File

@@ -10,7 +10,7 @@ export import :mapping.domain_mapper;
export namespace mean_field::operators::kernels { export namespace mean_field::operators::kernels {
void apply_gravity_displacement_force_residual( void apply_gravity_displacement_force_residual(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const mfem::Vector &densityTrue, const mfem::Vector &densityTrue,
const mfem::Vector &gravityGradientTrue, const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
@@ -19,7 +19,7 @@ export namespace mean_field::operators::kernels {
void apply_gravity_displacement_force_density_action( void apply_gravity_displacement_force_density_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const mfem::Vector &densityVariationTrue, const mfem::Vector &densityVariationTrue,
const mfem::Vector &baseGravityGradientTrue, const mfem::Vector &baseGravityGradientTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
@@ -28,7 +28,7 @@ export namespace mean_field::operators::kernels {
void apply_gravity_displacement_force_gradient_action( void apply_gravity_displacement_force_gradient_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const mfem::Vector &baseDensityTrue, const mfem::Vector &baseDensityTrue,
const mfem::Vector &gravityGradientVariationTrue, const mfem::Vector &gravityGradientVariationTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
@@ -37,7 +37,7 @@ export namespace mean_field::operators::kernels {
void apply_gravity_displacement_force_displacement_action( void apply_gravity_displacement_force_displacement_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const mfem::Vector &baseDensityTrue, const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseGravityGradientTrue, const mfem::Vector &baseGravityGradientTrue,
const mfem::Vector &displacementVariationTrue, const mfem::Vector &displacementVariationTrue,
@@ -47,7 +47,7 @@ export namespace mean_field::operators::kernels {
void apply_gravity_displacement_force_complete_action( void apply_gravity_displacement_force_complete_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const mfem::Vector &baseDensityTrue, const mfem::Vector &baseDensityTrue,
const mfem::Vector &densityVariationTrue, const mfem::Vector &densityVariationTrue,
const mfem::Vector &baseGravityGradientTrue, const mfem::Vector &baseGravityGradientTrue,

View File

@@ -15,7 +15,7 @@ export namespace mean_field::operators::kernels {
void apply_mapped_hdiv_mass( void apply_mapped_hdiv_mass(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper, const mapping::DomainMapper &domain_mapper,
const mfem::Vector &gravity_gradient_true, const mfem::Vector &gravity_gradient_true,
const mfem::Vector &displacement_true, const mfem::Vector &displacement_true,
mfem::Vector &action mfem::Vector &action
@@ -23,7 +23,7 @@ export namespace mean_field::operators::kernels {
void apply_mapped_source( void apply_mapped_source(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper, const mapping::DomainMapper &domain_mapper,
const mfem::Vector &density_true, const mfem::Vector &density_true,
const mfem::Vector &displacement_true, const mfem::Vector &displacement_true,
mfem::Vector &action mfem::Vector &action
@@ -31,7 +31,7 @@ export namespace mean_field::operators::kernels {
void apply_mapped_hdiv_mass_variation( void apply_mapped_hdiv_mass_variation(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper, const mapping::DomainMapper &domain_mapper,
const mfem::Vector &gravity_gradient_true, const mfem::Vector &gravity_gradient_true,
const mfem::Vector &displacement_true, const mfem::Vector &displacement_true,
const mfem::Vector &displacement_variation_true, const mfem::Vector &displacement_variation_true,
@@ -40,7 +40,7 @@ export namespace mean_field::operators::kernels {
void apply_mapped_source_variation( void apply_mapped_source_variation(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper, const mapping::DomainMapper &domain_mapper,
const mfem::Vector &density_true, const mfem::Vector &density_true,
const mfem::Vector &displacement_true, const mfem::Vector &displacement_true,
const mfem::Vector &displacement_variation_true, const mfem::Vector &displacement_variation_true,

View File

@@ -11,7 +11,7 @@ export import :physics.rigid_rotation;
export namespace mean_field::operators::kernels { export namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium( void apply_hydrostatic_equilibrium(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const physics::RigidRotation &rotation,
const mfem::Vector &enthalpyTrue, const mfem::Vector &enthalpyTrue,
const mfem::Vector &potentialTrue, const mfem::Vector &potentialTrue,
@@ -22,7 +22,7 @@ export namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium_enthalpy_action( void apply_hydrostatic_equilibrium_enthalpy_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const mfem::Vector &enthalpyVariationTrue, const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
mfem::Vector &action mfem::Vector &action
@@ -30,7 +30,7 @@ export namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium_potential_action( void apply_hydrostatic_equilibrium_potential_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const mfem::Vector &potentialVariationTrue, const mfem::Vector &potentialVariationTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
mfem::Vector &action mfem::Vector &action
@@ -38,7 +38,7 @@ export namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium_constant_action( void apply_hydrostatic_equilibrium_constant_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
double constantVariation, double constantVariation,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
mfem::Vector &action mfem::Vector &action
@@ -46,7 +46,7 @@ export namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium_displacement_action( void apply_hydrostatic_equilibrium_displacement_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue, const mfem::Vector &basePotentialTrue,
@@ -58,7 +58,7 @@ export namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium_action( void apply_hydrostatic_equilibrium_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue, const mfem::Vector &basePotentialTrue,

View File

@@ -11,7 +11,7 @@ export import :eos.polytrope;
export namespace mean_field::operators::kernels { export namespace mean_field::operators::kernels {
void apply_pressure_force_residual( void apply_pressure_force_residual(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const mfem::Vector &enthalpyTrue, const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
@@ -20,7 +20,7 @@ export namespace mean_field::operators::kernels {
void apply_pressure_force_enthalpy_action( void apply_pressure_force_enthalpy_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &enthalpyVariationTrue, const mfem::Vector &enthalpyVariationTrue,
@@ -30,7 +30,7 @@ export namespace mean_field::operators::kernels {
void apply_pressure_force_displacement_action( void apply_pressure_force_displacement_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementVariationTrue, const mfem::Vector &displacementVariationTrue,

View File

@@ -25,7 +25,7 @@ export namespace mean_field::operators::kernels {
*/ */
void apply_rotational_displacement_force_residual( void apply_rotational_displacement_force_residual(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const physics::RigidRotation &rotation,
const mfem::Vector &densityTrue, const mfem::Vector &densityTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
@@ -34,7 +34,7 @@ export namespace mean_field::operators::kernels {
void apply_rotational_displacement_force_density_action( void apply_rotational_displacement_force_density_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const physics::RigidRotation &rotation,
const mfem::Vector &densityVariationTrue, const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementTrue, const mfem::Vector &displacementTrue,
@@ -43,7 +43,7 @@ export namespace mean_field::operators::kernels {
void apply_rotational_displacement_force_displacement_action( void apply_rotational_displacement_force_displacement_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const physics::RigidRotation &rotation,
const mfem::Vector &baseDensityTrue, const mfem::Vector &baseDensityTrue,
const mfem::Vector &displacementVariationTrue, const mfem::Vector &displacementVariationTrue,
@@ -53,7 +53,7 @@ export namespace mean_field::operators::kernels {
void apply_rotational_displacement_force_complete_action( void apply_rotational_displacement_force_complete_action(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation, const physics::RigidRotation &rotation,
const mfem::Vector &baseDensityTrue, const mfem::Vector &baseDensityTrue,
const mfem::Vector &densityVariationTrue, const mfem::Vector &densityVariationTrue,

View File

@@ -26,7 +26,7 @@ export namespace mean_field::operators {
public: public:
PreparedBarotropicClosureOperator( PreparedBarotropicClosureOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState const eos::Polytrope &equationOfState
); );
@@ -71,7 +71,7 @@ export namespace mean_field::operators {
PreparedBarotropicClosureOperator( PreparedBarotropicClosureOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
ConstructionData constructionData ConstructionData constructionData
); );
@@ -100,7 +100,7 @@ export namespace mean_field::operators {
}; };
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
const eos::Polytrope &m_equationOfState; const eos::Polytrope &m_equationOfState;
field::FieldDofMap m_densityMap; field::FieldDofMap m_densityMap;

View File

@@ -85,7 +85,7 @@ export namespace mean_field::operators {
public: public:
PreparedDisplacementResidualOperator( PreparedDisplacementResidualOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope, const eos::Polytrope &barotrope,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext const context::gravity_field::GravityFieldLinearizationContext &gravityContext
); );
@@ -158,7 +158,7 @@ export namespace mean_field::operators {
void VerifyPrepared() const; void VerifyPrepared() const;
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext; const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext;
PreparedPressureForceOperator m_pressureOperator; PreparedPressureForceOperator m_pressureOperator;

View File

@@ -37,7 +37,7 @@ export namespace mean_field::operators {
public: public:
PreparedGravityDisplacementForceOperator( PreparedGravityDisplacementForceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext const context::gravity_field::GravityFieldLinearizationContext &gravityContext
); );
@@ -107,7 +107,7 @@ export namespace mean_field::operators {
void VerifyPrepared() const; void VerifyPrepared() const;
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext; const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext;
context::gravity_field::GravityFieldRevisions m_preparedRevisions; context::gravity_field::GravityFieldRevisions m_preparedRevisions;

View File

@@ -14,7 +14,7 @@ export namespace mean_field::operators {
public: public:
PreparedMappedGravitySourceOperator( PreparedMappedGravitySourceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper const mapping::DomainMapper &domain_mapper
); );
void Prepare(const mfem::Vector &displacement); void Prepare(const mfem::Vector &displacement);
@@ -55,7 +55,7 @@ export namespace mean_field::operators {
}; };
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper; const mapping::DomainMapper &m_domain_mapper;
field::FieldDofMap m_density_map; field::FieldDofMap m_density_map;
field::FieldDofMap m_potential_map; field::FieldDofMap m_potential_map;

View File

@@ -13,7 +13,7 @@ export namespace mean_field::operators {
public: public:
PreparedMappedHDivMassOperator( PreparedMappedHDivMassOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper const mapping::DomainMapper &domain_mapper
); );
void Prepare(const mfem::Vector &displacement); void Prepare(const mfem::Vector &displacement);
@@ -21,6 +21,8 @@ export namespace mean_field::operators {
const mfem::Vector &gravity_gradient, const mfem::Vector &gravity_gradient,
mfem::Vector &action mfem::Vector &action
) const override; ) const override;
void AssembleDiagonal(mfem::Vector &diagonal) const override;
void AssembleTrueDiagonal(mfem::Vector &diagonal) const;
[[nodiscard]] bool IsPrepared() const noexcept; [[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept; [[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
@@ -30,7 +32,7 @@ export namespace mean_field::operators {
private: private:
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper; const mapping::DomainMapper &m_domain_mapper;
field::FieldDofMap m_flux_map; field::FieldDofMap m_flux_map;
field::FieldDofMap m_displacement_map; field::FieldDofMap m_displacement_map;
@@ -40,9 +42,11 @@ export namespace mean_field::operators {
std::unique_ptr<mfem::MatrixCoefficient> m_stellar_mass_coefficient; std::unique_ptr<mfem::MatrixCoefficient> m_stellar_mass_coefficient;
std::unique_ptr<mfem::MatrixCoefficient> m_vacuum_mass_coefficient; std::unique_ptr<mfem::MatrixCoefficient> m_vacuum_mass_coefficient;
std::unique_ptr<mfem::ParBilinearForm> m_mass_form; std::unique_ptr<mfem::ParBilinearForm> m_stellar_mass_form;
std::unique_ptr<mfem::ParBilinearForm> m_vacuum_mass_form;
mutable mfem::Vector m_flux_true; mutable mfem::Vector m_flux_true;
mutable mfem::Vector m_action_true; mutable mfem::Vector m_action_true;
mutable mfem::Vector m_domain_action_true;
mfem::Vector m_displacement_true; mfem::Vector m_displacement_true;
std::uint64_t m_preparation_count{0}; std::uint64_t m_preparation_count{0};
bool m_is_prepared{false}; bool m_is_prepared{false};

View File

@@ -84,7 +84,7 @@ export namespace mean_field::operators {
public: public:
PreparedHydrostaticEquilibriumOperator( PreparedHydrostaticEquilibriumOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper const mapping::DomainMapper &domainMapper
); );
PreparedHydrostaticEquilibriumOperator(const PreparedHydrostaticEquilibriumOperator &) = delete; PreparedHydrostaticEquilibriumOperator(const PreparedHydrostaticEquilibriumOperator &) = delete;
@@ -217,7 +217,7 @@ export namespace mean_field::operators {
void VerifyPrepared() const; void VerifyPrepared() const;
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
context::hydrostatic::HydrostaticEquilibriumContext m_context; context::hydrostatic::HydrostaticEquilibriumContext m_context;

View File

@@ -68,7 +68,7 @@ export namespace mean_field::operators {
public: public:
PreparedMassNormalizationOperator( PreparedMassNormalizationOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext const context::gravity_field::GravityFieldLinearizationContext &gravityContext
); );
@@ -148,7 +148,7 @@ export namespace mean_field::operators {
[[nodiscard]] double GlobalSum(double localValue) const; [[nodiscard]] double GlobalSum(double localValue) const;
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext; const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext;
std::vector<ElementPAData> m_elements; std::vector<ElementPAData> m_elements;

View File

@@ -75,7 +75,7 @@ export namespace mean_field::operators {
public: public:
PreparedPressureForceOperator( PreparedPressureForceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState const eos::Polytrope &equationOfState
); );
@@ -155,7 +155,7 @@ export namespace mean_field::operators {
PreparedPressureForceOperator( PreparedPressureForceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
ConstructionData constructionData ConstructionData constructionData
); );
@@ -217,7 +217,7 @@ export namespace mean_field::operators {
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
const eos::Polytrope &m_equationOfState; const eos::Polytrope &m_equationOfState;

View File

@@ -42,7 +42,7 @@ export namespace mean_field::operators {
public: public:
PreparedRotationalDisplacementForceOperator( PreparedRotationalDisplacementForceOperator(
const fem::FEM &f, const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper const mapping::DomainMapper &domainMapper
); );
PreparedRotationalDisplacementForceOperator(const PreparedRotationalDisplacementForceOperator &) = delete; PreparedRotationalDisplacementForceOperator(const PreparedRotationalDisplacementForceOperator &) = delete;
@@ -105,7 +105,7 @@ export namespace mean_field::operators {
void VerifyPrepared() const; void VerifyPrepared() const;
const fem::FEM &m_fem; const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper; const mapping::DomainMapper &m_domainMapper;
context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext m_context; context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext m_context;

View File

@@ -76,14 +76,14 @@ export namespace mean_field::operators {
public: public:
PreparedStellarEquilibriumOperator( PreparedStellarEquilibriumOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
double targetMass double targetMass
); );
PreparedStellarEquilibriumOperator( PreparedStellarEquilibriumOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
const models::StellarModel &stellarModel const models::StellarModel &stellarModel
); );
@@ -130,7 +130,7 @@ export namespace mean_field::operators {
PreparedStellarEquilibriumOperator( PreparedStellarEquilibriumOperator(
fem::FEM &f, fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper, const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState, const eos::Polytrope &equationOfState,
double targetMass, double targetMass,
ConstructionData constructionData ConstructionData constructionData

View File

@@ -1,162 +0,0 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
export module mean_field:physics.barotrope;
export namespace mean_field::physics {
class PolytropicBarotrope final {
public:
PolytropicBarotrope(
const double polytropic_index,
const double polytropic_constant
)
: m_polytropic_index(polytropic_index),
m_polytropic_constant(polytropic_constant),
m_enthalpy_scale((polytropic_index + 1.0) * polytropic_constant) {
if (!std::isfinite(polytropic_index) || polytropic_index < 1.0) {
throw std::invalid_argument(
std::format(
"The differentiable polytropic closure requires a "
"finite polytropic index greater than or equal to one. "
"Instead a value of {} has been provided",
polytropic_index
)
);
}
if (!std::isfinite(polytropic_constant) || polytropic_constant <= 0.0) {
throw std::invalid_argument(
std::format(
"The polytropic constant must be finite and positive. "
"Instead a value of {} has been provided",
polytropic_constant
)
);
}
};
[[nodiscard]] double polytropic_index() const noexcept {
return m_polytropic_index;
}
[[nodiscard]] double polytropic_constant() const noexcept {
return m_polytropic_constant;
}
[[nodiscard]] double enthalpy_scale() const noexcept {
return m_enthalpy_scale;
}
[[nodiscard]] double pressure_from_density(const double density) const {
validate_nonnegativity(density, "density");
if (density == 0.0) {
return 0.0;
}
return m_polytropic_constant * std::pow(density, 1.0 + 1.0 / m_polytropic_index);
}
[[nodiscard]] double enthalpy_from_density(const double density) const {
validate_nonnegativity(density, "density");
if (density == 0.0) {
return 0.0;
}
return m_enthalpy_scale * std::pow(density, 1.0 / m_polytropic_index);
}
[[nodiscard]] double density_from_enthalpy(const double enthalpy) const {
validate_finite(enthalpy, "enthalpy");
if (enthalpy <= 0.0) {
return 0.0;
}
return std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index);
}
[[nodiscard]] double pressure_from_enthalpy(const double enthalpy) const {
validate_finite(enthalpy, "enthalpy");
if (enthalpy <= 0.0) {
return 0.0;
}
return density_from_enthalpy(enthalpy) * enthalpy / (m_polytropic_index + 1.0);
}
[[nodiscard]] double density_derivative_from_enthalpy(const double enthalpy) const {
validate_finite(enthalpy, "enthalpy");
if (enthalpy < 0.0) {
return 0.0;
}
if (enthalpy == 0.0) {
return m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0;
}
return m_polytropic_index / m_enthalpy_scale *
std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index - 1.0);
}
[[nodiscard]] double pressure_derivative_from_enthalpy(const double enthalpy) const {
validate_finite(enthalpy, "enthalpy");
if (enthalpy <= 0.0) {
return 0.0;
}
return density_from_enthalpy(enthalpy);
}
[[nodiscard]] double pressure_derivative_from_density(const double density) const {
validate_nonnegativity(density, "density");
if (density == 0.0) {
return 0.0;
}
return m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) *
std::pow(density, 1.0 / m_polytropic_index);
}
private:
static void validate_finite(
const double value,
const char *quantity
) {
if (!std::isfinite(value)) {
throw std::domain_error(
std::format(
"The {} must be finite. Instead a value of {} has been "
"provided",
quantity, value
)
);
}
}
static void validate_nonnegativity(
const double value,
const char *quantity
) {
validate_finite(value, quantity);
if (value < 0.0) {
throw std::domain_error(
std::format(
"The {} must be non-negative. Instead a value of {} "
"has been "
"provided",
quantity, value
)
);
}
}
double m_polytropic_index;
double m_polytropic_constant;
double m_enthalpy_scale;
};
} // namespace mean_field::physics

View File

@@ -1,29 +0,0 @@
module;
#include <memory>
#include <mfem.hpp>
export module mean_field:physics.contexts;
export import :mapping.coefficients;
export namespace mean_field::physics {
struct GravityContext {
std::unique_ptr<mfem::ParBilinearForm> m_form;
std::unique_ptr<mfem::ParMixedBilinearForm> b_form;
std::unique_ptr<mfem::BlockOperator> block_A;
std::unique_ptr<mfem::Solver> prec_M;
std::unique_ptr<mfem::HypreBoomerAMG> prec_Phi;
std::unique_ptr<mfem::BlockDiagonalPreconditioner> block_prec;
std::unique_ptr<mfem::MINRESSolver> minres;
mfem::Array<int> stellar_mask;
std::unique_ptr<mfem::TransposeOperator> BT;
std::unique_ptr<mfem::HypreParMatrix> Schur;
std::unique_ptr<mfem::MatrixCoefficient> mapped_hdiv_mass_coeff;
std::unique_ptr<mfem::Operator> source_form;
};
} // namespace mean_field::physics

View File

@@ -16,27 +16,13 @@ export namespace mean_field::physics {
} }
}; };
GravitySolution grav_potential( GravitySolution solve_gravity_field(
fem::FEM &f,
const utils::Args &args,
const mfem::GridFunction &rho,
bool phi_warm = false
);
GravitySolution grav_potential_new(
fem::FEM &f, fem::FEM &f,
const utils::Args &args, const utils::Args &args,
const mfem::GridFunction &rho, const mfem::GridFunction &rho,
const mfem::GridFunction &displacement const mfem::GridFunction &displacement
); );
mfem::GridFunction get_potential(
fem::FEM &fem,
const utils::Args &args,
const mfem::GridFunction &rho,
bool warm = false
);
mfem::DenseMatrix compute_quadrupole_moment_tensor( mfem::DenseMatrix compute_quadrupole_moment_tensor(
const fem::FEM &fem, const fem::FEM &fem,
const mfem::GridFunction &rho, const mfem::GridFunction &rho,
@@ -49,5 +35,4 @@ export namespace mean_field::physics {
const mfem::Vector &phys_x const mfem::Vector &phys_x
); );
void update_stiffness_matrix(fem::FEM &fem);
} // namespace mean_field::physics } // namespace mean_field::physics

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
module; module;
#include <expected>
#include <functional> #include <functional>
#include <string_view> #include <string_view>
@@ -9,109 +8,85 @@ module;
#include <XAD/XAD.hpp> #include <XAD/XAD.hpp>
export module mean_field:utils.misc; export module mean_field:utils.misc;
import :boundary.contexts; import :utils.domain;
export namespace mean_field::utils { export namespace mean_field::utils {
constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4; constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4;
bool is_vacuum( bool is_vacuum(const mfem::ElementTransformation &Tr,
const mfem::ElementTransformation &Tr, mfem::Array<mfem::Vector *> elvec) {
mfem::Array<mfem::Vector *> elvec using Schema = domain::CoreEnvelopeVacuumDomainSchema;
) {
if (Tr.Attribute == 3) { if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
const int size_elvec = elvec.Size(); const int size_elvec = elvec.Size();
for (int i = 0; i < size_elvec; i++) { for (int i = 0; i < size_elvec; i++) {
if (elvec[i]) { if (elvec[i]) {
*elvec[i] = 0.0; *elvec[i] = 0.0;
} }
}
return true;
}
return false;
} }
return true;
}
return false;
}
bool is_vacuum( bool is_vacuum(const mfem::ElementTransformation &Tr,
const mfem::ElementTransformation &Tr, const mfem::Array2D<mfem::DenseMatrix *> &elmats) {
const mfem::Array2D<mfem::DenseMatrix *> &elmats using Schema = domain::CoreEnvelopeVacuumDomainSchema;
) {
if (Tr.Attribute == 3) { if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
const int cols = elmats.NumCols(); const int cols = elmats.NumCols();
const int rows = elmats.NumRows(); const int rows = elmats.NumRows();
for (int rowID = 0; rowID < rows; rowID++) { for (int rowID = 0; rowID < rows; rowID++) {
for (int colID = 0; colID < cols; colID++) { for (int colID = 0; colID < cols; colID++) {
if (elmats(rowID, colID)) { if (elmats(rowID, colID)) {
*elmats(rowID, colID) = 0.0; *elmats(rowID, colID) = 0.0;
}
}
}
return true;
} }
return false; }
} }
return true;
}
return false;
}
constexpr std::string_view ANSI_GREEN = "\033[32m"; constexpr std::string_view ANSI_GREEN = "\033[32m";
constexpr std::string_view ANSI_RED = "\033[31m"; constexpr std::string_view ANSI_RED = "\033[31m";
constexpr std::string_view ANSI_YELLOW = "\033[33m"; constexpr std::string_view ANSI_YELLOW = "\033[33m";
constexpr std::string_view ANSI_BLUE = "\033[34m"; constexpr std::string_view ANSI_BLUE = "\033[34m";
constexpr std::string_view ANSI_MAGENTA = "\033[35m"; constexpr std::string_view ANSI_MAGENTA = "\033[35m";
constexpr std::string_view ANSI_CYAN = "\033[36m"; constexpr std::string_view ANSI_CYAN = "\033[36m";
constexpr std::string_view ANSI_RESET = "\033[0m"; constexpr std::string_view ANSI_RESET = "\033[0m";
constexpr std::string_view ANSI_BCYAN = "\033[1;36m"; constexpr std::string_view ANSI_BCYAN = "\033[1;36m";
constexpr double G = 1.0; constexpr double G = 1.0;
constexpr double MASS = 1.0; constexpr double MASS = 1.0;
constexpr double RADIUS = 1.0; constexpr double RADIUS = 1.0;
[[maybe_unused]] constexpr char HOST[10] = "localhost"; [[maybe_unused]] constexpr char HOST[10] = "localhost";
[[maybe_unused]] constexpr int PORT = 19916; [[maybe_unused]] constexpr int PORT = 19916;
template <typename T> template <typename T>
concept is_xad = std::is_same_v<T, xad::AReal<long double>> || std::is_same_v<T, xad::AReal<double>> || concept is_xad = std::is_same_v<T, xad::AReal<long double>> ||
std::is_same_v<T, xad::AReal<float>>; std::is_same_v<T, xad::AReal<double>> ||
std::is_same_v<T, xad::AReal<float>>;
template <typename T> template <typename T>
concept is_real = std::is_floating_point_v<T> || is_xad<T>; concept is_real = std::is_floating_point_v<T> || is_xad<T>;
template <is_real T> using EOS_P = std::function<T(const T &rho, const T &temp)>; template <is_real T>
using EOS_P = std::function<T(const T &rho, const T &temp)>;
enum class DOMAINS : uint8_t { enum class DOMAINS : uint8_t {
CORE = 1 << 0, CORE = 1 << 0,
ENVELOPE = 1 << 1, ENVELOPE = 1 << 1,
VACUUM = 1 << 2, VACUUM = 1 << 2,
STELLAR = CORE | ENVELOPE, STELLAR = CORE | ENVELOPE,
ALL = CORE | ENVELOPE | VACUUM ALL = CORE | ENVELOPE | VACUUM
}; };
DOMAINS operator|( DOMAINS operator|(DOMAINS lhs, DOMAINS rhs);
DOMAINS lhs,
DOMAINS rhs
);
DOMAINS operator&( DOMAINS operator&(DOMAINS lhs, DOMAINS rhs);
DOMAINS lhs,
DOMAINS rhs
);
void populate_element_mask( int get_mesh_order(const mfem::Mesh &mesh);
const mfem::Mesh *mesh,
DOMAINS domain,
mfem::Array<int> &mask
);
void populate_domain_tdofs(
const mfem::ParFiniteElementSpace *fes,
const mfem::Array<int> &element_mask,
mfem::Array<int> &ess_tdof
);
std::expected<
boundary::Bounds,
boundary::BoundsError>
discover_bounds(
const mfem::Mesh *mesh,
int vacuum_attr
);
int get_mesh_order(const mfem::Mesh &mesh);
} // namespace mean_field::utils } // namespace mean_field::utils

View File

@@ -7,9 +7,9 @@ export import :mapping.compactification.options;
export namespace mean_field::utils { export namespace mean_field::utils {
struct potential { struct potential {
double rtol; double rtol{1.0e-12};
double atol; double atol{1.0e-12};
int max_iters; int max_iters{1000};
}; };
struct rot { struct rot {
@@ -18,7 +18,7 @@ export namespace mean_field::utils {
double L; double L;
}; };
struct DomainMapperStatelessOptions { struct DomainMapperOptions {
int dimension{3}; int dimension{3};
int vacuum_element_attribute{3}; int vacuum_element_attribute{3};
}; };
@@ -31,7 +31,7 @@ export namespace mean_field::utils {
double index{}; double index{};
double mass{}; double mass{};
double c{}; double c{};
DomainMapperStatelessOptions domain_mapper_options{}; DomainMapperOptions domain_mapper_options{};
mapping::compactification::options::KelvinCompactificationOptions kelvin_options{}; mapping::compactification::options::KelvinCompactificationOptions kelvin_options{};
int max_iters{}; int max_iters{};
double tol{}; double tol{};

View File

@@ -1,6 +1,7 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <cmath>
#include <cstddef> #include <cstddef>
#include <mfem.hpp> #include <mfem.hpp>
#include <mpi.h> #include <mpi.h>
@@ -79,6 +80,11 @@ namespace field_dof_map_test_utils {
concept CanMakeFieldDofMap = concept CanMakeFieldDofMap =
requires(const mfem::ParFiniteElementSpace &space) { field::make_field_dof_map<FieldT, Schema>(space); }; requires(const mfem::ParFiniteElementSpace &space) { field::make_field_dof_map<FieldT, Schema>(space); };
template <typename FieldT>
concept CanMakeFieldDofGridFunctionAdapter = requires(const mfem::ParFiniteElementSpace &space) {
field::make_field_dof_grid_function_adapter<FieldT, Schema>(space);
};
using AlternateSchema = domain::DomainSchema< using AlternateSchema = domain::DomainSchema<
domain::MaterialList< domain::MaterialList<
domain::Material<domain::Core, 11>, domain::Material<domain::Core, 11>,
@@ -90,7 +96,7 @@ namespace field_dof_map_test_utils {
TEST_CASE( TEST_CASE(
"Field DOF Map Preserves Canonical Bidirectional Indexing", "Field DOF Map Preserves Canonical Bidirectional Indexing",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -166,7 +172,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Rejects Invalid Canonical Mappings", "Field DOF Map Rejects Invalid Canonical Mappings",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -191,7 +197,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Rejects Out Of Range Index Queries", "Field DOF Map Rejects Out Of Range Index Queries",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -212,7 +218,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Gather Selects Exactly The Active True DOFs", "Field DOF Map Gather Selects Exactly The Active True DOFs",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -247,7 +253,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Scatter Produces The Canonical Supported Projection", "Field DOF Map Scatter Produces The Canonical Supported Projection",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -281,7 +287,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Gather Scatter Projects A Full Vector Onto Field Support", "Field DOF Map Gather Scatter Projects A Full Vector Onto Field Support",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -310,7 +316,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Scatter Into Preserves Unsupported True DOFs", "Field DOF Map Scatter Into Preserves Unsupported True DOFs",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -337,7 +343,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Scatter Add Accumulates Only Onto Active True DOFs", "Field DOF Map Scatter Add Accumulates Only Onto Active True DOFs",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -364,7 +370,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Operations Support MFEM Vector Views Without Resizing", "Field DOF Map Operations Support MFEM Vector Views Without Resizing",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -406,7 +412,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Operations Reject Incompatible Vector Sizes", "Field DOF Map Operations Reject Incompatible Vector Sizes",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -433,7 +439,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Identity Mapping Is An Exact Vector Identity", "Field DOF Map Identity Mapping Is An Exact Vector Identity",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -463,7 +469,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Validates Field DOF Support Consistency", "Field DOF Map Validates Field DOF Support Consistency",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -495,7 +501,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Factory Is Available Only For Spatial Registered Fields", "Field DOF Map Factory Is Available Only For Spatial Registered Fields",
tags::unit &tags::field tags::field_dof_unit
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -509,12 +515,24 @@ TEST_CASE(
STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofMap<field::BarotropicConstant>); STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofMap<field::BarotropicConstant>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Density>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Enthalpy>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Gravity>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>);
STATIC_REQUIRE_FALSE(
field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>
);
CHECK(true); CHECK(true);
} }
TEST_CASE( TEST_CASE(
"Field DOF Map Factory Exactly Preserves Density Support", "Field DOF Map Factory Exactly Preserves Density Support",
tags::integration &tags::field tags::field_dof_integration
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -567,7 +585,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Factory Exactly Preserves H1 Enthalpy Support", "Field DOF Map Factory Exactly Preserves H1 Enthalpy Support",
tags::integration &tags::field tags::field_dof_integration
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -614,7 +632,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Factory Produces Identity Maps For All Supported Fields", "Field DOF Map Factory Produces Identity Maps For All Supported Fields",
tags::integration &tags::field tags::field_dof_integration
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -648,7 +666,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Factory Uses Schema Material Bindings Rather Than Numeric Conventions", "Field DOF Map Factory Uses Schema Material Bindings Rather Than Numeric Conventions",
tags::integration &tags::field tags::field_dof_integration
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -681,7 +699,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Field DOF Map Reduced Vectors Round Trip Through Real Field Support", "Field DOF Map Reduced Vectors Round Trip Through Real Field Support",
tags::integration &tags::field tags::field_dof_integration
) { ) {
namespace field = mean_field::field; namespace field = mean_field::field;
@@ -721,4 +739,246 @@ TEST_CASE(
CHECK(full(trueDof) == 0.0); CHECK(full(trueDof) == 0.0);
} }
} }
} }
TEST_CASE(
"Field DOF Grid Function Adapter Gathers Exactly The Supported True DOFs",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector full(adapter.dof_map().full_size());
for (int trueDof = 0; trueDof < full.Size(); ++trueDof) {
full(trueDof) = 1.25 + 0.375 * static_cast<double>(trueDof + 1);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction.SetFromTrueDofs(full);
const mfem::Vector expected = adapter.dof_map().gather(full);
const mfem::Vector actual = adapter.gather(gridFunction);
REQUIRE(actual.Size() == expected.Size());
for (int reducedDof = 0; reducedDof < actual.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(actual(reducedDof) == expected(reducedDof));
}
mfem::Vector output(adapter.dof_map().reduced_size());
adapter.gather(gridFunction, output);
for (int reducedDof = 0; reducedDof < output.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(output(reducedDof) == expected(reducedDof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Scatter Projects And Round Trips Reduced Fields",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Enthalpy, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
reduced(reducedDof) = -0.75 + 0.0625 * static_cast<double>(reducedDof + 1);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction = 91.0;
adapter.scatter(reduced, gridFunction);
mfem::Vector actualFull;
gridFunction.GetTrueDofs(actualFull);
const mfem::Vector expectedFull = adapter.dof_map().scatter(reduced);
REQUIRE(actualFull.Size() == expectedFull.Size());
for (int trueDof = 0; trueDof < actualFull.Size(); ++trueDof) {
CAPTURE(trueDof);
CHECK(actualFull(trueDof) == expectedFull(trueDof));
if (!adapter.dof_map().contains_true_dof(trueDof)) {
CHECK(actualFull(trueDof) == 0.0);
}
}
const mfem::Vector recovered = adapter.gather(gridFunction);
REQUIRE(recovered.Size() == reduced.Size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(recovered(reducedDof) == reduced(reducedDof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Scatter Into Preserves Unsupported True DOFs",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector initialFull(adapter.dof_map().full_size());
for (int trueDof = 0; trueDof < initialFull.Size(); ++trueDof) {
initialFull(trueDof) = 40.0 + static_cast<double>(trueDof);
}
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
reduced(reducedDof) = -10.0 - static_cast<double>(reducedDof);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction.SetFromTrueDofs(initialFull);
adapter.scatter_into(reduced, gridFunction);
mfem::Vector actualFull;
gridFunction.GetTrueDofs(actualFull);
mfem::Vector expectedFull(initialFull);
adapter.dof_map().scatter_into(reduced, expectedFull);
REQUIRE(actualFull.Size() == expectedFull.Size());
for (int trueDof = 0; trueDof < actualFull.Size(); ++trueDof) {
CAPTURE(trueDof);
CHECK(actualFull(trueDof) == expectedFull(trueDof));
if (!adapter.dof_map().contains_true_dof(trueDof)) {
CHECK(actualFull(trueDof) == initialFull(trueDof));
}
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Is Exact For Identity Vector Field Maps",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2);
auto finiteElementSpace =
field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Displacement, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
REQUIRE(adapter.dof_map().is_identity());
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int dof = 0; dof < reduced.Size(); ++dof) {
reduced(dof) = std::sin(0.23 * static_cast<double>(dof + 1));
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
adapter.scatter(reduced, gridFunction);
const mfem::Vector recovered = adapter.gather(gridFunction);
REQUIRE(recovered.Size() == reduced.Size());
for (int dof = 0; dof < reduced.Size(); ++dof) {
CAPTURE(dof);
CHECK(recovered(dof) == reduced(dof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Rejects Incompatible Maps Spaces And Vectors",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto otherFiniteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
REQUIRE(finiteElementSpace != nullptr);
REQUIRE(otherFiniteElementSpace != nullptr);
REQUIRE(finiteElementSpace->GetTrueVSize() == otherFiniteElementSpace->GetTrueVSize());
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
const mfem::Array<int> empty;
CHECK_THROWS_AS(
(field::FieldDofGridFunctionAdapter(
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty),
*finiteElementSpace
)),
std::invalid_argument
);
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
mfem::ParGridFunction otherGridFunction(otherFiniteElementSpace.get());
mfem::Vector reduced(adapter.dof_map().reduced_size());
reduced = 1.0;
mfem::Vector wrongReduced(adapter.dof_map().reduced_size() + 1);
mfem::Vector wrongOutput(adapter.dof_map().reduced_size() + 1);
CHECK_THROWS_AS(adapter.gather(otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter(reduced, otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter_into(reduced, otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.gather(gridFunction, wrongOutput), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter(wrongReduced, gridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter_into(wrongReduced, gridFunction), std::invalid_argument);
}

View File

@@ -9,6 +9,20 @@ import test_helpers;
using namespace mean_field; using namespace mean_field;
namespace { 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( double compute_roche_surface_scale(
const double rotation_fraction, const double rotation_fraction,
const double sine_theta_squared const double sine_theta_squared
@@ -29,7 +43,7 @@ namespace {
TEST_CASE( TEST_CASE(
"Centrifugal Integrator Matches Manufactured Cartesian Load", "Centrifugal Integrator Matches Manufactured Cartesian Load",
tags::unit &tags::solver &tags::integrator &tags::centrifugal tags::rotation_integrator_unit
) { ) {
constexpr int dim = 3; constexpr int dim = 3;
constexpr double density = 1.7; constexpr double density = 1.7;
@@ -49,13 +63,15 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0); SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim); mfem::Vector omega(dim);
omega = 0.0; omega = 0.0;
omega(2) = omega_value; omega(2) = omega_value;
integrators::CentrifugalForceIntegrator integrator(domain_mapper, omega); integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0);
@@ -66,8 +82,7 @@ TEST_CASE(
quadrature::Policy policy(std::move(rule_set)); quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy)); quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
!domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder(); const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal( quadrature_factory.configure_centrifugal(
@@ -127,7 +142,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Centrifugal Integrator Jacobian Matches Residual Linearization", "Centrifugal Integrator Jacobian Matches Residual Linearization",
tags::unit &tags::solver &tags::integrator &tags::centrifugal tags::rotation_integrator_unit
) { ) {
constexpr int dim = 3; constexpr int dim = 3;
constexpr double step = 1.0e-6; constexpr double step = 1.0e-6;
@@ -147,14 +162,16 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0); SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim); mfem::Vector omega(dim);
omega(0) = 0.7; omega(0) = 0.7;
omega(1) = -1.1; omega(1) = -1.1;
omega(2) = 1.6; omega(2) = 1.6;
integrators::CentrifugalForceIntegrator integrator(domain_mapper, omega); integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0);
@@ -165,8 +182,7 @@ TEST_CASE(
quadrature::Policy policy(std::move(rule_set)); quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy)); quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
!domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder(); const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal( quadrature_factory.configure_centrifugal(
@@ -287,7 +303,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Centrifugal Integrator Preserves Rotation Identities", "Centrifugal Integrator Preserves Rotation Identities",
tags::unit &tags::solver &tags::integrator &tags::centrifugal tags::rotation_integrator_unit
) { ) {
constexpr int dim = 3; constexpr int dim = 3;
constexpr double density = 1.4; constexpr double density = 1.4;
@@ -307,14 +323,16 @@ TEST_CASE(
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0); SerialMappingData mapping_data(mesh);
mfem::Vector omega(dim); mfem::Vector omega(dim);
omega(0) = 0.7; omega(0) = 0.7;
omega(1) = -1.1; omega(1) = -1.1;
omega(2) = 1.6; omega(2) = 1.6;
integrators::CentrifugalForceIntegrator integrator(domain_mapper, omega); integrators::CentrifugalForceIntegrator integrator(
mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega
);
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0);
@@ -325,8 +343,7 @@ TEST_CASE(
quadrature::Policy policy(std::move(rule_set)); quadrature::Policy policy(std::move(rule_set));
quadrature::RuleFactory quadrature_factory(std::move(policy)); quadrature::RuleFactory quadrature_factory(std::move(policy));
const quadrature::MappingKind mapping_kind = const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
!domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general;
const int position_order = displacement_element->GetOrder(); const int position_order = displacement_element->GetOrder();
quadrature_factory.configure_centrifugal( quadrature_factory.configure_centrifugal(
@@ -424,7 +441,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Centrifugal Integrator Matches Rotational Virial On Roche Mappings", "Centrifugal Integrator Matches Rotational Virial On Roche Mappings",
tags::integration &tags::solver &tags::integrator &tags::centrifugal tags::rotation_integrator_integration
) { ) {
auto args = test_utils::setup_args(); auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
@@ -486,16 +503,20 @@ TEST_CASE(
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
displacement.ProjectCoefficient(displacement_coefficient); displacement.ProjectCoefficient(displacement_coefficient);
f.mapping->SetDisplacement(displacement); *f.displacement = displacement;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim); mfem::Vector omega(dim);
omega = 0.0; omega = 0.0;
omega(2) = rotation_fraction; omega(2) = rotation_fraction;
integrators::CentrifugalForceIntegrator integrator(*f.mapping, omega); integrators::CentrifugalForceIntegrator integrator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate, omega
);
const quadrature::MappingKind mapping_kind = const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general;
!f.mapping->HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general;
f.quadratureFactory->configure_centrifugal( f.quadratureFactory->configure_centrifugal(
integrator, quadrature::QuadratureRole::discretization, representative_density_element, integrator, quadrature::QuadratureRole::discretization, representative_density_element,
representative_velocity_element, representative_transformation, position_order, utils::DOMAINS::STELLAR, representative_velocity_element, representative_transformation, position_order, utils::DOMAINS::STELLAR,
@@ -560,7 +581,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) { for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node); transformation->SetIntPoint(&node);
f.mapping->GetPhysicalPoint(*transformation, node, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
for (int d = 0; d < dim; ++d) { for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); position_test_dofs(i + d * velocity_dofs_count) = x_physical(d);
@@ -581,14 +602,14 @@ TEST_CASE(
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point); transformation->SetIntPoint(&integration_point);
const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point);
const mapping::VolumeQuadratureContext context = const mapping::VolumeQuadratureContext context =
f.mapping->GetQuadratureContext(*transformation, integration_point); mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
const double signed_map_determinant = context.detJ;
local_minimum_map_determinant = std::min(local_minimum_map_determinant, signed_map_determinant); 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); local_maximum_map_determinant = std::max(local_maximum_map_determinant, signed_map_determinant);
f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape); velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0; position_test_value = 0.0;
@@ -661,13 +682,13 @@ TEST_CASE(
CHECK_THAT(relative_position_error, Catch::Matchers::WithinAbs(0.0, position_tolerance)); CHECK_THAT(relative_position_error, Catch::Matchers::WithinAbs(0.0, position_tolerance));
} }
f.mapping->ResetDisplacement(); *f.displacement = 0.0;
} }
TEST_CASE( TEST_CASE(
"Centrifugal Virial Position Representation Is Consistent At The " "Centrifugal Virial Position Representation Is Consistent At The "
"Registered Order", "Registered Order",
tags::integration &tags::solver &tags::integrator &tags::centrifugal tags::rotation_integrator_integration
) { ) {
constexpr int dim = 3; constexpr int dim = 3;
constexpr double concentration = 4.0; constexpr double concentration = 4.0;
@@ -728,7 +749,10 @@ TEST_CASE(
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
displacement.ProjectCoefficient(displacement_coefficient); displacement.ProjectCoefficient(displacement_coefficient);
f.mapping->SetDisplacement(displacement); *f.displacement = displacement;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim); mfem::Vector omega(dim);
omega = 0.0; omega = 0.0;
@@ -761,7 +785,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) { for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node); transformation->SetIntPoint(&node);
f.mapping->GetPhysicalPoint(*transformation, node, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
for (int d = 0; d < dim; ++d) { for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); position_test_dofs(i + d * velocity_dofs_count) = x_physical(d);
@@ -780,13 +804,13 @@ TEST_CASE(
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point); transformation->SetIntPoint(&integration_point);
const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point);
const mapping::VolumeQuadratureContext context = const mapping::VolumeQuadratureContext context =
f.mapping->GetQuadratureContext(*transformation, integration_point); mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
const double signed_map_determinant = context.detJ;
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant); local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape); velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0; position_test_value = 0.0;
@@ -833,7 +857,7 @@ TEST_CASE(
minimum_determinants[rotation_index][order_index] = global_minimum_determinant; minimum_determinants[rotation_index][order_index] = global_minimum_determinant;
} }
f.mapping->ResetDisplacement(); *f.displacement = 0.0;
} }
for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) {
@@ -853,7 +877,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Centrifugal Virial Position Representation Converges Under H Refinement", "Centrifugal Virial Position Representation Converges Under H Refinement",
tags::integration &tags::solver &tags::integrator &tags::convergence &tags::h_refinement &tags::centrifugal tags::rotation_integrator_convergence
) { ) {
constexpr int dim = 3; constexpr int dim = 3;
constexpr double concentration = 4.0; constexpr double concentration = 4.0;
@@ -916,7 +940,10 @@ TEST_CASE(
mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement);
displacement.ProjectCoefficient(displacement_coefficient); displacement.ProjectCoefficient(displacement_coefficient);
f.mapping->SetDisplacement(displacement); *f.displacement = displacement;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate
);
mfem::Vector omega(dim); mfem::Vector omega(dim);
omega = 0.0; omega = 0.0;
@@ -949,7 +976,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) { for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node); transformation->SetIntPoint(&node);
f.mapping->GetPhysicalPoint(*transformation, node, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
for (int d = 0; d < dim; ++d) { for (int d = 0; d < dim; ++d) {
position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); position_test_dofs(i + d * velocity_dofs_count) = x_physical(d);
@@ -968,13 +995,13 @@ TEST_CASE(
const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point); transformation->SetIntPoint(&integration_point);
const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point);
const mapping::VolumeQuadratureContext context = const mapping::VolumeQuadratureContext context =
f.mapping->GetQuadratureContext(*transformation, integration_point); mapping_evaluator.GetQuadratureContext(*transformation, integration_point);
const double signed_map_determinant = context.detJ;
local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant); local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant);
f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical);
velocity_element->CalcShape(integration_point, velocity_shape); velocity_element->CalcShape(integration_point, velocity_shape);
position_test_value = 0.0; position_test_value = 0.0;
@@ -1021,7 +1048,7 @@ TEST_CASE(
minimum_determinants[rotation_index][refinement_index] = global_minimum_determinant; minimum_determinants[rotation_index][refinement_index] = global_minimum_determinant;
} }
f.mapping->ResetDisplacement(); *f.displacement = 0.0;
} }
for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) {

View File

@@ -10,7 +10,7 @@ using namespace mean_field;
TEST_CASE( TEST_CASE(
"Gravity Force Integrator Jacobian Matches Residual Linearization", "Gravity Force Integrator Jacobian Matches Residual Linearization",
tags::unit &tags::solver &tags::integrator &tags::gravity tags::gravity_integrator_unit
) { ) {
constexpr int dim = 3; constexpr int dim = 3;
constexpr double finite_difference_step = 1.0e-3; constexpr double finite_difference_step = 1.0e-3;
@@ -31,21 +31,20 @@ TEST_CASE(
mfem::RT_FECollection gravity_gradient_fec(1, dim); mfem::RT_FECollection gravity_gradient_fec(1, dim);
mfem::L2_FECollection gravity_potential_fec(1, dim); mfem::L2_FECollection gravity_potential_fec(1, dim);
mfem::H1_FECollection displacement_fec(2, dim); mfem::H1_FECollection displacement_fec(2, dim);
mfem::H1_FECollection compactification_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec); mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
mfem::FiniteElementSpace gravity_potential_fes(&mesh, &gravity_potential_fec); mfem::FiniteElementSpace gravity_potential_fes(&mesh, &gravity_potential_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec);
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mfem::GridFunction compactification_coordinate(&compactification_fes);
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0); compactification_coordinate = 0.0;
INFO(std::format("Domain mapping is has displacement field: {}", domain_mapper.HasDisplacementField())); mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
INFO(std::format("Domain mapping is identity: {}", domain_mapper.CalcIsIdentity()));
REQUIRE(domain_mapper.CalcIsIdentity());
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0);
@@ -130,7 +129,8 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, integrators::GravityForceJacobianMode::field_coupled domain_mapper, displacement, compactification_coordinate,
integrators::GravityForceJacobianMode::field_coupled
); );
const int maximum_order = std::max( const int maximum_order = std::max(
@@ -268,7 +268,7 @@ TEST_CASE(
TEST_CASE( TEST_CASE(
"Gravity Force Integrator Matches Manufactured Cartesian Load", "Gravity Force Integrator Matches Manufactured Cartesian Load",
tags::unit &tags::solver &tags::integrator &tags::gravity tags::gravity_integrator_unit
) { ) {
constexpr int dim = 3; constexpr int dim = 3;
constexpr double tolerance = 1.0e-12; constexpr double tolerance = 1.0e-12;
@@ -286,18 +286,23 @@ TEST_CASE(
mfem::L2_FECollection density_fec(1, dim); mfem::L2_FECollection density_fec(1, dim);
mfem::RT_FECollection gravity_gradient_fec(0, dim); mfem::RT_FECollection gravity_gradient_fec(0, dim);
mfem::H1_FECollection displacement_fec(1, dim); mfem::H1_FECollection displacement_fec(1, dim);
mfem::H1_FECollection compactification_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec); mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec);
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mfem::GridFunction compactification_coordinate(&compactification_fes);
compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0); mapping::GridFunctionMappingEvaluator mapping_evaluator(
domain_mapper, displacement, compactification_coordinate
REQUIRE(domain_mapper.CalcIsIdentity()); );
auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); }; auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); };
@@ -376,7 +381,8 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, integrators::GravityForceJacobianMode::field_coupled domain_mapper, displacement, compactification_coordinate,
integrators::GravityForceJacobianMode::field_coupled
); );
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
@@ -395,7 +401,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) { for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node); transformation->SetIntPoint(&node);
domain_mapper.GetPhysicalPoint(*transformation, node, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
test_dofs(i + component * velocity_dofs_count) = test_dofs(i + component * velocity_dofs_count) =
coordinate_weight < 0 ? 1.0 : x_physical(coordinate_weight); coordinate_weight < 0 ? 1.0 : x_physical(coordinate_weight);
} }
@@ -433,7 +439,7 @@ TEST_CASE(
} }
TEST_CASE( TEST_CASE(
"Gravity Force Integrator Preserves Gravity Identities", "Gravity Force Integrator Preserves Gravity Identities",
tags::unit &tags::solver &tags::integrator &tags::gravity tags::gravity_integrator_unit
) { ) {
constexpr int dim = 3; constexpr int dim = 3;
constexpr double density_value = 1.7; constexpr double density_value = 1.7;
@@ -454,18 +460,22 @@ TEST_CASE(
mfem::L2_FECollection density_fec(0, dim); mfem::L2_FECollection density_fec(0, dim);
mfem::RT_FECollection gravity_gradient_fec(0, dim); mfem::RT_FECollection gravity_gradient_fec(0, dim);
mfem::H1_FECollection displacement_fec(1, dim); mfem::H1_FECollection displacement_fec(1, dim);
mfem::H1_FECollection compactification_fec(1, dim);
mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec); mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM);
mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec);
mfem::GridFunction displacement(&displacement_fes); mfem::GridFunction displacement(&displacement_fes);
displacement = 0.0; displacement = 0.0;
mfem::GridFunction compactification_coordinate(&compactification_fes);
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0); compactification_coordinate = 0.0;
mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper();
REQUIRE(domain_mapper.HasDisplacementField()); mapping::GridFunctionMappingEvaluator mapping_evaluator(
domain_mapper, displacement, compactification_coordinate
);
auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) { auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) {
gravity.SetSize(3); gravity.SetSize(3);
@@ -543,7 +553,8 @@ TEST_CASE(
element_residual[displacement_block] = &displacement_residual; element_residual[displacement_block] = &displacement_residual;
integrators::GravityMomentumIntegrator integrator( integrators::GravityMomentumIntegrator integrator(
domain_mapper, integrators::GravityForceJacobianMode::field_coupled domain_mapper, displacement, compactification_coordinate,
integrators::GravityForceJacobianMode::field_coupled
); );
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
@@ -609,7 +620,7 @@ TEST_CASE(
for (int i = 0; i < velocity_dofs_count; ++i) { for (int i = 0; i < velocity_dofs_count; ++i) {
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
transformation->SetIntPoint(&node); transformation->SetIntPoint(&node);
domain_mapper.GetPhysicalPoint(*transformation, node, x_physical); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical);
for (int component = 0; component < dim; ++component) { for (int component = 0; component < dim; ++component) {
centered_position(component) = x_physical(component) - 0.5; centered_position(component) = x_physical(component) - 0.5;
@@ -714,4 +725,4 @@ TEST_CASE(
Catch::Matchers::WithinAbs(0.0, tolerance) Catch::Matchers::WithinAbs(0.0, tolerance)
); );
CHECK_THAT(zero_density_field_action.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(zero_density_field_action.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance));
} }

File diff suppressed because it is too large Load Diff

View File

@@ -43,11 +43,18 @@ TEST_CASE(
CHECK(initial_report.geometry.reconstructed_operators); CHECK(initial_report.geometry.reconstructed_operators);
CHECK(initial_report.geometry.rebuilt_mass_operator); CHECK(initial_report.geometry.rebuilt_mass_operator);
CHECK(initial_report.geometry.rebuilt_source_operator); CHECK(initial_report.geometry.rebuilt_source_operator);
CHECK(initial_report.geometry.rebuilt_divergence_operator);
CHECK(initial_report.geometry.refreshed_variation_state); CHECK(initial_report.geometry.refreshed_variation_state);
CHECK(initial_report.updated_density); CHECK(initial_report.updated_density);
CHECK(initial_report.updated_gravity_gradient); CHECK(initial_report.updated_gravity_gradient);
CHECK(initial_report.DidAnyWork()); CHECK(initial_report.DidAnyWork());
const auto &geometry_context = context.GetGeometryContext();
CHECK(geometry_context.GetDivergenceOperator().Width() == f.gravityFluxFes->GetTrueVSize());
CHECK(geometry_context.GetDivergenceOperator().Height() == f.gravityPotentialFes->GetTrueVSize());
CHECK(geometry_context.GetTransposeDivergenceOperator().Width() == f.gravityPotentialFes->GetTrueVSize());
CHECK(geometry_context.GetTransposeDivergenceOperator().Height() == f.gravityFluxFes->GetTrueVSize());
const auto initial_mass_preparations = context.GetGeometryContext().GetMassOperator().GetPreparationCount(); const auto initial_mass_preparations = context.GetGeometryContext().GetMassOperator().GetPreparationCount();
const auto initial_source_preparations = context.GetGeometryContext().GetSourceOperator().GetPreparationCount(); const auto initial_source_preparations = context.GetGeometryContext().GetSourceOperator().GetPreparationCount();
@@ -93,6 +100,7 @@ TEST_CASE(
CHECK_FALSE(displacement_report.geometry.reconstructed_operators); CHECK_FALSE(displacement_report.geometry.reconstructed_operators);
CHECK(displacement_report.geometry.rebuilt_mass_operator); CHECK(displacement_report.geometry.rebuilt_mass_operator);
CHECK(displacement_report.geometry.rebuilt_source_operator); CHECK(displacement_report.geometry.rebuilt_source_operator);
CHECK_FALSE(displacement_report.geometry.rebuilt_divergence_operator);
CHECK(displacement_report.geometry.refreshed_variation_state); CHECK(displacement_report.geometry.refreshed_variation_state);
CHECK_FALSE(displacement_report.updated_density); CHECK_FALSE(displacement_report.updated_density);
CHECK_FALSE(displacement_report.updated_gravity_gradient); CHECK_FALSE(displacement_report.updated_gravity_gradient);
@@ -107,6 +115,7 @@ TEST_CASE(
CHECK(discretization_report.geometry.reconstructed_operators); CHECK(discretization_report.geometry.reconstructed_operators);
CHECK(discretization_report.geometry.rebuilt_mass_operator); CHECK(discretization_report.geometry.rebuilt_mass_operator);
CHECK(discretization_report.geometry.rebuilt_source_operator); CHECK(discretization_report.geometry.rebuilt_source_operator);
CHECK(discretization_report.geometry.rebuilt_divergence_operator);
CHECK(discretization_report.updated_density); CHECK(discretization_report.updated_density);
CHECK(discretization_report.updated_gravity_gradient); CHECK(discretization_report.updated_gravity_gradient);
CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == 1); CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == 1);

File diff suppressed because it is too large Load Diff

View File

@@ -11,371 +11,386 @@ import mean_field;
import test_helpers; import test_helpers;
namespace gravity_displacement_force_analytic_test_utils { namespace gravity_displacement_force_analytic_test_utils {
struct AffineCase { struct AffineCase {
const char *name; const char *name;
std::array<double, 3> scales; std::array<double, 3> scales;
}; };
[[nodiscard]] double analytic_sphere_volume(const double radius) { [[nodiscard]] double analytic_sphere_volume(const double radius) {
return (4.0 / 3.0) * std::numbers::pi * radius * radius * radius; return (4.0 / 3.0) * std::numbers::pi * radius * radius * radius;
} }
[[nodiscard]] double determinant( [[nodiscard]] double determinant(const std::array<double, 3> &scales) {
const std::array< return scales[0] * scales[1] * scales[2];
double, }
3> &scales
) {
return scales[0] * scales[1] * scales[2];
}
[[nodiscard]] double relative_scalar_error( [[nodiscard]] double relative_scalar_error(const double computed,
const double computed, const double expected) {
const double expected return std::abs(computed - expected) / std::max(std::abs(expected), 1.0e-30);
) { }
return std::abs(computed - expected) / std::max(std::abs(expected), 1.0e-30);
}
[[nodiscard]] mfem::Vector make_constant_density( [[nodiscard]] mfem::Vector make_constant_density(const mean_field::fem::FEM &f,
const mean_field::fem::FEM &f, const double densityValue) {
const double densityValue mfem::ParGridFunction densityField(f.densityFes.get());
) { mfem::ConstantCoefficient densityCoefficient(densityValue);
mfem::ParGridFunction densityField(f.densityFes.get()); densityField.ProjectCoefficient(densityCoefficient);
mfem::ConstantCoefficient densityCoefficient(densityValue);
densityField.ProjectCoefficient(densityCoefficient);
mfem::Vector densityTrue; mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue); densityField.GetTrueDofs(densityTrue);
return densityTrue; return densityTrue;
} }
[[nodiscard]] mfem::Vector make_reference_gravity( [[nodiscard]] mfem::Vector
const mean_field::fem::FEM &f, make_reference_gravity(const mean_field::fem::FEM &f,
const std::array< const std::array<double, 3> &referenceGravity) {
double, mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
3> &referenceGravity
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
mfem::VectorFunctionCoefficient gravityCoefficient( mfem::VectorFunctionCoefficient gravityCoefficient(
f.mesh->Dimension(), [referenceGravity](const mfem::Vector &, mfem::Vector &value) { f.mesh->Dimension(),
value.SetSize(3); [referenceGravity](const mfem::Vector &, mfem::Vector &value) {
value.SetSize(3);
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
value(component) = referenceGravity[static_cast<std::size_t>(component)]; value(component) =
} referenceGravity[static_cast<std::size_t>(component)];
} }
); });
gravityField.ProjectCoefficient(gravityCoefficient); gravityField.ProjectCoefficient(gravityCoefficient);
mfem::Vector gravityTrue; mfem::Vector gravityTrue;
gravityField.GetTrueDofs(gravityTrue); gravityField.GetTrueDofs(gravityTrue);
return gravityTrue; return gravityTrue;
} }
[[nodiscard]] mfem::Vector make_radial_gravity( [[nodiscard]] mfem::Vector make_radial_gravity(const mean_field::fem::FEM &f,
const mean_field::fem::FEM &f, const double radialCoefficient) {
const double radialCoefficient mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
) {
mfem::ParGridFunction gravityField(f.gravityFluxFes.get());
mfem::VectorFunctionCoefficient gravityCoefficient( mfem::VectorFunctionCoefficient gravityCoefficient(
f.mesh->Dimension(), [radialCoefficient](const mfem::Vector &position, mfem::Vector &value) { f.mesh->Dimension(),
value.SetSize(position.Size()); [radialCoefficient](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int component = 0; component < position.Size(); ++component) { for (int component = 0; component < position.Size(); ++component) {
value(component) = radialCoefficient * position(component); value(component) = radialCoefficient * position(component);
} }
} });
);
gravityField.ProjectCoefficient(gravityCoefficient); gravityField.ProjectCoefficient(gravityCoefficient);
mfem::Vector gravityTrue; mfem::Vector gravityTrue;
gravityField.GetTrueDofs(gravityTrue); gravityField.GetTrueDofs(gravityTrue);
return gravityTrue; return gravityTrue;
} }
[[nodiscard]] mfem::Vector make_affine_displacement( [[nodiscard]] mfem::Vector
const mean_field::fem::FEM &f, make_affine_displacement(const mean_field::fem::FEM &f,
const std::array< const std::array<double, 3> &scales) {
double, mfem::ParGridFunction displacementField(f.displacementFes.get());
3> &scales
) {
mfem::ParGridFunction displacementField(f.displacementFes.get());
mfem::VectorFunctionCoefficient displacementCoefficient( mfem::VectorFunctionCoefficient displacementCoefficient(
f.mesh->Dimension(), [scales](const mfem::Vector &position, mfem::Vector &value) { f.mesh->Dimension(),
value.SetSize(position.Size()); [scales](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int component = 0; component < position.Size(); ++component) { for (int component = 0; component < position.Size(); ++component) {
value(component) = (scales[static_cast<std::size_t>(component)] - 1.0) * position(component); value(component) =
} (scales[static_cast<std::size_t>(component)] - 1.0) *
} position(component);
); }
});
displacementField.ProjectCoefficient(displacementCoefficient); displacementField.ProjectCoefficient(displacementCoefficient);
mfem::Vector displacementTrue; mfem::Vector displacementTrue;
displacementField.GetTrueDofs(displacementTrue); displacementField.GetTrueDofs(displacementTrue);
return displacementTrue; return displacementTrue;
} }
[[nodiscard]] mfem::Vector make_constant_test_direction( [[nodiscard]] mfem::Vector
const mean_field::fem::FEM &f, make_constant_test_direction(const mean_field::fem::FEM &f,
const int selectedComponent const int selectedComponent) {
) { mfem::ParGridFunction testField(f.displacementFes.get());
mfem::ParGridFunction testField(f.displacementFes.get());
mfem::VectorFunctionCoefficient testCoefficient( mfem::VectorFunctionCoefficient testCoefficient(
f.mesh->Dimension(), [selectedComponent](const mfem::Vector &position, mfem::Vector &value) { f.mesh->Dimension(),
value.SetSize(position.Size()); [selectedComponent](const mfem::Vector &position, mfem::Vector &value) {
value = 0.0; value.SetSize(position.Size());
value(selectedComponent) = 1.0; value = 0.0;
} value(selectedComponent) = 1.0;
); });
testField.ProjectCoefficient(testCoefficient); testField.ProjectCoefficient(testCoefficient);
mfem::Vector testTrue; mfem::Vector testTrue;
testField.GetTrueDofs(testTrue); testField.GetTrueDofs(testTrue);
return testTrue; return testTrue;
} }
[[nodiscard]] mfem::Vector make_dilation_test_direction(const mean_field::fem::FEM &f) { [[nodiscard]] mfem::Vector
mfem::ParGridFunction testField(f.displacementFes.get()); make_dilation_test_direction(const mean_field::fem::FEM &f) {
mfem::ParGridFunction testField(f.displacementFes.get());
mfem::VectorFunctionCoefficient testCoefficient( mfem::VectorFunctionCoefficient testCoefficient(
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) { value = position; } f.mesh->Dimension(), [](const mfem::Vector &position,
); mfem::Vector &value) { value = position; });
testField.ProjectCoefficient(testCoefficient); testField.ProjectCoefficient(testCoefficient);
mfem::Vector testTrue; mfem::Vector testTrue;
testField.GetTrueDofs(testTrue); testField.GetTrueDofs(testTrue);
return testTrue; return testTrue;
} }
void set_mass_normalized_density( void set_mass_normalized_density(mean_field::fem::FEM &f,
mean_field::fem::FEM &f, const double targetMass,
const double targetMass, mfem::ParGridFunction &densityField) {
mfem::ParGridFunction &densityField const mfem::Vector stellarDensityTrue =
) { gravity_prepared_test_utils::make_domain_supported_density(f, true);
const mfem::Vector stellarDensityTrue = gravity_prepared_test_utils::make_domain_supported_density(f, true);
densityField.SetFromTrueDofs(stellarDensityTrue); densityField.SetFromTrueDofs(stellarDensityTrue);
const double unnormalizedMass = const double unnormalizedMass =
mean_field::analysis::domain_integrate_grid_function(f, densityField, mean_field::utils::DOMAINS::STELLAR); mean_field::analysis::domain_integrate_grid_function(
f, densityField, mean_field::utils::DOMAINS::STELLAR);
MFEM_VERIFY(unnormalizedMass > 0.0, "The analytic gravity-force test obtained non-positive mass."); MFEM_VERIFY(unnormalizedMass > 0.0,
"The analytic gravity-force test obtained non-positive mass.");
densityField *= targetMass / unnormalizedMass; densityField *= targetMass / unnormalizedMass;
} }
} // namespace gravity_displacement_force_analytic_test_utils } // namespace gravity_displacement_force_analytic_test_utils
TEST_CASE( TEST_CASE("Gravity Displacement Force Matches Analytic Affine Resultants",
"Gravity Displacement Force Matches Analytic Affine Resultants", tags::gravity &tags::accuracy &tags::analytic_comparison
tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration &tags::integration) {
) { mean_field::utils::Args args = test_utils::setup_args();
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay()); REQUIRE(f.okay());
REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
REQUIRE(f.mapping != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
constexpr double densityValue = 1.37; constexpr double densityValue = 1.37;
constexpr std::array<double, 3> physicalGravity{0.31, -0.47, 0.22}; constexpr std::array<double, 3> physicalGravity{0.31, -0.47, 0.22};
constexpr std::array<gravity_displacement_force_analytic_test_utils::AffineCase, 3> affineCases{ constexpr std::array<
{{.name = "identity geometry", .scales = {1.0, 1.0, 1.0}}, gravity_displacement_force_analytic_test_utils::AffineCase, 3>
{.name = "volume-preserving affine geometry", .scales = {1.14, 0.93, 1.0 / (1.14 * 0.93)}}, affineCases{{{.name = "identity geometry", .scales = {1.0, 1.0, 1.0}},
{.name = "volume-changing affine geometry", .scales = {1.11, 0.96, 1.07}}} {.name = "volume-preserving affine geometry",
}; .scales = {1.14, 0.93, 1.0 / (1.14 * 0.93)}},
{.name = "volume-changing affine geometry",
.scales = {1.11, 0.96, 1.07}}}};
const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue); const mfem::Vector density =
gravity_displacement_force_analytic_test_utils::make_constant_density(
f, densityValue);
const double referenceVolume = const double referenceVolume =
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(mean_field::utils::RADIUS); gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(
mean_field::utils::RADIUS);
constexpr double relativeTolerance = 5.0e-6; constexpr double relativeTolerance = 5.0e-6;
for (const gravity_displacement_force_analytic_test_utils::AffineCase &affineCase : affineCases) { for (const gravity_displacement_force_analytic_test_utils::AffineCase
DYNAMIC_SECTION(affineCase.name) { &affineCase : affineCases) {
const double mapDeterminant = DYNAMIC_SECTION(affineCase.name) {
gravity_displacement_force_analytic_test_utils::determinant(affineCase.scales); const double mapDeterminant =
gravity_displacement_force_analytic_test_utils::determinant(
affineCase.scales);
REQUIRE(mapDeterminant > 0.0); REQUIRE(mapDeterminant > 0.0);
std::array<double, 3> referenceGravity{}; std::array<double, 3> referenceGravity{};
/* /*
* For x = A X, the H(div) Piola relation is * For x = A X, the H(div) Piola relation is
* *
* g_phys = A g_ref / det(A). * g_phys = A g_ref / det(A).
* *
* Prescribe the RT pullback that represents the requested * Prescribe the RT pullback that represents the requested
* constant physical gravity field exactly. * constant physical gravity field exactly.
*/ */
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
referenceGravity[static_cast<std::size_t>(component)] = referenceGravity[static_cast<std::size_t>(component)] =
mapDeterminant * physicalGravity[static_cast<std::size_t>(component)] / mapDeterminant *
affineCase.scales[static_cast<std::size_t>(component)]; physicalGravity[static_cast<std::size_t>(component)] /
} affineCase.scales[static_cast<std::size_t>(component)];
}
const mfem::Vector gravityGradient = const mfem::Vector gravityGradient =
gravity_displacement_force_analytic_test_utils::make_reference_gravity(f, referenceGravity); gravity_displacement_force_analytic_test_utils::
make_reference_gravity(f, referenceGravity);
const mfem::Vector displacement = const mfem::Vector displacement =
gravity_displacement_force_analytic_test_utils::make_affine_displacement(f, affineCase.scales); gravity_displacement_force_analytic_test_utils::
make_affine_displacement(f, affineCase.scales);
mfem::Vector residual; mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual( mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual f, *f.domainMapperStateless, density, gravityGradient, displacement,
); residual);
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
const mfem::Vector testDirection = const mfem::Vector testDirection =
gravity_displacement_force_analytic_test_utils::make_constant_test_direction(f, component); gravity_displacement_force_analytic_test_utils::
make_constant_test_direction(f, component);
const double computedResultant = const double computedResultant =
gravity_prepared_test_utils::global_dot(residual, testDirection, f.mesh->GetComm()); gravity_prepared_test_utils::global_dot(residual, testDirection,
f.mesh->GetComm());
const double expectedResultant = densityValue * physicalGravity[static_cast<std::size_t>(component)] * const double expectedResultant =
mapDeterminant * referenceVolume; densityValue *
physicalGravity[static_cast<std::size_t>(component)] *
mapDeterminant * referenceVolume;
const double relativeError = gravity_displacement_force_analytic_test_utils::relative_scalar_error( const double relativeError =
computedResultant, expectedResultant gravity_displacement_force_analytic_test_utils::
); relative_scalar_error(computedResultant, expectedResultant);
CAPTURE(component); CAPTURE(component);
INFO("Map determinant = " << mapDeterminant); INFO("Map determinant = " << mapDeterminant);
INFO("Computed resultant = " << computedResultant); INFO("Computed resultant = " << computedResultant);
INFO("Analytic resultant = " << expectedResultant); INFO("Analytic resultant = " << expectedResultant);
INFO("Relative resultant error = " << relativeError); INFO("Relative resultant error = " << relativeError);
CHECK(relativeError < relativeTolerance); CHECK(relativeError < relativeTolerance);
} }
}
} }
}
} }
TEST_CASE( TEST_CASE(
"Gravity Displacement Force Reproduces Analytic Homogeneous Sphere Work", "Gravity Displacement Force Reproduces Analytic Homogeneous Sphere Work",
tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration tags::gravity &tags::accuracy &tags::analytic_comparison
) { &tags::integration) {
mean_field::utils::Args args = test_utils::setup_args(); mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay()); REQUIRE(f.okay());
REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
const double radius = mean_field::utils::RADIUS; const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS; const double mass = mean_field::utils::MASS;
const double volume = gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(radius); const double volume =
gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(
radius);
const double densityValue = mass / volume; const double densityValue = mass / volume;
const double radialGravityCoefficient = mean_field::utils::G * mass / (radius * radius * radius); const double radialGravityCoefficient =
mean_field::utils::G * mass / (radius * radius * radius);
const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue); const mfem::Vector density =
gravity_displacement_force_analytic_test_utils::make_constant_density(
f, densityValue);
const mfem::Vector gravityGradient = const mfem::Vector gravityGradient =
gravity_displacement_force_analytic_test_utils::make_radial_gravity(f, radialGravityCoefficient); gravity_displacement_force_analytic_test_utils::make_radial_gravity(
f, radialGravityCoefficient);
mfem::Vector displacement(f.displacementFes->GetTrueVSize()); mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0; displacement = 0.0;
mfem::Vector residual; mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual( mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual f, *f.domainMapperStateless, density, gravityGradient, displacement,
); residual);
const mfem::Vector dilationDirection = const mfem::Vector dilationDirection =
gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f); gravity_displacement_force_analytic_test_utils::
make_dilation_test_direction(f);
const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm()); const double computedWork = gravity_prepared_test_utils::global_dot(
residual, dilationDirection, f.mesh->GetComm());
const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius; const double analyticWork =
(3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
const double relativeError = const double relativeError =
gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork); gravity_displacement_force_analytic_test_utils::relative_scalar_error(
computedWork, analyticWork);
INFO("Computed positive gravity work = " << computedWork); INFO("Computed positive gravity work = " << computedWork);
INFO("Analytic positive gravity work = " << analyticWork); INFO("Analytic positive gravity work = " << analyticWork);
INFO("Computed gravitational virial = " << -computedWork); INFO("Computed gravitational virial = " << -computedWork);
INFO("Analytic binding energy = " << -analyticWork); INFO("Analytic binding energy = " << -analyticWork);
INFO("Relative analytic work error = " << relativeError); INFO("Relative analytic work error = " << relativeError);
REQUIRE(computedWork > 0.0); REQUIRE(computedWork > 0.0);
CHECK(relativeError < 1.0e-5); CHECK(relativeError < 1.0e-5);
} }
TEST_CASE( TEST_CASE("Solved Homogeneous Sphere Gravity Force Matches Analytic Virial",
"Solved Homogeneous Sphere Gravity Force Matches Analytic Virial", tags::gravity &tags::accuracy &tags::analytic_comparison
tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration &tags::initialization &tags::integration &tags::initialization) {
) { mean_field::utils::Args args = test_utils::setup_args();
mean_field::utils::Args args = test_utils::setup_args(); args.p.rtol = 1.0e-13;
args.p.rtol = 1.0e-13; args.p.max_iters = std::max(args.p.max_iters, 1000);
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); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay()); REQUIRE(f.okay());
REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
mfem::ParGridFunction displacementField(f.displacementFes.get()); mfem::ParGridFunction displacementField(f.displacementFes.get());
displacementField = 0.0; displacementField = 0.0;
REQUIRE(f.mapping != nullptr); REQUIRE(f.domainMapperStateless != nullptr);
f.mapping->ResetDisplacement(); *f.displacement = 0.0;
mean_field::physics::update_stiffness_matrix(f);
const double radius = mean_field::utils::RADIUS; const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS; const double mass = mean_field::utils::MASS;
mfem::ParGridFunction densityField(f.densityFes.get()); mfem::ParGridFunction densityField(f.densityFes.get());
gravity_displacement_force_analytic_test_utils::set_mass_normalized_density(f, mass, densityField); gravity_displacement_force_analytic_test_utils::set_mass_normalized_density(
f, mass, densityField);
const mean_field::physics::GravitySolution gravitySolution = const mean_field::physics::GravitySolution gravitySolution =
mean_field::physics::grav_potential_new(f, args, densityField, displacementField); mean_field::physics::solve_gravity_field(f, args, densityField,
displacementField);
mfem::Vector densityTrue; mfem::Vector densityTrue;
mfem::Vector gravityGradientTrue; mfem::Vector gravityGradientTrue;
mfem::Vector displacementTrue; mfem::Vector displacementTrue;
densityField.GetTrueDofs(densityTrue); densityField.GetTrueDofs(densityTrue);
gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue); gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue);
displacementField.GetTrueDofs(displacementTrue); displacementField.GetTrueDofs(displacementTrue);
mfem::Vector residual; mfem::Vector residual;
mean_field::operators::kernels::apply_gravity_displacement_force_residual( mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, densityTrue, gravityGradientTrue, displacementTrue, residual f, *f.domainMapperStateless, densityTrue, gravityGradientTrue,
); displacementTrue, residual);
const mfem::Vector dilationDirection = const mfem::Vector dilationDirection =
gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f); gravity_displacement_force_analytic_test_utils::
make_dilation_test_direction(f);
const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm()); const double computedWork = gravity_prepared_test_utils::global_dot(
residual, dilationDirection, f.mesh->GetComm());
const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius; const double analyticWork =
(3.0 / 5.0) * mean_field::utils::G * mass * mass / radius;
const double relativeError = const double relativeError =
gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork); gravity_displacement_force_analytic_test_utils::relative_scalar_error(
computedWork, analyticWork);
INFO("Solved-field positive gravity work = " << computedWork); INFO("Solved-field positive gravity work = " << computedWork);
INFO("Analytic positive gravity work = " << analyticWork); INFO("Analytic positive gravity work = " << analyticWork);
INFO("Solved-field gravitational virial = " << -computedWork); INFO("Solved-field gravitational virial = " << -computedWork);
INFO("Analytic homogeneous-sphere binding energy = " << -analyticWork); INFO("Analytic homogeneous-sphere binding energy = " << -analyticWork);
INFO("Relative solved-field virial error = " << relativeError); INFO("Relative solved-field virial error = " << relativeError);
REQUIRE(computedWork > 0.0); REQUIRE(computedWork > 0.0);
CHECK(relativeError < 1.0e-5); CHECK(relativeError < 1.0e-5);
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp> #include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <cmath>
#include <mfem.hpp> #include <mfem.hpp>
import mean_field; import mean_field;
@@ -9,128 +10,181 @@ using namespace mean_field;
using Catch::Matchers::WithinAbs; using Catch::Matchers::WithinAbs;
namespace prepared_test = gravity_prepared_test_utils; namespace prepared_test = gravity_prepared_test_utils;
TEST_CASE( TEST_CASE("Prepared Mapped Hdiv Mass Matches Stateless Kernel",
"Prepared Mapped Hdiv Mass Matches Stateless Kernel", tags::gravity_prepared) {
tags::gravity_prepared auto args = test_utils::setup_args();
) { fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless); operators::PreparedMappedHDivMassOperator prepared_operator(
REQUIRE(prepared_operator.Width() == prepared_operator.GetFluxMap().reduced_size()); f, *f.domainMapperStateless);
REQUIRE(prepared_operator.Height() == prepared_operator.GetFluxMap().reduced_size()); REQUIRE(prepared_operator.Width() ==
prepared_operator.GetFluxMap().reduced_size());
REQUIRE(prepared_operator.Height() ==
prepared_operator.GetFluxMap().reduced_size());
const mfem::Vector gravity_gradient_true = const mfem::Vector gravity_gradient_true =
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.21); prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
const mfem::Vector gravity_gradient = prepared_operator.GetFluxMap().gather(gravity_gradient_true); 0.21);
const MPI_Comm communicator = f.gravityFluxFes->GetComm(); const mfem::Vector gravity_gradient =
prepared_operator.GetFluxMap().gather(gravity_gradient_true);
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
mfem::Vector identity_action; mfem::Vector identity_action;
mfem::Vector deformed_action; mfem::Vector deformed_action;
for (const double deformation_scale : {0.0, 1.0}) { for (const double deformation_scale : {0.0, 1.0}) {
const mfem::Vector displacement_true = prepared_test::make_displacement(f, deformation_scale); const mfem::Vector displacement_true =
const mfem::Vector displacement = prepared_operator.GetDisplacementMap().gather(displacement_true); prepared_test::make_displacement(f, deformation_scale);
prepared_operator.Prepare(displacement);
mfem::Vector prepared_action;
prepared_operator.Mult(gravity_gradient, prepared_action);
mfem::Vector reference_action_true;
operators::kernels::apply_mapped_hdiv_mass(
f, *f.domainMapperStateless, gravity_gradient_true, displacement_true, reference_action_true
);
const mfem::Vector reference_action = prepared_operator.GetFluxMap().gather(reference_action_true);
const double relative_error = prepared_test::relative_error(prepared_action, reference_action, communicator);
INFO("Deformation scale = " << deformation_scale);
INFO("Prepared action norm = " << prepared_test::global_norm(prepared_action, communicator));
INFO("Reference action norm = " << prepared_test::global_norm(reference_action, communicator));
INFO("Relative prepared-operator error = " << relative_error);
REQUIRE(prepared_operator.IsPrepared());
CHECK_THAT(relative_error, WithinAbs(0.0, 2.0e-11));
if (deformation_scale == 0.0) {
identity_action = prepared_action;
} else {
deformed_action = prepared_action;
}
}
const double geometry_change = prepared_test::relative_error(deformed_action, identity_action, communicator);
INFO("Relative action change under deformation = " << geometry_change);
CHECK(prepared_operator.GetPreparationCount() == 2);
CHECK(geometry_change > 1.0e-5);
}
TEST_CASE(
"Prepared Mapped Hdiv Mass Preserves Operator Identities",
tags::gravity_prepared
) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless);
REQUIRE(prepared_operator.Width() == prepared_operator.GetFluxMap().reduced_size());
REQUIRE(prepared_operator.Height() == prepared_operator.GetFluxMap().reduced_size());
const mfem::Vector displacement = const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(prepared_test::make_displacement(f, 1.0)); prepared_operator.GetDisplacementMap().gather(displacement_true);
prepared_operator.Prepare(displacement); prepared_operator.Prepare(displacement);
const mfem::Vector first = prepared_operator.GetFluxMap().gather( mfem::Vector prepared_action;
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.17)
);
const mfem::Vector second = prepared_operator.GetFluxMap().gather(
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.83)
);
const mfem::Vector combination = prepared_test::linear_combination(first, 1.7, second, -0.4);
mfem::Vector first_action; prepared_operator.Mult(gravity_gradient, prepared_action);
mfem::Vector second_action; mfem::Vector reference_action_true;
mfem::Vector combination_action; operators::kernels::apply_mapped_hdiv_mass(
mfem::Vector zero_action; f, *f.domainMapperStateless, gravity_gradient_true, displacement_true,
reference_action_true);
const mfem::Vector reference_action =
prepared_operator.GetFluxMap().gather(reference_action_true);
prepared_operator.Mult(first, first_action); const double relative_error = prepared_test::relative_error(
prepared_operator.Mult(second, second_action); prepared_action, reference_action, communicator);
prepared_operator.Mult(combination, combination_action);
mfem::Vector expected_combination = prepared_test::linear_combination(first_action, 1.7, second_action, -0.4); INFO("Deformation scale = " << deformation_scale);
INFO("Prepared action norm = "
<< prepared_test::global_norm(prepared_action, communicator));
INFO("Reference action norm = "
<< prepared_test::global_norm(reference_action, communicator));
INFO("Relative prepared-operator error = " << relative_error);
mfem::Vector zero(first.Size()); REQUIRE(prepared_operator.IsPrepared());
zero = 0.0; CHECK_THAT(relative_error, WithinAbs(0.0, 2.0e-11));
prepared_operator.Mult(zero, zero_action);
const MPI_Comm communicator = f.gravityFluxFes->GetComm(); if (deformation_scale == 0.0) {
identity_action = prepared_action;
} else {
deformed_action = prepared_action;
}
}
const double first_second_product = prepared_test::global_dot(first, second_action, communicator); const double geometry_change = prepared_test::relative_error(
const double second_first_product = prepared_test::global_dot(second, first_action, communicator); deformed_action, identity_action, communicator);
const double symmetry_error = prepared_test::relative_scalar_error(first_second_product, second_first_product);
const double linearity_error =
prepared_test::relative_error(combination_action, expected_combination, communicator);
const double first_energy = prepared_test::global_dot(first, first_action, communicator);
const double second_energy = prepared_test::global_dot(second, second_action, communicator);
const std::uint64_t preparation_count = prepared_operator.GetPreparationCount();
mfem::Vector repeated_action; INFO("Relative action change under deformation = " << geometry_change);
prepared_operator.Mult(first, repeated_action);
INFO("u^T M v = " << first_second_product); CHECK(prepared_operator.GetPreparationCount() == 2);
INFO("v^T M u = " << second_first_product); CHECK(geometry_change > 1.0e-5);
INFO("Relative symmetry error = " << symmetry_error); }
INFO("Relative linearity error = " << linearity_error);
INFO("u^T M u = " << first_energy); TEST_CASE("Prepared Mapped Hdiv Mass Preserves Operator Identities",
INFO("v^T M v = " << second_energy); tags::gravity_prepared) {
auto args = test_utils::setup_args();
CHECK_THAT(symmetry_error, WithinAbs(0.0, 2.0e-12)); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
CHECK_THAT(prepared_test::global_norm(zero_action, communicator), WithinAbs(0.0, 1.0e-14)); operators::PreparedMappedHDivMassOperator prepared_operator(
CHECK(first_energy > 0.0); f, *f.domainMapperStateless);
CHECK(second_energy > 0.0); REQUIRE(prepared_operator.Width() ==
CHECK(prepared_test::relative_error(repeated_action, first_action, communicator) < 2.0e-14); prepared_operator.GetFluxMap().reduced_size());
CHECK(prepared_operator.GetPreparationCount() == preparation_count); REQUIRE(prepared_operator.Height() ==
prepared_operator.GetFluxMap().reduced_size());
const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(
prepared_test::make_displacement(f, 1.0));
prepared_operator.Prepare(displacement);
const mfem::Vector first = prepared_operator.GetFluxMap().gather(
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
0.17));
const mfem::Vector second = prepared_operator.GetFluxMap().gather(
prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(),
0.83));
const mfem::Vector combination =
prepared_test::linear_combination(first, 1.7, second, -0.4);
mfem::Vector first_action;
mfem::Vector second_action;
mfem::Vector combination_action;
mfem::Vector zero_action;
prepared_operator.Mult(first, first_action);
prepared_operator.Mult(second, second_action);
prepared_operator.Mult(combination, combination_action);
mfem::Vector expected_combination =
prepared_test::linear_combination(first_action, 1.7, second_action, -0.4);
mfem::Vector zero(first.Size());
zero = 0.0;
prepared_operator.Mult(zero, zero_action);
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
const double first_second_product =
prepared_test::global_dot(first, second_action, communicator);
const double second_first_product =
prepared_test::global_dot(second, first_action, communicator);
const double symmetry_error = prepared_test::relative_scalar_error(
first_second_product, second_first_product);
const double linearity_error = prepared_test::relative_error(
combination_action, expected_combination, communicator);
const double first_energy =
prepared_test::global_dot(first, first_action, communicator);
const double second_energy =
prepared_test::global_dot(second, second_action, communicator);
const std::uint64_t preparation_count =
prepared_operator.GetPreparationCount();
mfem::Vector repeated_action;
prepared_operator.Mult(first, repeated_action);
INFO("u^T M v = " << first_second_product);
INFO("v^T M u = " << second_first_product);
INFO("Relative symmetry error = " << symmetry_error);
INFO("Relative linearity error = " << linearity_error);
INFO("u^T M u = " << first_energy);
INFO("v^T M v = " << second_energy);
CHECK_THAT(symmetry_error, WithinAbs(0.0, 2.0e-12));
CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12));
CHECK_THAT(prepared_test::global_norm(zero_action, communicator),
WithinAbs(0.0, 1.0e-14));
CHECK(first_energy > 0.0);
CHECK(second_energy > 0.0);
CHECK(prepared_test::relative_error(repeated_action, first_action,
communicator) < 2.0e-14);
CHECK(prepared_operator.GetPreparationCount() == preparation_count);
}
TEST_CASE("Prepared Mapped Hdiv Mass Diagonal Is Positive Across Both Domains",
tags::gravity_prepared) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
operators::PreparedMappedHDivMassOperator prepared_operator(
f, *f.domainMapperStateless);
const mfem::Vector displacement =
prepared_operator.GetDisplacementMap().gather(
prepared_test::make_displacement(f, 1.0));
prepared_operator.Prepare(displacement);
mfem::Vector diagonal;
mfem::Vector true_diagonal;
prepared_operator.AssembleDiagonal(diagonal);
prepared_operator.AssembleTrueDiagonal(true_diagonal);
REQUIRE(diagonal.Size() == prepared_operator.Height());
REQUIRE(true_diagonal.Size() == prepared_operator.GetFluxMap().full_size());
const mfem::Vector gathered_true_diagonal =
prepared_operator.GetFluxMap().gather(true_diagonal);
for (int i = 0; i < diagonal.Size(); ++i) {
REQUIRE(std::isfinite(diagonal(i)));
CHECK(diagonal(i) > 0.0);
CHECK_THAT(diagonal(i), WithinAbs(gathered_true_diagonal(i),
1.0e-14 * std::abs(diagonal(i))));
}
} }

View File

@@ -9,423 +9,430 @@ import mean_field;
import test_helpers; import test_helpers;
namespace prepared_hydrostatic_analytic_solve_test_utils { namespace prepared_hydrostatic_analytic_solve_test_utils {
constexpr double bernoulliConstant = 0.83; constexpr double bernoulliConstant = 0.83;
constexpr double enthalpyAmplitude = 0.61; constexpr double enthalpyAmplitude = 0.61;
struct AnalyticCase { struct AnalyticCase {
const char *name; const char *name;
std::array<double, 3> deformationScale; std::array<double, 3> deformationScale;
std::array<double, 3> angularVelocity; std::array<double, 3> angularVelocity;
std::array<double, 3> rotationCenter; std::array<double, 3> rotationCenter;
}; };
class EnthalpyJacobianOperator final : public mfem::Operator { class EnthalpyJacobianOperator final : public mfem::Operator {
public: public:
EnthalpyJacobianOperator( EnthalpyJacobianOperator(
const int enthalpySize, const int enthalpySize,
const mean_field::operators::PreparedHydrostaticEquilibriumOperator &preparedOperator const mean_field::operators::PreparedHydrostaticEquilibriumOperator
) &preparedOperator)
: mfem::Operator(enthalpySize), : mfem::Operator(enthalpySize), m_preparedOperator(preparedOperator) {}
m_preparedOperator(preparedOperator) {
}
void Mult( void Mult(const mfem::Vector &direction,
const mfem::Vector &direction, mfem::Vector &action) const override {
mfem::Vector &action m_preparedOperator.ApplyEnthalpyJacobianAction(direction, action);
) const override { }
m_preparedOperator.ApplyEnthalpyJacobianAction(direction, action);
}
private: private:
const mean_field::operators::PreparedHydrostaticEquilibriumOperator &m_preparedOperator; const mean_field::operators::PreparedHydrostaticEquilibriumOperator
}; &m_preparedOperator;
};
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() { mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies
return { make_dependencies() {
.discretization = {.identity = 701, .revision = 2}, return {.discretization = {.identity = 701, .revision = 2},
.enthalpy = {.identity = 709, .revision = 3}, .enthalpy = {.identity = 709, .revision = 3},
.gravityPotential = {.identity = 719, .revision = 5}, .gravityPotential = {.identity = 719, .revision = 5},
.displacement = {.identity = 727, .revision = 7}, .displacement = {.identity = 727, .revision = 7},
.rotation = {.identity = 733, .revision = 11}, .rotation = {.identity = 733, .revision = 11},
.bernoulliConstant = {.identity = 739, .revision = 13} .bernoulliConstant = {.identity = 739, .revision = 13}};
}; }
}
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state( mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
const mfem::Vector &enthalpy, make_state(const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential,
const mfem::Vector &gravityPotential, const mfem::Vector &displacement) {
const mfem::Vector &displacement return {.enthalpy = enthalpy,
) { .gravityPotential = gravityPotential,
return { .displacement = displacement,
.enthalpy = enthalpy, .bernoulliConstant = bernoulliConstant};
.gravityPotential = gravityPotential, }
.displacement = displacement,
.bernoulliConstant = bernoulliConstant
};
}
mfem::Vector make_vector( mfem::Vector make_vector(const std::array<double, 3> &values) {
const std::array< mfem::Vector vector(3);
double,
3> &values
) {
mfem::Vector vector(3);
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
vector(component) = values[static_cast<std::size_t>(component)]; vector(component) = values[static_cast<std::size_t>(component)];
} }
return vector; return vector;
} }
mean_field::physics::RigidRotation make_rotation(const AnalyticCase &analyticCase) { mean_field::physics::RigidRotation
return mean_field::physics::RigidRotation( make_rotation(const AnalyticCase &analyticCase) {
make_vector(analyticCase.angularVelocity), make_vector(analyticCase.rotationCenter) return mean_field::physics::RigidRotation(
); make_vector(analyticCase.angularVelocity),
} make_vector(analyticCase.rotationCenter));
}
void map_to_physical( void map_to_physical(const mfem::Vector &referencePosition,
const mfem::Vector &referencePosition, const AnalyticCase &analyticCase,
const AnalyticCase &analyticCase, mfem::Vector &physicalPosition) {
mfem::Vector &physicalPosition physicalPosition.SetSize(3);
) {
physicalPosition.SetSize(3);
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
physicalPosition(component) = physicalPosition(component) =
analyticCase.deformationScale[static_cast<std::size_t>(component)] * referencePosition(component); analyticCase.deformationScale[static_cast<std::size_t>(component)] *
} referencePosition(component);
} }
}
double exact_enthalpy_value(const mfem::Vector &referencePosition) { double exact_enthalpy_value(const mfem::Vector &referencePosition) {
double normalizedRadiusSquared = 0.0; double normalizedRadiusSquared = 0.0;
for (int component = 0; component < 3; ++component) { for (int component = 0; component < 3; ++component) {
const double normalizedCoordinate = referencePosition(component) / mean_field::utils::RADIUS; const double normalizedCoordinate =
referencePosition(component) / mean_field::utils::RADIUS;
normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate; normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate;
} }
return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared); return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared);
} }
double exact_potential_value( double
const mfem::Vector &referencePosition, exact_potential_value(const mfem::Vector &referencePosition,
const AnalyticCase &analyticCase, const AnalyticCase &analyticCase,
const mean_field::physics::RigidRotation &rotation const mean_field::physics::RigidRotation &rotation) {
) { mfem::Vector physicalPosition;
mfem::Vector physicalPosition;
map_to_physical(referencePosition, analyticCase, physicalPosition); map_to_physical(referencePosition, analyticCase, physicalPosition);
/* /*
* Construct Phi so that * Construct Phi so that
* *
* h + Phi - Psi_rotation - C = 0 * h + Phi - Psi_rotation - C = 0
* *
* analytically. * analytically.
*/ */
return bernoulliConstant + rotation.potential(physicalPosition) - exact_enthalpy_value(referencePosition); return bernoulliConstant + rotation.potential(physicalPosition) -
} exact_enthalpy_value(referencePosition);
}
mfem::Array<int> make_stellar_element_marker(const mean_field::fem::FEM &f) { mfem::Array<int> make_stellar_element_marker(const mean_field::fem::FEM &f) {
mfem::Array<int> stellarElementMarker(f.mesh->GetNE()); mfem::Array<int> stellarElementMarker(f.mesh->GetNE());
const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute(); const int vacuumAttribute = field_dof_test_utils::vacuum_material_attribute;
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
stellarElementMarker[elementId] = f.mesh->GetAttribute(elementId) != vacuumAttribute; stellarElementMarker[elementId] =
} f.mesh->GetAttribute(elementId) != vacuumAttribute;
}
return stellarElementMarker; return stellarElementMarker;
} }
} // namespace prepared_hydrostatic_analytic_solve_test_utils } // namespace prepared_hydrostatic_analytic_solve_test_utils
TEST_CASE( TEST_CASE("Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria",
"Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria", tags::barotrope_hydrostatic_prepared_analytic &tags::convergence
tags::barotrope_hydrostatic_prepared_analytic &tags::convergence &tags::accuracy &tags::accuracy) {
) { using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase;
using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase;
constexpr double deformationX = 1.08;
constexpr double deformationY = 0.96;
/*
* The third scale makes the affine deformation
* volume-preserving:
*
* det(F) = sx * sy * sz = 1.
*/
constexpr double deformationZ = 1.0 / (deformationX * deformationY);
const std::array<AnalyticCase, 3> analyticCases{
{{.name = "spherical nonrotating equilibrium",
.deformationScale = {1.0, 1.0, 1.0},
.angularVelocity = {0.0, 0.0, 0.0},
.rotationCenter = {0.0, 0.0, 0.0}},
{.name = "spherical rotating equilibrium",
.deformationScale = {1.0, 1.0, 1.0},
.angularVelocity = {0.13, -0.09, 0.31},
.rotationCenter = {0.04, -0.03, 0.02}},
{.name = "volume-preserving deformed rotating equilibrium",
.deformationScale = {deformationX, deformationY, deformationZ},
.angularVelocity = {0.17, -0.12, 0.43},
.rotationCenter = {0.031, -0.024, 0.018}}}};
constexpr double deformationX = 1.08; auto args = test_utils::setup_args();
constexpr double deformationY = 0.96;
/* mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
* The third scale makes the affine deformation
* volume-preserving:
*
* det(F) = sx * sy * sz = 1.
*/
constexpr double deformationZ = 1.0 / (deformationX * deformationY);
const std::array<AnalyticCase, 3> analyticCases{ const MPI_Comm communicator = f.mesh->GetComm();
{{.name = "spherical nonrotating equilibrium",
.deformationScale = {1.0, 1.0, 1.0},
.angularVelocity = {0.0, 0.0, 0.0},
.rotationCenter = {0.0, 0.0, 0.0}},
{.name = "spherical rotating equilibrium",
.deformationScale = {1.0, 1.0, 1.0},
.angularVelocity = {0.13, -0.09, 0.31},
.rotationCenter = {0.04, -0.03, 0.02}},
{.name = "volume-preserving deformed rotating equilibrium",
.deformationScale = {deformationX, deformationY, deformationZ},
.angularVelocity = {0.17, -0.12, 0.43},
.rotationCenter = {0.031, -0.024, 0.018}}}
};
auto args = test_utils::setup_args(); const mean_field::field::FieldDofMap enthalpyMap =
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(
*f.enthalpyFes);
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); const mean_field::field::FieldDofMap gravityPotentialMap =
field_dof_test_utils::make_map<mean_field::field::Gravity>(
*f.gravityPotentialFes);
const MPI_Comm communicator = f.mesh->GetComm(); const mean_field::field::FieldDofMap displacementMap =
field_dof_test_utils::make_map<mean_field::field::Displacement>(
*f.displacementFes);
const mean_field::field::FieldDofMap enthalpyMap = const mfem::Array<int> stellarElementMarker =
field_dof_test_utils::make_map<mean_field::field::Enthalpy>(*f.enthalpyFes); prepared_hydrostatic_analytic_solve_test_utils::
make_stellar_element_marker(f);
const mean_field::field::FieldDofMap gravityPotentialMap = for (const AnalyticCase &analyticCase : analyticCases) {
field_dof_test_utils::make_map<mean_field::field::Gravity>(*f.gravityPotentialFes); DYNAMIC_SECTION(analyticCase.name) {
const double deformationDeterminant = analyticCase.deformationScale[0] *
analyticCase.deformationScale[1] *
analyticCase.deformationScale[2];
const mean_field::field::FieldDofMap displacementMap = REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14);
field_dof_test_utils::make_map<mean_field::field::Displacement>(*f.displacementFes);
const mfem::Array<int> stellarElementMarker = const mean_field::physics::RigidRotation rotation =
prepared_hydrostatic_analytic_solve_test_utils::make_stellar_element_marker(f); prepared_hydrostatic_analytic_solve_test_utils::make_rotation(
analyticCase);
for (const AnalyticCase &analyticCase : analyticCases) { auto displacementFunction =
DYNAMIC_SECTION(analyticCase.name) { [&analyticCase](const mfem::Vector &referencePosition,
const double deformationDeterminant = mfem::Vector &displacementValue) {
analyticCase.deformationScale[0] * analyticCase.deformationScale[1] * analyticCase.deformationScale[2]; mfem::Vector physicalPosition;
REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14); prepared_hydrostatic_analytic_solve_test_utils::map_to_physical(
referencePosition, analyticCase, physicalPosition);
const mean_field::physics::RigidRotation rotation = displacementValue.SetSize(3);
prepared_hydrostatic_analytic_solve_test_utils::make_rotation(analyticCase); displacementValue = physicalPosition;
displacementValue -= referencePosition;
};
auto displacementFunction = auto potentialFunction = [&analyticCase, &rotation](
[&analyticCase](const mfem::Vector &referencePosition, mfem::Vector &displacementValue) { const mfem::Vector &referencePosition) {
mfem::Vector physicalPosition; return prepared_hydrostatic_analytic_solve_test_utils::
exact_potential_value(referencePosition, analyticCase, rotation);
};
prepared_hydrostatic_analytic_solve_test_utils::map_to_physical( auto enthalpyFunction = [](const mfem::Vector &referencePosition) {
referencePosition, analyticCase, physicalPosition return prepared_hydrostatic_analytic_solve_test_utils::
); exact_enthalpy_value(referencePosition);
};
displacementValue.SetSize(3); mfem::VectorFunctionCoefficient displacementCoefficient(
displacementValue = physicalPosition; f.mesh->Dimension(), displacementFunction);
displacementValue -= referencePosition;
};
auto potentialFunction = [&analyticCase, &rotation](const mfem::Vector &referencePosition) { mfem::FunctionCoefficient potentialCoefficient(potentialFunction);
return prepared_hydrostatic_analytic_solve_test_utils::exact_potential_value(
referencePosition, analyticCase, rotation
);
};
auto enthalpyFunction = [](const mfem::Vector &referencePosition) { mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction);
return prepared_hydrostatic_analytic_solve_test_utils::exact_enthalpy_value(referencePosition);
};
mfem::VectorFunctionCoefficient displacementCoefficient(f.mesh->Dimension(), displacementFunction); /*
* Project the prescribed geometry and potential.
*/
mfem::ParGridFunction displacementField(f.displacementFes.get());
mfem::FunctionCoefficient potentialCoefficient(potentialFunction); mfem::ParGridFunction potentialField(f.gravityPotentialFes.get());
mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction); displacementField.ProjectCoefficient(displacementCoefficient);
/* potentialField.ProjectCoefficient(potentialCoefficient);
* Project the prescribed geometry and potential.
*/
mfem::ParGridFunction displacementField(f.displacementFes.get());
mfem::ParGridFunction potentialField(f.gravityPotentialFes.get()); mfem::Vector displacementTrue;
mfem::Vector gravityPotentialTrue;
displacementField.ProjectCoefficient(displacementCoefficient); displacementField.GetTrueDofs(displacementTrue);
potentialField.GetTrueDofs(gravityPotentialTrue);
potentialField.ProjectCoefficient(potentialCoefficient); const mfem::Vector displacement =
displacementMap.gather(displacementTrue);
const mfem::Vector gravityPotential =
gravityPotentialMap.gather(gravityPotentialTrue);
mfem::Vector displacementTrue; /*
mfem::Vector gravityPotentialTrue; * This projection is not used as the solution. It gives
* the best directly available representation baseline
* against which the solved field can be compared.
*/
mfem::ParGridFunction projectedEnthalpyField(f.enthalpyFes.get());
displacementField.GetTrueDofs(displacementTrue); projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient);
potentialField.GetTrueDofs(gravityPotentialTrue);
const mfem::Vector displacement = displacementMap.gather(displacementTrue); mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get());
const mfem::Vector gravityPotential = gravityPotentialMap.gather(gravityPotentialTrue);
/* zeroEnthalpyField = 0.0;
* This projection is not used as the solution. It gives
* the best directly available representation baseline
* against which the solved field can be compared.
*/
mfem::ParGridFunction projectedEnthalpyField(f.enthalpyFes.get());
projectedEnthalpyField.ProjectCoefficient(exactEnthalpyCoefficient); const double exactEnthalpyNorm = zeroEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get()); const double projectionError = projectedEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
zeroEnthalpyField = 0.0; REQUIRE(exactEnthalpyNorm > 0.0);
const double exactEnthalpyNorm = const double relativeProjectionError =
zeroEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker); projectionError / exactEnthalpyNorm;
const double projectionError = /*
projectedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker); * Begin deliberately far from equilibrium.
*/
mfem::Vector enthalpy(enthalpyMap.reduced_size());
REQUIRE(exactEnthalpyNorm > 0.0); enthalpy = 0.0;
const double relativeProjectionError = projectionError / exactEnthalpyNorm; auto dependencies =
prepared_hydrostatic_analytic_solve_test_utils::make_dependencies();
/* mean_field::operators::PreparedHydrostaticEquilibriumOperator
* Begin deliberately far from equilibrium. preparedOperator(f, *f.domainMapperStateless);
*/
mfem::Vector enthalpy(enthalpyMap.reduced_size());
enthalpy = 0.0; const auto initialReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement),
dependencies, rotation);
auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::make_dependencies(); REQUIRE(initialReport.preparedResidual);
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks);
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); mfem::Vector initialResidual;
const auto initialReport = preparedOperator.Prepare( preparedOperator.BuildResidual(initialResidual);
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
REQUIRE(initialReport.preparedResidual); const double initialResidualNorm =
REQUIRE(initialReport.preparedAlgebraicJacobianBlocks); gravity_prepared_test_utils::global_norm(initialResidual,
communicator);
mfem::Vector initialResidual; REQUIRE(initialResidualNorm > 1.0e-12);
preparedOperator.BuildResidual(initialResidual); /*
* One discrete Newton step:
*
* M_h delta_h = -R_h.
*
* The full four-block Bernoulli Jacobian is rectangular
* and underdetermined in isolation. Freezing Phi, C,
* rotation, and displacement makes this a well-defined
* enthalpy solve.
*/
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator
enthalpyJacobian(enthalpyMap.reduced_size(), preparedOperator);
const double initialResidualNorm = gravity_prepared_test_utils::global_norm(initialResidual, communicator); mfem::Vector rightHandSide(initialResidual);
rightHandSide *= -1.0;
REQUIRE(initialResidualNorm > 1.0e-12); mfem::Vector enthalpyCorrection(enthalpyMap.reduced_size());
/* enthalpyCorrection = 0.0;
* One discrete Newton step:
*
* M_h delta_h = -R_h.
*
* The full four-block Bernoulli Jacobian is rectangular
* and underdetermined in isolation. Freezing Phi, C,
* rotation, and displacement makes this a well-defined
* enthalpy solve.
*/
prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator enthalpyJacobian(
enthalpyMap.reduced_size(), preparedOperator
);
mfem::Vector rightHandSide(initialResidual); /*
rightHandSide *= -1.0; * The reduced operator contains only stellar-supported
* enthalpy DOFs and is positive definite. MINRES remains
* appropriate for this symmetric system.
*/
mfem::MINRESSolver linearSolver(communicator);
mfem::Vector enthalpyCorrection(enthalpyMap.reduced_size()); linearSolver.SetOperator(enthalpyJacobian);
enthalpyCorrection = 0.0; linearSolver.SetRelTol(1.0e-13);
linearSolver.SetAbsTol(1.0e-14);
linearSolver.SetMaxIter(2000);
linearSolver.SetPrintLevel(0);
/* linearSolver.Mult(rightHandSide, enthalpyCorrection);
* The reduced operator contains only stellar-supported
* enthalpy DOFs and is positive definite. MINRES remains
* appropriate for this symmetric system.
*/
mfem::MINRESSolver linearSolver(communicator);
linearSolver.SetOperator(enthalpyJacobian); INFO("Linear solver converged = " << linearSolver.GetConverged());
linearSolver.SetRelTol(1.0e-13); INFO("Linear solver iterations = " << linearSolver.GetNumIterations());
linearSolver.SetAbsTol(1.0e-14);
linearSolver.SetMaxIter(2000);
linearSolver.SetPrintLevel(0);
linearSolver.Mult(rightHandSide, enthalpyCorrection); INFO("Linear solver final norm = " << linearSolver.GetFinalNorm());
INFO("Linear solver converged = " << linearSolver.GetConverged()); REQUIRE(linearSolver.GetConverged());
INFO("Linear solver iterations = " << linearSolver.GetNumIterations()); enthalpy += enthalpyCorrection;
INFO("Linear solver final norm = " << linearSolver.GetFinalNorm()); /*
* Only the enthalpy state changed. Geometry, rotation,
* and algebraic Jacobian data must remain reusable.
*/
++dependencies.enthalpy.revision;
REQUIRE(linearSolver.GetConverged()); const auto solvedReport = preparedOperator.Prepare(
prepared_hydrostatic_analytic_solve_test_utils::make_state(
enthalpy, gravityPotential, displacement),
dependencies, rotation);
enthalpy += enthalpyCorrection; CHECK(solvedReport.contextReport.updatedEnthalpy);
/* CHECK(solvedReport.contextReport.preparedBaseState);
* Only the enthalpy state changed. Geometry, rotation,
* and algebraic Jacobian data must remain reusable.
*/
++dependencies.enthalpy.revision;
const auto solvedReport = preparedOperator.Prepare( CHECK_FALSE(solvedReport.contextReport.preparedGeometryState);
prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement),
dependencies, rotation
);
CHECK(solvedReport.contextReport.updatedEnthalpy); CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks);
CHECK(solvedReport.contextReport.preparedBaseState); mfem::Vector solvedResidual;
CHECK_FALSE(solvedReport.contextReport.preparedGeometryState); preparedOperator.BuildResidual(solvedResidual);
CHECK_FALSE(solvedReport.preparedAlgebraicJacobianBlocks); const double solvedResidualNorm =
gravity_prepared_test_utils::global_norm(solvedResidual,
communicator);
mfem::Vector solvedResidual; const double residualReduction = solvedResidualNorm / initialResidualNorm;
preparedOperator.BuildResidual(solvedResidual); /*
* Compare the solved field with the continuum analytic
* enthalpy over stellar elements only.
*
* All three mappings have determinant one, so this
* normalized L2 error is also unchanged by the physical
* volume transformation.
*/
mfem::ParGridFunction solvedEnthalpyField(f.enthalpyFes.get());
const double solvedResidualNorm = gravity_prepared_test_utils::global_norm(solvedResidual, communicator); mfem::Vector enthalpyTrue(enthalpyMap.full_size());
enthalpyMap.scatter(enthalpy, enthalpyTrue);
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
const double residualReduction = solvedResidualNorm / initialResidualNorm; const double solvedAnalyticError = solvedEnthalpyField.ComputeL2Error(
exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
/* const double relativeSolvedAnalyticError =
* Compare the solved field with the continuum analytic solvedAnalyticError / exactEnthalpyNorm;
* enthalpy over stellar elements only.
*
* All three mappings have determinant one, so this
* normalized L2 error is also unchanged by the physical
* volume transformation.
*/
mfem::ParGridFunction solvedEnthalpyField(f.enthalpyFes.get());
mfem::Vector enthalpyTrue(enthalpyMap.full_size()); INFO("Deformation determinant = " << deformationDeterminant);
enthalpyMap.scatter(enthalpy, enthalpyTrue);
solvedEnthalpyField.SetFromTrueDofs(enthalpyTrue);
const double solvedAnalyticError = INFO("Initial weak residual norm = " << initialResidualNorm);
solvedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker);
const double relativeSolvedAnalyticError = solvedAnalyticError / exactEnthalpyNorm; INFO("Solved weak residual norm = " << solvedResidualNorm);
INFO("Deformation determinant = " << deformationDeterminant); INFO("Weak residual reduction = " << residualReduction);
INFO("Initial weak residual norm = " << initialResidualNorm); INFO("Relative analytic projection floor = " << relativeProjectionError);
INFO("Solved weak residual norm = " << solvedResidualNorm); INFO("Relative solved analytic L2 error = "
<< relativeSolvedAnalyticError);
INFO("Weak residual reduction = " << residualReduction); /*
* The discrete Bernoulli equation must be solved essentially
* to the linear-solver floor.
*/
CHECK(residualReduction < 1.0e-10);
INFO("Relative analytic projection floor = " << relativeProjectionError); /*
* The directly projected analytic enthalpy provides a lower
* representation bound, but it is not the expected solution
* of the cross-space discrete Bernoulli equation. The latter
* also contains potential-projection and mapped-space
* compatibility errors.
*/
CHECK(relativeSolvedAnalyticError <
std::max(5.0 * relativeProjectionError, 1.25e-4));
INFO("Relative solved analytic L2 error = " << relativeSolvedAnalyticError); /*
* Record that the analytic error remains within one order of
/* * magnitude of the direct enthalpy projection floor.
* The discrete Bernoulli equation must be solved essentially */
* to the linear-solver floor. CHECK(relativeSolvedAnalyticError / relativeProjectionError < 5.0);
*/
CHECK(residualReduction < 1.0e-10);
/*
* The directly projected analytic enthalpy provides a lower
* representation bound, but it is not the expected solution
* of the cross-space discrete Bernoulli equation. The latter
* also contains potential-projection and mapped-space
* compatibility errors.
*/
CHECK(relativeSolvedAnalyticError < std::max(5.0 * relativeProjectionError, 1.25e-4));
/*
* Record that the analytic error remains within one order of
* magnitude of the direct enthalpy projection floor.
*/
CHECK(relativeSolvedAnalyticError / relativeProjectionError < 5.0);
}
} }
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,106 +8,115 @@
import mean_field; import mean_field;
import test_helpers; import test_helpers;
TEST_CASE( TEST_CASE("Polytropic EOS Satisfies Its Analytic Identities",
"Polytropic Barotrope Satisfies Its Analytic Identities", tags::barotrope_eos_unit) {
tags::hydro &tags::unit &tags::barotrope constexpr double polytropic_index = 3.0;
) { constexpr double polytropic_constant = 1.5;
constexpr double polytropic_index = 3.0;
constexpr double polytropic_constant = 1.5;
const mean_field::physics::PolytropicBarotrope barotrope(polytropic_index, polytropic_constant); const mean_field::eos::Polytrope barotrope(polytropic_index,
polytropic_constant);
const std::array<double, 5> densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0}; const std::array<double, 5> densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0};
for (const double density : densities) { for (const double density : densities) {
const double pressure = barotrope.pressure_from_density(density); const double pressure = barotrope.pressure_from_density(density);
const double enthalpy = barotrope.enthalpy_from_density(density); const double enthalpy = barotrope.enthalpy_from_density(density);
const double reconstructed_density = barotrope.density_from_enthalpy(enthalpy); const double reconstructed_density =
barotrope.density_from_enthalpy(enthalpy);
const double reconstructed_pressure = barotrope.pressure_from_enthalpy(enthalpy); const double reconstructed_pressure =
barotrope.pressure_from_enthalpy(enthalpy);
CHECK_THAT(reconstructed_density, Catch::Matchers::WithinRel(density, 2.0e-14)); const double reconstructed_enthalpy =
barotrope.enthalpy_from_pressure(pressure);
CHECK_THAT(reconstructed_pressure, Catch::Matchers::WithinRel(pressure, 2.0e-14)); CHECK_THAT(reconstructed_density,
Catch::Matchers::WithinRel(density, 2.0e-14));
CHECK_THAT(pressure, Catch::Matchers::WithinRel(density * enthalpy / (polytropic_index + 1.0), 2.0e-14)); CHECK_THAT(reconstructed_pressure,
Catch::Matchers::WithinRel(pressure, 2.0e-14));
CHECK_THAT(barotrope.pressure_derivative_from_enthalpy(enthalpy), Catch::Matchers::WithinRel(density, 2.0e-14)); CHECK_THAT(reconstructed_enthalpy,
Catch::Matchers::WithinRel(enthalpy, 2.0e-14));
CHECK_THAT( CHECK_THAT(pressure,
barotrope.pressure_derivative_from_density(density), Catch::Matchers::WithinRel(
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14) density * enthalpy / (polytropic_index + 1.0), 2.0e-14));
);
} CHECK_THAT(barotrope.pressure_derivative_from_enthalpy(enthalpy),
Catch::Matchers::WithinRel(density, 2.0e-14));
CHECK_THAT(
barotrope.pressure_derivative_from_density(density),
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14));
}
} }
TEST_CASE( TEST_CASE("Polytropic EOS Derivatives Match Centered Differences",
"Polytropic Barotrope Derivatives Match Centered Differences", tags::barotrope_eos_jacobian) {
tags::hydro &tags::jacobian &tags::unit &tags::barotrope const mean_field::eos::Polytrope barotrope(3.0, 1.5);
) {
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
const std::array<double, 4> enthalpies{0.05, 0.2, 0.7, 1.4}; const std::array<double, 4> enthalpies{0.05, 0.2, 0.7, 1.4};
for (const double enthalpy : enthalpies) { for (const double enthalpy : enthalpies) {
const double step = 1.0e-6 * std::max(1.0, enthalpy); const double step = 1.0e-6 * std::max(1.0, enthalpy);
const double density_difference = const double density_difference =
(barotrope.density_from_enthalpy(enthalpy + step) - barotrope.density_from_enthalpy(enthalpy - step)) / (barotrope.density_from_enthalpy(enthalpy + step) -
(2.0 * step); barotrope.density_from_enthalpy(enthalpy - step)) /
(2.0 * step);
const double pressure_difference = const double pressure_difference =
(barotrope.pressure_from_enthalpy(enthalpy + step) - barotrope.pressure_from_enthalpy(enthalpy - step)) / (barotrope.pressure_from_enthalpy(enthalpy + step) -
(2.0 * step); barotrope.pressure_from_enthalpy(enthalpy - step)) /
(2.0 * step);
CHECK_THAT( CHECK_THAT(
density_difference, density_difference,
Catch::Matchers::WithinRel(barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10) Catch::Matchers::WithinRel(
); barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10));
CHECK_THAT( CHECK_THAT(
pressure_difference, pressure_difference,
Catch::Matchers::WithinRel(barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10) Catch::Matchers::WithinRel(
); barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10));
} }
} }
TEST_CASE( TEST_CASE("Polytropic EOS Has An Exact Zero Density Surface",
"Polytropic Barotrope Has An Exact Zero Density Surface", tags::barotrope_eos_unit) {
tags::hydro &tags::unit &tags::barotrope const mean_field::eos::Polytrope barotrope(3.0, 1.5);
) {
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
CHECK(barotrope.density_from_enthalpy(-1.0) == 0.0); CHECK(barotrope.density_from_enthalpy(-1.0) == 0.0);
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0); CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(-1.0) == 0.0); CHECK(barotrope.pressure_from_enthalpy(-1.0) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0); CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.density_derivative_from_enthalpy(-1.0) == 0.0); CHECK(barotrope.density_derivative_from_enthalpy(-1.0) == 0.0);
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == 0.0); CHECK(barotrope.density_derivative_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0); CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
} }
TEST_CASE( TEST_CASE("Polytropic EOS Rejects Invalid Material Parameters",
"Polytropic Barotrope Rejects Invalid Material Parameters", tags::barotrope_eos_unit) {
tags::hydro &tags::unit CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument);
) {
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(0.5, 1.0), std::invalid_argument);
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(3.0, 0.0), std::invalid_argument); CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
CHECK_THROWS_AS( CHECK_THROWS_AS(
mean_field::physics::PolytropicBarotrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
); std::invalid_argument);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.0); const mean_field::eos::Polytrope barotrope(3.0, 1.0);
CHECK_THROWS_AS(barotrope.pressure_from_density(-1.0), std::domain_error); CHECK_THROWS_AS(barotrope.pressure_from_density(-1.0), std::domain_error);
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error); CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error);
}
CHECK_THROWS_AS(barotrope.enthalpy_from_pressure(-1.0), std::domain_error);
}

View File

@@ -13,520 +13,561 @@
import mean_field; import mean_field;
import test_helpers; import test_helpers;
namespace polytropic_barotrope_test_utils { namespace polytropic_eos_test_utils {
template <typename Function> template <typename Function>
double centered_derivative( double centered_derivative(Function &&function, const double position,
Function &&function, const double step) {
const double position, return (function(position + step) - function(position - step)) / (2.0 * step);
const double step
) {
return (function(position + step) - function(position - step)) / (2.0 * step);
}
template <typename Integrand>
double integrate_cube(
const mfem::IntegrationRule &integrationRule,
Integrand &&integrand
) {
double integral = 0.0;
for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints(); ++pointIndex) {
const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(pointIndex);
integral += integrationPoint.weight * integrand(integrationPoint);
}
return integral;
}
} // namespace polytropic_barotrope_test_utils
TEST_CASE(
"Polytropic Barotrope Satisfies Its Thermodynamic Identities",
tags::barotrope &tags::physics &tags::unit
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 4> densities{1.0e-4, 0.02, 0.37, 2.4};
constexpr double polytropicConstant = 0.73;
for (const double polytropicIndex : polytropicIndices) {
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant);
const double expectedEnthalpyScale = (polytropicIndex + 1.0) * polytropicConstant;
CHECK(barotrope.polytropic_index() == polytropicIndex);
CHECK(barotrope.polytropic_constant() == polytropicConstant);
CHECK(barotrope.enthalpy_scale() == expectedEnthalpyScale);
for (const double density : densities) {
CAPTURE(polytropicIndex, polytropicConstant, density);
const double expectedPressure = polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
const double expectedEnthalpy = expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex);
const double pressureFromDensity = barotrope.pressure_from_density(density);
const double enthalpyFromDensity = barotrope.enthalpy_from_density(density);
const double recoveredDensity = barotrope.density_from_enthalpy(enthalpyFromDensity);
const double pressureFromEnthalpy = barotrope.pressure_from_enthalpy(enthalpyFromDensity);
CHECK_THAT(pressureFromDensity, Catch::Matchers::WithinRel(expectedPressure, 2.0e-13));
CHECK_THAT(enthalpyFromDensity, Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13));
CHECK_THAT(recoveredDensity, Catch::Matchers::WithinRel(density, 5.0e-13));
CHECK_THAT(pressureFromEnthalpy, Catch::Matchers::WithinRel(expectedPressure, 5.0e-13));
/*
* Polytropic identity:
*
* P = rho h / (n + 1).
*/
CHECK_THAT(
pressureFromEnthalpy,
Catch::Matchers::WithinRel(density * enthalpyFromDensity / (polytropicIndex + 1.0), 5.0e-13)
);
/*
* Polytropic identity:
*
* dP / dh = rho.
*
* The implementation should return the same
* value as density_from_enthalpy().
*/
CHECK(
barotrope.pressure_derivative_from_enthalpy(enthalpyFromDensity) ==
barotrope.density_from_enthalpy(enthalpyFromDensity)
);
/*
* Since
*
* h = (n + 1) K rho^(1/n),
*
* it follows that
*
* dP / d rho = h / n.
*/
CHECK_THAT(
barotrope.pressure_derivative_from_density(density),
Catch::Matchers::WithinRel(enthalpyFromDensity / polytropicIndex, 5.0e-13)
);
}
}
}
} }
TEST_CASE( template <typename Integrand>
"Polytropic Barotrope Pressure Derivatives Match Centered Differences", double integrate_cube(const mfem::IntegrationRule &integrationRule,
tags::barotrope &tags::physics &tags::unit &tags::jacobian &tags::pressure Integrand &&integrand) {
) { double integral = 0.0;
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 3> positiveValues{0.2, 0.73, 1.8}; for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints();
++pointIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(pointIndex);
constexpr double polytropicConstant = 0.61; integral += integrationPoint.weight * integrand(integrationPoint);
}
for (const double polytropicIndex : polytropicIndices) { return integral;
const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant); }
} // namespace polytropic_eos_test_utils
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { TEST_CASE("Polytropic EOS Satisfies Its Thermodynamic Identities",
for (const double enthalpy : positiveValues) { tags::barotrope_eos_unit) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy)); constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative( constexpr std::array<double, 4> densities{1.0e-4, 0.02, 0.37, 2.4};
[&barotrope](const double perturbedEnthalpy) {
return barotrope.pressure_from_enthalpy(perturbedEnthalpy);
},
enthalpy, step
);
const double analyticDerivative = barotrope.pressure_derivative_from_enthalpy(enthalpy); constexpr double polytropicConstant = 0.73;
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative); for (const double polytropicIndex : polytropicIndices) {
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)); const double expectedEnthalpyScale =
} (polytropicIndex + 1.0) * polytropicConstant;
for (const double density : positiveValues) { CHECK(barotrope.polytropic_index() == polytropicIndex);
const double step = 2.0e-6 * std::max(1.0, std::abs(density));
const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative( CHECK(barotrope.polytropic_constant() == polytropicConstant);
[&barotrope](const double perturbedDensity) {
return barotrope.pressure_from_density(perturbedDensity);
},
density, step
);
const double analyticDerivative = barotrope.pressure_derivative_from_density(density); CHECK(barotrope.enthalpy_scale() == expectedEnthalpyScale);
CAPTURE(polytropicIndex, density, step, numericalDerivative, analyticDerivative); for (const double density : densities) {
CAPTURE(polytropicIndex, polytropicConstant, density);
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)); const double expectedPressure =
} polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex);
}
const double expectedEnthalpy =
expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex);
const double pressureFromDensity =
barotrope.pressure_from_density(density);
const double enthalpyFromDensity =
barotrope.enthalpy_from_density(density);
const double recoveredDensity =
barotrope.density_from_enthalpy(enthalpyFromDensity);
const double pressureFromEnthalpy =
barotrope.pressure_from_enthalpy(enthalpyFromDensity);
CHECK_THAT(pressureFromDensity,
Catch::Matchers::WithinRel(expectedPressure, 2.0e-13));
CHECK_THAT(enthalpyFromDensity,
Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13));
CHECK_THAT(recoveredDensity,
Catch::Matchers::WithinRel(density, 5.0e-13));
CHECK_THAT(pressureFromEnthalpy,
Catch::Matchers::WithinRel(expectedPressure, 5.0e-13));
/*
* Polytropic identity:
*
* P = rho h / (n + 1).
*/
CHECK_THAT(pressureFromEnthalpy,
Catch::Matchers::WithinRel(density * enthalpyFromDensity /
(polytropicIndex + 1.0),
5.0e-13));
/*
* Polytropic identity:
*
* dP / dh = rho.
*
* The implementation should return the same
* value as density_from_enthalpy().
*/
CHECK(
barotrope.pressure_derivative_from_enthalpy(enthalpyFromDensity) ==
barotrope.density_from_enthalpy(enthalpyFromDensity));
/*
* Since
*
* h = (n + 1) K rho^(1/n),
*
* it follows that
*
* dP / d rho = h / n.
*/
CHECK_THAT(barotrope.pressure_derivative_from_density(density),
Catch::Matchers::WithinRel(
enthalpyFromDensity / polytropicIndex, 5.0e-13));
}
} }
}
} }
TEST_CASE( TEST_CASE("Polytropic EOS Pressure Derivatives Match Centered Differences",
"Polytropic Barotrope Density Derivative Matches Centered Differences", tags::barotrope_eos_jacobian) {
tags::barotrope &tags::physics &tags::unit &tags::jacobian &tags::pressure constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr std::array<double, 3> enthalpies{0.2, 0.73, 1.8}; constexpr std::array<double, 3> positiveValues{0.2, 0.73, 1.8};
constexpr double polytropicConstant = 0.61; constexpr double polytropicConstant = 0.61;
for (const double polytropicIndex : polytropicIndices) { for (const double polytropicIndex : polytropicIndices) {
const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant); const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : enthalpies) { for (const double enthalpy : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy)); const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative( const double numericalDerivative =
[&barotrope](const double perturbedEnthalpy) { polytropic_eos_test_utils::centered_derivative(
return barotrope.density_from_enthalpy(perturbedEnthalpy); [&barotrope](const double perturbedEnthalpy) {
}, return barotrope.pressure_from_enthalpy(perturbedEnthalpy);
enthalpy, step },
); enthalpy, step);
const double analyticDerivative = barotrope.density_derivative_from_enthalpy(enthalpy); const double analyticDerivative =
barotrope.pressure_derivative_from_enthalpy(enthalpy);
CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative); CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative,
analyticDerivative);
CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)); CHECK_THAT(numericalDerivative,
} Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
} }
for (const double density : positiveValues) {
const double step = 2.0e-6 * std::max(1.0, std::abs(density));
const double numericalDerivative =
polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedDensity) {
return barotrope.pressure_from_density(perturbedDensity);
},
density, step);
const double analyticDerivative =
barotrope.pressure_derivative_from_density(density);
CAPTURE(polytropicIndex, density, step, numericalDerivative,
analyticDerivative);
CHECK_THAT(numericalDerivative,
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
}
} }
}
} }
TEST_CASE( TEST_CASE("Polytropic EOS Density Derivative Matches Centered Differences",
"Polytropic Barotrope Defines Consistent Surface And Exterior Behavior", tags::barotrope_eos_jacobian) {
tags::barotrope &tags::physics &tags::unit &tags::pressure constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
) {
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
constexpr double polytropicConstant = 0.47; constexpr std::array<double, 3> enthalpies{0.2, 0.73, 1.8};
constexpr double exteriorEnthalpy = -0.3;
for (const double polytropicIndex : polytropicIndices) { constexpr double polytropicConstant = 0.61;
const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant);
DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { for (const double polytropicIndex : polytropicIndices) {
/* const mean_field::eos::Polytrope barotrope(polytropicIndex,
* Exact surface values. polytropicConstant);
*/
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0); DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
for (const double enthalpy : enthalpies) {
const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy));
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0); const double numericalDerivative =
polytropic_eos_test_utils::centered_derivative(
[&barotrope](const double perturbedEnthalpy) {
return barotrope.density_from_enthalpy(perturbedEnthalpy);
},
enthalpy, step);
CHECK(barotrope.pressure_from_density(0.0) == 0.0); const double analyticDerivative =
barotrope.density_derivative_from_enthalpy(enthalpy);
CHECK(barotrope.enthalpy_from_density(0.0) == 0.0); CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative,
analyticDerivative);
CHECK(barotrope.pressure_derivative_from_density(0.0) == 0.0); CHECK_THAT(numericalDerivative,
Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8));
/* }
* Positive-part extension into h < 0.
*/
CHECK(barotrope.density_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK(barotrope.pressure_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK(barotrope.density_derivative_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK(barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) == 0.0);
/*
* At h = 0, rho(h) has a nonzero right
* derivative only for n = 1.
*/
const double expectedSurfaceDensityDerivative =
polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0;
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == expectedSurfaceDensityDerivative);
}
} }
}
} }
TEST_CASE( TEST_CASE("Polytropic EOS Defines Consistent Surface And Exterior Behavior",
"Polytropic Barotrope Rejects Invalid Physical Inputs", tags::barotrope_eos_unit) {
tags::barotrope &tags::physics &tags::unit &tags::pressure constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
) {
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(0.999, 1.0), std::invalid_argument);
CHECK_THROWS_AS( constexpr double polytropicConstant = 0.47;
mean_field::physics::PolytropicBarotrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument constexpr double exteriorEnthalpy = -0.3;
);
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(3.0, 0.0), std::invalid_argument); for (const double polytropicIndex : polytropicIndices) {
const mean_field::eos::Polytrope barotrope(polytropicIndex,
polytropicConstant);
CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(3.0, -1.0), std::invalid_argument); DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) {
/*
* Exact surface values.
*/
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.75); CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
CHECK_THROWS_AS(barotrope.pressure_from_density(-0.1), std::domain_error); CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-0.1), std::domain_error); CHECK(barotrope.pressure_from_density(0.0) == 0.0);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_density(-0.1), std::domain_error); CHECK(barotrope.enthalpy_from_density(0.0) == 0.0);
constexpr std::array<double, 3> nonfiniteValues{ CHECK(barotrope.pressure_derivative_from_density(0.0) == 0.0);
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()
};
for (const double nonfiniteValue : nonfiniteValues) { /*
CAPTURE(nonfiniteValue); * Positive-part extension into h < 0.
*/
CHECK(barotrope.density_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK_THROWS_AS(barotrope.density_from_enthalpy(nonfiniteValue), std::domain_error); CHECK(barotrope.pressure_from_enthalpy(exteriorEnthalpy) == 0.0);
CHECK_THROWS_AS(barotrope.pressure_from_enthalpy(nonfiniteValue), std::domain_error); CHECK(barotrope.density_derivative_from_enthalpy(exteriorEnthalpy) ==
0.0);
CHECK_THROWS_AS(barotrope.density_derivative_from_enthalpy(nonfiniteValue), std::domain_error); CHECK(barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) ==
0.0);
CHECK_THROWS_AS(barotrope.pressure_derivative_from_enthalpy(nonfiniteValue), std::domain_error); /*
* At h = 0, rho(h) has a nonzero right
* derivative only for n = 1.
*/
const double expectedSurfaceDensityDerivative =
polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0;
CHECK(barotrope.density_derivative_from_enthalpy(0.0) ==
expectedSurfaceDensityDerivative);
} }
}
} }
TEST_CASE( TEST_CASE("Polytropic EOS Rejects Invalid Physical Inputs",
"Pressure Force And Pressure Integral Have Distinct Registered Forms", tags::barotrope_eos_unit) {
tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature &tags::unit CHECK_THROWS_AS(mean_field::eos::Polytrope(0.999, 1.0),
) { std::invalid_argument);
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
/* CHECK_THROWS_AS(
* For the registered H1 order p = 3 and n = 3: mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
* std::invalid_argument);
* h has degree p,
* P(h) has degree 4p,
*
* so the nonlinear EOS contributes an additional
*
* 4p - p = 3p = 9
*
* beyond the registered enthalpy operand.
*/
constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder; CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
constexpr int geometryWeightOrder = 2; CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, -1.0), std::invalid_argument);
constexpr mean_field::quadrature::Query pressureIntegralQuery = const mean_field::eos::Polytrope barotrope(3.0, 0.75);
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, geometryWeightOrder,
std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
constexpr mean_field::quadrature::Query pressureForceQuery = CHECK_THROWS_AS(barotrope.pressure_from_density(-0.1), std::domain_error);
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder,
std::array<int, 1>{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureIntegral::dynamicOrderCount == 1); CHECK_THROWS_AS(barotrope.enthalpy_from_density(-0.1), std::domain_error);
STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1); CHECK_THROWS_AS(barotrope.pressure_derivative_from_density(-0.1),
std::domain_error);
STATIC_CHECK( constexpr std::array<double, 3> nonfiniteValues{
mean_field::field::Enthalpy::Form::PressureIntegral::policyKey != std::numeric_limits<double>::infinity(),
mean_field::field::Enthalpy::Form::PressureForce::policyKey -std::numeric_limits<double>::infinity(),
); std::numeric_limits<double>::quiet_NaN()};
REQUIRE(pressureIntegralQuery.base_order.has_value()); for (const double nonfiniteValue : nonfiniteValues) {
CAPTURE(nonfiniteValue);
REQUIRE(pressureForceQuery.base_order.has_value()); CHECK_THROWS_AS(barotrope.density_from_enthalpy(nonfiniteValue),
std::domain_error);
/* CHECK_THROWS_AS(barotrope.pressure_from_enthalpy(nonfiniteValue),
* Pressure integral: std::domain_error);
*
* degree(P) + degree(J)
* = 12 + 2
* = 14.
*/
CHECK(*pressureIntegralQuery.base_order == 14);
/* CHECK_THROWS_AS(barotrope.density_derivative_from_enthalpy(nonfiniteValue),
* Pressure force: std::domain_error);
*
* degree(P)
* + degree(grad w)
* + degree(J)
*
* = 12 + 2 + 2
* = 16.
*/
CHECK(*pressureForceQuery.base_order == 16);
CHECK(pressureIntegralQuery.term == mean_field::quadrature::Term::pressure_integral); CHECK_THROWS_AS(barotrope.pressure_derivative_from_enthalpy(nonfiniteValue),
std::domain_error);
CHECK(pressureForceQuery.term == mean_field::quadrature::Term::pressure_force); }
CHECK(pressureIntegralQuery.role == mean_field::quadrature::QuadratureRole::diagnostic);
CHECK(pressureForceQuery.role == mean_field::quadrature::QuadratureRole::discretization);
CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR);
CHECK(pressureForceQuery.domain == mean_field::utils::DOMAINS::STELLAR);
/*
* Verify that the two terms route to independent policy
* controls.
*/
mean_field::quadrature::RuleSet ruleSet =
mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production);
ruleSet.pressure_integral.boost = 3;
ruleSet.pressure_force.boost = 5;
const mean_field::quadrature::Policy policy(std::move(ruleSet));
const mean_field::quadrature::Resolution pressureIntegralResolution = policy.resolve(pressureIntegralQuery);
const mean_field::quadrature::Resolution pressureForceResolution = policy.resolve(pressureForceQuery);
CHECK(pressureIntegralResolution.base_order == 14);
CHECK(pressureIntegralResolution.boost == 3);
CHECK(pressureIntegralResolution.order == 17);
CHECK(pressureForceResolution.base_order == 16);
CHECK(pressureForceResolution.boost == 5);
CHECK(pressureForceResolution.order == 21);
} }
TEST_CASE( TEST_CASE("Pressure Force And Pressure Integral Have Distinct Registered Forms",
"Pressure Quadrature Exactly Integrates An N Three Polynomial", tags::barotrope_pressure_quadrature_unit) {
tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature &tags::accuracy using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder; /*
* For the registered H1 order p = 3 and n = 3:
*
* h has degree p,
* P(h) has degree 4p,
*
* so the nonlinear EOS contributes an additional
*
* 4p - p = 3p = 9
*
* beyond the registered enthalpy operand.
*/
constexpr int enthalpyOrder =
mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder; constexpr int pressureExtraOrder = 3 * enthalpyOrder;
/* constexpr int geometryWeightOrder = 2;
* K = 1/4 and n = 3 give
*
* (n + 1) K = 1,
* rho(h) = h^3,
* P(h) = h^4 / 4.
*/
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
constexpr mean_field::quadrature::Query pressureIntegralQuery = constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureIntegral>( EnthalpyField::make_query<
mean_field::quadrature::QuadratureRole::diagnostic, 0, std::array<int, 1>{pressureExtraOrder}, mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine mean_field::quadrature::QuadratureRole::diagnostic,
); geometryWeightOrder, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
constexpr mean_field::quadrature::Query pressureForceQuery = constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>( EnthalpyField::make_query<
mean_field::quadrature::QuadratureRole::discretization, 0, std::array<int, 1>{pressureExtraOrder}, mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine mean_field::quadrature::QuadratureRole::discretization,
); geometryWeightOrder, std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general);
const mean_field::quadrature::RuleFactory ruleFactory{ STATIC_CHECK(
mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production)) mean_field::field::Enthalpy::Form::PressureIntegral::dynamicOrderCount ==
}; 1);
const mean_field::quadrature::MfemRule pressureIntegralRule = STATIC_CHECK(
ruleFactory.get(pressureIntegralQuery, mfem::Geometry::CUBE); mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1);
const mean_field::quadrature::MfemRule pressureForceRule = STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureIntegral::policyKey !=
ruleFactory.get(pressureForceQuery, mfem::Geometry::CUBE); mean_field::field::Enthalpy::Form::PressureForce::policyKey);
/* REQUIRE(pressureIntegralQuery.base_order.has_value());
* On the reference cube [0,1]^3 choose
*
* h = x^3 y^3 z^3.
*
* This is representable by the order-three H1 space.
* Then
*
* P = x^12 y^12 z^12 / 4.
*/
const double numericalPressureIntegral = polytropic_barotrope_test_utils::integrate_cube(
*pressureIntegralRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0); REQUIRE(pressureForceQuery.base_order.has_value());
/*
* Pressure integral:
*
* degree(P) + degree(J)
* = 12 + 2
* = 14.
*/
CHECK(*pressureIntegralQuery.base_order == 14);
/*
* Pressure force:
*
* degree(P)
* + degree(grad w)
* + degree(J)
*
* = 12 + 2 + 2
* = 16.
*/
CHECK(*pressureForceQuery.base_order == 16);
CHECK(pressureIntegralQuery.term ==
mean_field::quadrature::Term::pressure_integral);
CHECK(pressureForceQuery.term ==
mean_field::quadrature::Term::pressure_force);
CHECK(pressureIntegralQuery.role ==
mean_field::quadrature::QuadratureRole::diagnostic);
CHECK(pressureForceQuery.role ==
mean_field::quadrature::QuadratureRole::discretization);
CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR);
CHECK(pressureForceQuery.domain == mean_field::utils::DOMAINS::STELLAR);
/*
* Verify that the two terms route to independent policy
* controls.
*/
mean_field::quadrature::RuleSet ruleSet =
mean_field::quadrature::make_rule_set(
mean_field::quadrature::Mode::production);
ruleSet.pressure_integral.boost = 3;
ruleSet.pressure_force.boost = 5;
const mean_field::quadrature::Policy policy(std::move(ruleSet));
const mean_field::quadrature::Resolution pressureIntegralResolution =
policy.resolve(pressureIntegralQuery);
const mean_field::quadrature::Resolution pressureForceResolution =
policy.resolve(pressureForceQuery);
CHECK(pressureIntegralResolution.base_order == 14);
CHECK(pressureIntegralResolution.boost == 3);
CHECK(pressureIntegralResolution.order == 17);
CHECK(pressureForceResolution.base_order == 16);
CHECK(pressureForceResolution.boost == 5);
CHECK(pressureForceResolution.order == 21);
}
TEST_CASE("Pressure Quadrature Exactly Integrates An N Three Polynomial",
tags::barotrope_pressure_quadrature_accuracy) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
constexpr int enthalpyOrder =
mean_field::field::Enthalpy::Scalar::familyOrder;
constexpr int pressureExtraOrder = 3 * enthalpyOrder;
/*
* K = 1/4 and n = 3 give
*
* (n + 1) K = 1,
* rho(h) = h^3,
* P(h) = h^4 / 4.
*/
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
constexpr mean_field::quadrature::Query pressureIntegralQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureIntegral>(
mean_field::quadrature::QuadratureRole::diagnostic, 0,
std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::affine);
constexpr mean_field::quadrature::Query pressureForceQuery =
EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, 0,
std::array<int, 1>{pressureExtraOrder},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::affine);
const mean_field::quadrature::RuleFactory ruleFactory{
mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set(
mean_field::quadrature::Mode::production))};
const mean_field::quadrature::MfemRule pressureIntegralRule =
ruleFactory.get(pressureIntegralQuery, mfem::Geometry::CUBE);
const mean_field::quadrature::MfemRule pressureForceRule =
ruleFactory.get(pressureForceQuery, mfem::Geometry::CUBE);
/*
* On the reference cube [0,1]^3 choose
*
* h = x^3 y^3 z^3.
*
* This is representable by the order-three H1 space.
* Then
*
* P = x^12 y^12 z^12 / 4.
*/
const double numericalPressureIntegral =
polytropic_eos_test_utils::integrate_cube(
*pressureIntegralRule.integration_rule,
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct =
integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0);
return barotrope.pressure_from_enthalpy(enthalpy); return barotrope.pressure_from_enthalpy(enthalpy);
} });
);
const double analyticPressureIntegral = 0.25 / std::pow(13.0, 3.0); const double analyticPressureIntegral = 0.25 / std::pow(13.0, 3.0);
/* /*
* Choose a representable vector test function whose * Choose a representable vector test function whose
* divergence is * divergence is
* *
* div(w) = x^2 y^2 z^2. * div(w) = x^2 y^2 z^2.
* *
* Therefore * Therefore
* *
* -P div(w) * -P div(w)
* = -x^14 y^14 z^14 / 4. * = -x^14 y^14 z^14 / 4.
*/ */
const double numericalPressureForceIntegral = polytropic_barotrope_test_utils::integrate_cube( const double numericalPressureForceIntegral =
*pressureForceRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) { polytropic_eos_test_utils::integrate_cube(
const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z; *pressureForceRule.integration_rule,
[&barotrope](const mfem::IntegrationPoint &integrationPoint) {
const double coordinateProduct =
integrationPoint.x * integrationPoint.y * integrationPoint.z;
const double enthalpy = std::pow(coordinateProduct, 3.0); const double enthalpy = std::pow(coordinateProduct, 3.0);
const double pressure = barotrope.pressure_from_enthalpy(enthalpy); const double pressure = barotrope.pressure_from_enthalpy(enthalpy);
const double testDivergence = integrationPoint.x * integrationPoint.x * integrationPoint.y * const double testDivergence =
integrationPoint.y * integrationPoint.z * integrationPoint.z; integrationPoint.x * integrationPoint.x * integrationPoint.y *
integrationPoint.y * integrationPoint.z * integrationPoint.z;
return -pressure * testDivergence; return -pressure * testDivergence;
} });
);
const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0); const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0);
INFO("Pressure-integral quadrature order = " << pressureIntegralRule.resolution.order); INFO("Pressure-integral quadrature order = "
<< pressureIntegralRule.resolution.order);
INFO("Pressure-force quadrature order = " << pressureForceRule.resolution.order); INFO("Pressure-force quadrature order = "
<< pressureForceRule.resolution.order);
INFO("Numerical pressure integral = " << numericalPressureIntegral); INFO("Numerical pressure integral = " << numericalPressureIntegral);
INFO("Analytic pressure integral = " << analyticPressureIntegral); INFO("Analytic pressure integral = " << analyticPressureIntegral);
INFO("Numerical pressure-force integral = " << numericalPressureForceIntegral); INFO(
"Numerical pressure-force integral = " << numericalPressureForceIntegral);
INFO("Analytic pressure-force integral = " << analyticPressureForceIntegral); INFO("Analytic pressure-force integral = " << analyticPressureForceIntegral);
CHECK(pressureIntegralRule.resolution.base_order == 12); CHECK(pressureIntegralRule.resolution.base_order == 12);
CHECK(pressureIntegralRule.resolution.order == 12); CHECK(pressureIntegralRule.resolution.order == 12);
CHECK(pressureForceRule.resolution.base_order == 14); CHECK(pressureForceRule.resolution.base_order == 14);
CHECK(pressureForceRule.resolution.order == 14); CHECK(pressureForceRule.resolution.order == 14);
CHECK_THAT(numericalPressureIntegral, Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14)); CHECK_THAT(numericalPressureIntegral,
Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14));
CHECK_THAT(numericalPressureForceIntegral, Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14)); CHECK_THAT(
} numericalPressureForceIntegral,
Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14));
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More