feat(libmeanfield): centrifugal + pressure

This commit is contained in:
2026-08-04 14:24:55 -04:00
parent 9bc4f2758a
commit dc912fd15e
115 changed files with 260058 additions and 163261 deletions

View File

@@ -1,53 +1,110 @@
module;
#include <array>
#include <mfem.hpp>
module mean_field;
import :mapping.coefficients;
namespace {
template <typename FormT>
const mfem::IntegrationRule &get_density_rule(
const mean_field::fem::FEM &fem,
const mfem::ElementTransformation &transformation,
const std::array<
int,
FormT::dynamicOrderCount> &dynamic_orders = {},
const mean_field::utils::DOMAINS domain =
mean_field::utils::DOMAINS::ALL
) {
using DensityField =
mean_field::field::Field<mean_field::field::Density>;
const mean_field::quadrature::Query query =
DensityField::make_query<FormT>(
mean_field::quadrature::QuadratureRole::diagnostic,
transformation.OrderW(), dynamic_orders, domain,
fem.has_mapping() ? mean_field::quadrature::MappingKind::general
: mean_field::quadrature::MappingKind::none
);
return *fem.quadratureFactory
->get(query, transformation.GetGeometryType())
.integration_rule;
}
} // namespace
namespace mean_field::analysis {
double domain_integrate_grid_function(const fem::FEM &fem, const mfem::GridFunction &gf, utils::DOMAINS domain, mapping::COORDINATE_SPACE coord_space) {
mfem::LinearForm lf(fem.H1_fes.get());
double domain_integrate_grid_function(
const fem::FEM &fem,
const mfem::GridFunction &gf,
utils::DOMAINS domain,
mapping::COORDINATE_SPACE coord_space
) {
mfem::LinearForm lf(fem.densityFes.get());
mfem::GridFunctionCoefficient gf_c(&gf);
double local_integral;
mfem::Array<int> elem_markers;
populate_element_mask(fem.mesh.get(), domain, elem_markers);
const mfem::ElementTransformation &representative_transformation =
*fem.mesh->GetElementTransformation(0);
const mfem::IntegrationRule &integration_rule =
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);
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM takes ownership so memory is not leaked
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM
// takes ownership so memory is not leaked
auto *lf_integrator = new mfem::DomainLFIntegrator(mapped_gf_c);
lf_integrator->SetIntRule(fem.int_rule.get());
lf_integrator->SetIntRule(&integration_rule);
lf.AddDomainIntegrator(lf_integrator, elem_markers);
lf.Assemble();
local_integral = lf.Sum();
} else {
if (coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
MFEM_ABORT(
"Physical evaluation mode requested but no mapping provided. Check domain bounds and mapping setup.");
"Physical evaluation mode requested but no mapping "
"provided. Check "
"domain bounds and mapping setup."
);
}
lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(gf_c), elem_markers);
auto *lf_integrator = new mfem::DomainLFIntegrator(gf_c);
lf_integrator->SetIntRule(&integration_rule);
lf.AddDomainIntegrator(lf_integrator, elem_markers);
lf.Assemble();
local_integral = lf.Sum();
}
double global_integral = 0.0;
MPI_Allreduce(&local_integral, &global_integral, 1, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
MPI_Allreduce(
&local_integral, &global_integral, 1, MPI_DOUBLE, MPI_SUM,
fem.mesh->GetComm()
);
return global_integral;
}
mfem::Vector get_com(const fem::FEM &fem, const mfem::GridFunction &rho) {
mfem::Vector get_com(
const fem::FEM &fem,
const mfem::GridFunction &rho
) {
const int dim = fem.mesh->Dimension();
mfem::Vector local_com(dim);
local_com = 0.0;
local_com = 0.0;
double local_mass = 0.0;
for (int i = 0; i < fem.H1_fes->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3) continue;
mfem::ElementTransformation *trans = fem.H1_fes->GetElementTransformation(i);
const mfem::IntegrationRule &ir = *fem.int_rule;
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3)
continue;
mfem::ElementTransformation *trans =
fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir =
get_density_rule<field::Density::Form::CenterOfMass>(
fem, *trans, std::array<int, 1>{1}, utils::DOMAINS::STELLAR
);
for (int j = 0; j < ir.GetNPoints(); ++j) {
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
@@ -77,11 +134,14 @@ namespace mean_field::analysis {
double global_mass = 0.0;
mfem::Vector global_com(dim);
MPI_Comm comm = fem.H1_fes->GetComm();
MPI_Comm comm = fem.mesh->GetComm();
MPI_Allreduce(&local_mass, &global_mass, 1, MPI_DOUBLE, MPI_SUM, comm);
MPI_Allreduce(local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM, comm);
MPI_Allreduce(
local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM,
comm
);
if (global_mass > 1e-18) {
global_com /= global_mass;
@@ -92,19 +152,32 @@ namespace mean_field::analysis {
return global_com;
}
void conserve_mass(const fem::FEM &fem, mfem::GridFunction &rho, const double target_mass) {
if (const double current_mass = domain_integrate_grid_function(fem, rho, utils::DOMAINS::STELLAR); current_mass > 1e-15)
void conserve_mass(
const fem::FEM &fem,
mfem::GridFunction &rho,
const double target_mass
) {
if (const double current_mass = domain_integrate_grid_function(
fem, rho, utils::DOMAINS::STELLAR
);
current_mass > 1e-15)
rho *= (target_mass / current_mass);
}
double get_moment_of_inertia(const fem::FEM &fem, const mfem::GridFunction &rho) {
double get_moment_of_inertia(
const fem::FEM &fem,
const mfem::GridFunction &rho
) {
auto s2_func = [](const mfem::Vector &x) {
return std::pow(x(0), 2) + std::pow(x(1), 2);
};
std::unique_ptr<mfem::Coefficient> s2_coeff;
if (fem.has_mapping()) {
s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, s2_func);
s2_coeff =
std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(
*fem.mapping, s2_func
);
} else {
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
}
@@ -112,53 +185,82 @@ namespace mean_field::analysis {
mfem::GridFunctionCoefficient rho_coeff(&rho);
mfem::ProductCoefficient I_integrand(rho_coeff, *s2_coeff);
mfem::LinearForm I_lf(fem.H1_fes.get());
mfem::LinearForm I_lf(fem.densityFes.get());
const mfem::ElementTransformation &representative_transformation =
*fem.mesh->GetElementTransformation(0);
const mfem::IntegrationRule &integration_rule =
get_density_rule<field::Density::Form::Quadrupole>(
fem, representative_transformation, std::array<int, 1>{2},
utils::DOMAINS::STELLAR
);
mfem::Array<int> stellar_markers;
populate_element_mask(
fem.mesh.get(), utils::DOMAINS::STELLAR, stellar_markers
);
double I = 0.0;
// TODO: Need to filter here to just the stellar domain and also update the IntRule
double local_I = 0.0;
if (fem.has_mapping()) {
mapping::MappedScalarCoefficient mapped_integrand(*fem.mapping, I_integrand);
I_lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(mapped_integrand));
mapping::MappedScalarCoefficient mapped_integrand(
*fem.mapping, I_integrand
);
auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand);
integrator->SetIntRule(&integration_rule);
I_lf.AddDomainIntegrator(integrator, stellar_markers);
I_lf.Assemble();
I = I_lf.Sum();
local_I = I_lf.Sum();
} else {
I_lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(I_integrand));
auto *integrator = new mfem::DomainLFIntegrator(I_integrand);
integrator->SetIntRule(&integration_rule);
I_lf.AddDomainIntegrator(integrator, stellar_markers);
I_lf.Assemble();
I = I_lf.Sum();
local_I = I_lf.Sum();
}
return I;
double global_I = 0.0;
MPI_Allreduce(
&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()
);
return global_I;
}
double get_mesh_volume(
const fem::FEM& fem,
const fem::FEM &fem,
const mapping::COORDINATE_SPACE coordinate_space,
const utils::DOMAINS domain
) {
mfem::ParMesh &mesh = *fem.mesh;
const mapping::DomainMapper &map = *fem.mapping;
const mfem::IntegrationRule &ir = *fem.int_rule;
const bool physical =
(coordinate_space == mapping::COORDINATE_SPACE::PHYSICAL);
if (physical && !fem.has_mapping()) {
MFEM_ABORT(
"Physical volume requested but no domain mapping is available."
);
}
double local_volume = 0.0;
for (int e = 0; e < mesh.GetNE(); ++e) {
const int attr = mesh.GetAttribute(e);
switch (domain) {
case utils::DOMAINS::ALL:
break;
case utils::DOMAINS::STELLAR:
if (attr == 3) continue;
break;
case utils::DOMAINS::VACUUM:
if (attr != 3) continue;
break;
default:
MFEM_ABORT("Unsupported domain type for volume computation.");
case utils::DOMAINS::ALL:
break;
case utils::DOMAINS::STELLAR:
if (attr == 3)
continue;
break;
case utils::DOMAINS::VACUUM:
if (attr != 3)
continue;
break;
default:
MFEM_ABORT("Unsupported domain type for volume computation.");
}
mfem::ElementTransformation *T = mesh.GetElementTransformation(e);
const mfem::IntegrationRule &ir =
get_density_rule<field::Density::Form::MassConservation>(
fem, *T, {}, domain
);
for (int q = 0; q < ir.GetNPoints(); ++q) {
const mfem::IntegrationPoint &ip = ir.IntPoint(q);
@@ -167,7 +269,7 @@ namespace mean_field::analysis {
double dV = ip.weight * T->Weight();
if (physical) {
dV *= std::fabs(map.ComputeDetJ(*T, ip));
dV *= std::fabs(fem.mapping->ComputeDetJ(*T, ip));
}
local_volume += dV;
@@ -175,8 +277,10 @@ namespace mean_field::analysis {
}
double global_volume = 0.0;
MPI_Allreduce(&local_volume, &global_volume, 1, MPI_DOUBLE, MPI_SUM,
mesh.GetComm());
MPI_Allreduce(
&local_volume, &global_volume, 1, MPI_DOUBLE, MPI_SUM,
mesh.GetComm()
);
return global_volume;
}
}
} // namespace mean_field::analysis

View File

@@ -1,190 +1,560 @@
module;
#include <string>
#include <algorithm>
#include <cmath>
#include <expected>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <mfem.hpp>
#include <stroid/stroid.h>
module mean_field;
import :boundary.contexts;
import :field.mfem;
import :mapping.coefficients;
import :utils.misc;
import :utils.user;
namespace mean_field::fem {
FEM setup_fem(const std::string &filename, const utils::Args &args, const int extra_refine) {
FEM setup_fem(
const std::string &filename,
const utils::Args &args,
const int extraRefine
) {
FEM fem;
//==================================================================
// Section 1: Mesh and FE Space Setup
//==================================================================
fem.smesh = stroid::IO::LoadStroidMesh(filename).value();
if (extra_refine > 0) {
stroid::refinement::UniformRefinement(fem.smesh, extra_refine);
using GravityPotential = field::Gravity::Potential;
using GravityFlux = field::Gravity::Flux;
using DisplacementVector = field::Displacement::Vector;
using DensityScalar = field::Density::Scalar;
using EnthalpyScalar = field::Enthalpy::Scalar;
// =====================================================================
// Section 1: Mesh construction
// =====================================================================
fem.smesh = stroid::IO::LoadStroidMesh(filename).value();
if (extraRefine > 0) {
stroid::refinement::UniformRefinement(fem.smesh, extraRefine);
}
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh);
int mpiSize = 1;
MPI_Comm_size(MPI_COMM_WORLD, &mpiSize);
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->EnsureNodes();
const int geom_order = utils::get_mesh_order(*fem.mesh);
const int dim = fem.mesh->Dimension();
// =====================================================================
// Section 2: Exterior compactification coordinate
// =====================================================================
const int v_order = 2;
const int rho_order = 2;
const int p = rho_order ;
if (fem.smesh.exterior_coordinate == nullptr) {
throw std::runtime_error("Exterior coordinate not set.");
}
const int cb_type = mfem::BasisType::GaussLobatto;
const int ob_type = mfem::BasisType::IntegratedGLL;
if (fem.smesh.exterior_coordinate->space == nullptr) {
throw std::runtime_error("Space for exterior coordinate not set.");
}
fem.RT_fec = std::make_unique<mfem::RT_FECollection>(p, dim, cb_type, ob_type);
fem.RT_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.RT_fec.get());
if (fem.smesh.exterior_coordinate->values == nullptr) {
throw std::runtime_error("Values for exterior coordinate not set.");
}
fem.H1_fec = std::make_unique<mfem::H1_FECollection>(v_order, dim);
fem.L2_fec = std::make_unique<mfem::L2_FECollection>(rho_order, dim);
const mfem::FiniteElementSpace &serialCoordinateSpace =
*fem.smesh.exterior_coordinate->space;
// Gravity (Scalar H1) and Velocity (Vector H1)
fem.H1_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.H1_fec.get());
fem.Vec_H1_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.H1_fec.get(), dim,
mfem::Ordering::byNODES);
const mfem::GridFunction &serialCoordinate =
*fem.smesh.exterior_coordinate->values;
// Density & Pressure (Scalar Discontinuous L2)
fem.L2_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.L2_fec.get());
if (serialCoordinate.FESpace() != &serialCoordinateSpace) {
throw std::runtime_error(
"Exterior coordinate values are not associated with the "
"supplied finite-element space."
);
}
//==================================================================
// Section 2: Domain Mapping
//==================================================================
auto [r_star_ref, r_inf_ref] = utils::discover_bounds(fem.mesh.get(), 3)
.or_else([](const boundary::BoundsError &err)-> std::expected<boundary::Bounds, boundary::BoundsError> {
throw std::runtime_error("Unable to determine vacuum domain reference boundary...");
}).value();
if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) {
throw std::runtime_error(
"Exterior coordinate space is not associated with the "
"loaded STROID mesh."
);
}
fem.mapping = std::make_unique<mapping::DomainMapper>(r_star_ref, r_inf_ref);
if (serialCoordinateSpace.GetVDim() != 1) {
throw std::runtime_error(
"Exterior coordinate must be a scalar field."
);
}
//==================================================================
// Section 3: Multi-physics Block-offsets
//==================================================================
fem.block_true_offsets.SetSize(3);
fem.block_true_offsets[0] = 0;
fem.block_true_offsets[1] = fem.Vec_H1_fes->GetTrueVSize();
fem.block_true_offsets[2] = fem.block_true_offsets[1] + fem.L2_fes->GetTrueVSize();
if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) {
throw std::runtime_error(
"Exterior coordinate value count does not match its "
"finite-element space."
);
}
fem.gravity_block_true_offsets.SetSize(3);
fem.gravity_block_true_offsets[0] = 0;
fem.gravity_block_true_offsets[1] = fem.RT_fes->GetTrueVSize();
fem.gravity_block_true_offsets[2] = fem.gravity_block_true_offsets[1] + fem.L2_fes->GetTrueVSize();
const int compactificationOrder =
serialCoordinateSpace.GetMaxElementOrder();
//==================================================================
// Section 4: Multipole BC setup.
//==================================================================
fem.com.SetSize(dim);
const int dimension = fem.mesh->Dimension();
fem.compactificationFec = std::make_unique<mfem::H1_FECollection>(
compactificationOrder, dimension
);
fem.compactificationFes = std::make_unique<mfem::ParFiniteElementSpace>(
fem.mesh.get(), fem.compactificationFec.get()
);
mfem::ParGridFunction distributedCoordinate(
fem.mesh.get(), &serialCoordinate, meshPartitioning.get()
);
if (distributedCoordinate.Size() !=
fem.compactificationFes->GetVSize()) {
throw std::runtime_error(
"Distributed exterior coordinate does not match the "
"constructed parallel finite-element space."
);
}
fem.compactificationCoordinate =
std::make_unique<mfem::ParGridFunction>(
fem.compactificationFes.get()
);
*fem.compactificationCoordinate = distributedCoordinate;
double localMinimum = std::numeric_limits<double>::infinity();
double localMaximum = -std::numeric_limits<double>::infinity();
for (int index = 0; index < fem.compactificationCoordinate->Size();
++index) {
const double value = (*fem.compactificationCoordinate)(index);
if (!std::isfinite(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(dim, dim);
fem.Q.SetSize(dimension, dimension);
fem.Q = 0.0;
//==================================================================
// Section 5: Integration Rules
//==================================================================
MFEM_ASSERT(fem.mesh->GetElementGeometry(0) == mfem::Geometry::CUBE,
"Currently only hexahedral meshes are supported");
const int element_order = fem.H1_fes->GetMaxElementOrder();
fem.int_order = 2 * element_order + geom_order - 2 + args.quad_boost;
// =====================================================================
// Section 7: Essential boundaries and domain masks
// =====================================================================
fem.int_rule = std::make_unique<mfem::IntegrationRule>(mfem::IntRules.Get(mfem::Geometry::CUBE, fem.int_order));
fem.essentialDisplacementTdofs.SetSize(0);
//==================================================================
// Section 6: Essential Boundaries & Domain Masks
//==================================================================
fem.ess_v_tdofs.SetSize(0);
populate_element_mask(
fem.mesh.get(), utils::DOMAINS::STELLAR,
fem.gravityContext.stellar_mask
);
populate_element_mask(fem.mesh.get(), utils::DOMAINS::STELLAR, fem.gravity_context.stellar_mask);
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
const int n_bdr_attrs = fem.mesh->bdr_attributes.Max();
fem.boundary_context.inf_bounds.SetSize(n_bdr_attrs);
fem.boundary_context.stellar_bounds.SetSize(n_bdr_attrs);
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
fem.boundary_context.inf_bounds = 0;
fem.boundary_context.stellar_bounds = 0;
fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount);
fem.boundary_context.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundary_context.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
fem.boundaryContext.inf_bounds = 0;
fem.boundaryContext.stellar_bounds = 0;
//==================================================================
// Section 7: Gravity Context Setup
//==================================================================
fem.gravity_context.minres = std::make_unique<mfem::MINRESSolver>(fem.mesh->GetComm());
fem.gravity_context.minres->SetRelTol(1e-12);
fem.gravity_context.minres->SetAbsTol(1e-12);
fem.gravity_context.minres->SetMaxIter(1000);
fem.gravity_context.minres->SetPrintLevel(0);
fem.boundaryContext.inf_bounds
[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.gravity_context.prec_Phi = std::make_unique<mfem::HypreBoomerAMG>();
fem.gravity_context.prec_Phi->SetPrintLevel(0);
fem.boundaryContext.stellar_bounds
[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
fem.gravity_context.block_prec = std::make_unique<mfem::BlockDiagonalPreconditioner>(fem.gravity_block_true_offsets);
// =====================================================================
// 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
// =====================================================================
fem.gravity_context.minres->SetPreconditioner(*fem.gravity_context.block_prec);
//=========================================================
// Section 10: Set All vacuum elements true degrees of freedom
//=========================================================
{
mfem::Array<int> vacuum_mask;
utils::populate_element_mask(fem.mesh.get(), utils::DOMAINS::VACUUM, vacuum_mask);
mfem::Array<int> vacuumMask;
utils::populate_domain_tdofs(fem.Vec_H1_fes.get(), vacuum_mask, fem.vacuum_tdof_v);
utils::populate_domain_tdofs(fem.L2_fes.get(), vacuum_mask, fem.vacuum_tdof_rho);
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
);
}
const quadrature::QuadratureOptions& quadrature_options = args.quadrature;
// =====================================================================
// Section 10: Quadrature policy
// =====================================================================
if (quadrature_options.validation.reject_negative_boosts && quadrature_options.global_boost < 0) {
throw std::invalid_argument("Global quadrature boost cannot be negative.");
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 quadrature_rule_set = quadrature::make_rule_set(quadrature_options.mode, quadrature_options.global_boost);
quadrature::RuleSet quadratureRuleSet = quadrature::make_rule_set(
quadratureOptions.mode, quadratureOptions.global_boost
);
if (quadrature_options.fallback_fixed_order.has_value()) {
if (*quadrature_options.fallback_fixed_order < 0) {
throw std::invalid_argument("Fallback quadrature order cannot be negative.");
if (quadratureOptions.fallback_fixed_order.has_value()) {
if (*quadratureOptions.fallback_fixed_order < 0) {
throw std::invalid_argument(
"Fallback quadrature order cannot be negative."
);
}
quadrature_rule_set.fallback.fixed_order = quadrature_options.fallback_fixed_order;
quadratureRuleSet.fallback.fixed_order =
quadratureOptions.fallback_fixed_order;
}
auto apply_quadrature_options = [&quadrature_options](quadrature::RuleControl& rule_control, const quadrature::QuadratureTermOptions& term_options) {
if (term_options.fixed_order.has_value() && *term_options.fixed_order < 0) {
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
}
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 (quadrature_options.validation.reject_negative_boosts && term_options.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
if (quadratureOptions.validation.reject_negative_boosts &&
termOptions.additional_boost < 0) {
throw std::invalid_argument(
"Term quadrature boost cannot be negative."
);
}
rule_control.boost += term_options.additional_boost;
ruleControl.boost += termOptions.additional_boost;
if (term_options.fixed_order.has_value()) {
rule_control.fixed_order = term_options.fixed_order;
}
};
if (termOptions.fixed_order.has_value()) {
ruleControl.fixed_order = termOptions.fixed_order;
}
};
apply_quadrature_options(quadrature_rule_set.gravity_hdiv_mass, quadrature_options.gravity_hdiv_mass);
apply_quadrature_options(quadrature_rule_set.gravity_divergence, quadrature_options.gravity_divergence);
apply_quadrature_options(quadrature_rule_set.gravity_source, quadrature_options.gravity_source);
apply_quadrature_options(quadrature_rule_set.gravity_boundary, quadrature_options.gravity_boundary);
apply_quadrature_options(quadrature_rule_set.density_projection, quadrature_options.density_projection);
apply_quadrature_options(quadrature_rule_set.mass_conservation, quadrature_options.mass_conservation);
apply_quadrature_options(quadrature_rule_set.center_of_mass, quadrature_options.center_of_mass);
apply_quadrature_options(quadrature_rule_set.quadrupole, quadrature_options.quadrupole);
apply_quadrature_options(quadrature_rule_set.gravitational_energy, quadrature_options.gravitational_energy);
apply_quadrature_options(quadrature_rule_set.virial, quadrature_options.virial);
apply_quadrature_options(quadrature_rule_set.error_norm, quadrature_options.error_norm);
apply_quadrature_options(
quadratureRuleSet.gravity_hdiv_mass,
quadratureOptions.gravity_hdiv_mass
);
apply_quadrature_options(quadrature_rule_set.roles.discretization, quadrature_options.roles.discretization);
apply_quadrature_options(quadrature_rule_set.roles.preconditioner, quadrature_options.roles.preconditioner);
apply_quadrature_options(quadrature_rule_set.roles.diagnostic, quadrature_options.roles.diagnostic);
apply_quadrature_options(quadrature_rule_set.roles.projection, quadrature_options.roles.projection);
apply_quadrature_options(
quadratureRuleSet.gravity_divergence,
quadratureOptions.gravity_divergence
);
apply_quadrature_options(
quadratureRuleSet.gravity_source, quadratureOptions.gravity_source
);
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)
);
fem.quadrature_factory = std::make_unique<quadrature::RuleFactory>(quadrature::Policy(std::move(quadrature_rule_set)));
return fem;
}
}
} // namespace mean_field::fem

View File

@@ -4,7 +4,9 @@ module;
module mean_field;
namespace mean_field::integrators {
AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map) : m_map(map) {}
AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map)
: m_map(map) {
}
void AdvectionIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -16,17 +18,17 @@ namespace mean_field::integrators {
return;
}
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &rho_dofs = *elfun[1];
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &rho_dofs = *elfun[1];
mfem::Vector &r_v = *elvec[0];
mfem::Vector &r_v = *elvec[0];
r_v.SetSize(dof_v * dim);
r_v = 0.0;
if (elvec[1]) {
@@ -37,7 +39,8 @@ namespace mean_field::integrators {
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
for (int q = 0; q < ir->GetNPoints(); q++) {
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
@@ -80,7 +83,8 @@ namespace mean_field::integrators {
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
r_v(i + c * dof_v) += shape_v(i) * rho_val * adv_val(c) * weight;
r_v(i + c * dof_v) +=
shape_v(i) * rho_val * adv_val(c) * weight;
}
}
}
@@ -91,27 +95,30 @@ namespace mean_field::integrators {
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
const mfem::FiniteElement *fe_v = el[0];
) {
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &rho_dofs = *elfun[1];
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &rho_dofs = *elfun[1];
mfem::DenseMatrix *dv_dv = elmats(0, 0);
mfem::DenseMatrix *dv_drho = elmats(0, 1);
mfem::DenseMatrix *dv_dv = elmats(0, 0);
mfem::DenseMatrix *dv_drho = elmats(0, 1);
if (dv_dv) *dv_dv = 0.0;
if (dv_drho) *dv_drho = 0.0;
if (dv_dv)
*dv_dv = 0.0;
if (dv_drho)
*dv_drho = 0.0;
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
for (int q = 0; q < ir->GetNPoints(); q++) {
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
@@ -164,22 +171,27 @@ namespace mean_field::integrators {
double v_dot_grad_phi_j = 0.0;
for (int k = 0; k < dim; ++k) {
v_dot_grad_phi_j += v_val(k) * dshape_v_phys(j, k);
v_dot_grad_phi_j +=
v_val(k) * dshape_v_phys(j, k);
}
for (int d = 0; d < dim; ++d) {
// Trial function component
int col = j + d * dof_v;
int col = j + d * dof_v;
// \rho (\delta \vec{v} \cdot \nabla \vec{v})
// \delta v is along direction 'd' for the cth component of advection
// \delta v is along direction 'd' for the cth
// component of advection
double termA = shape_v(j) * grad_v(c, d);
// \rho(\vec{v} \cdot \nabla \delta \vec{v})
// Only non-zero when the advected component matches the test component
double termB = (c == d) ? v_dot_grad_phi_j : 0.0;
// Only non-zero when the advected component
// matches the test component
double termB =
(c == d) ? v_dot_grad_phi_j : 0.0;
(*dv_dv)(row, col) += shape_v(i) * rho_val * (termA + termB) * weight;
(*dv_dv)(row, col) += shape_v(i) * rho_val *
(termA + termB) * weight;
}
}
}
@@ -192,7 +204,7 @@ namespace mean_field::integrators {
for (int c = 0; c < dim; ++c) {
int row = i + c * dof_v;
for (int j = 0; j < dof_rho; ++j) {
int col = j;
int col = j;
// \delta \rho * (\vec{v} \cdot \nabla \vec{v})
double term = shape_rho(j) * adv_val(c);
@@ -203,4 +215,4 @@ namespace mean_field::integrators {
}
}
}
}
} // namespace mean_field::integrators

View File

@@ -3,41 +3,49 @@ module;
module mean_field;
namespace mean_field::integrators {
CentrifugalForceIntegrator::CentrifugalForceIntegrator(
const mapping::DomainMapper& map,
const mfem::Vector& omega
) : m_map(map), m_omega(3) {
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
m_omega = omega;
}
void CentrifugalForceIntegrator::SetOmega(const mfem::Vector& omega) {
CentrifugalForceIntegrator::CentrifugalForceIntegrator(
const mapping::DomainMapper &map,
const mfem::Vector &omega
)
: m_map(map),
m_omega(3) {
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
m_omega = omega;
}
void CentrifugalForceIntegrator::SetOmega(const mfem::Vector &omega) {
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
m_omega = omega;
}
void CentrifugalForceIntegrator::SetIntegrationRule(
const mfem::IntegrationRule &ir
) {
m_ir = &ir;
}
void CentrifugalForceIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
if (utils::is_vacuum(Tr, elvec)) {
return;
}
if (utils::is_vacuum(Tr, elvec)) {
return;
}
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector& rho_dofs = *elfun[1];
const mfem::Vector &rho_dofs = *elfun[1];
mfem::Vector& r_v = *elvec[0];
r_v = 0.0;
mfem::Vector &r_v = *elvec[0];
r_v.SetSize(dof_v * dim);
r_v = 0.0;
if (elvec[1]) {
elvec[1]->SetSize(dof_rho);
*elvec[1] = 0.0;
@@ -47,10 +55,15 @@ namespace mean_field::integrators {
mfem::Vector x_phys(dim);
mfem::Vector a(dim), b(dim);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
MFEM_VERIFY(
m_ir, "CentrifugalForceIntegrator must be configured with an "
"integration rule before assembly. Call "
"SetIntegrationRule first."
);
const mfem::IntegrationRule *ir = m_ir;
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -61,14 +74,14 @@ namespace mean_field::integrators {
m_map.GetPhysicalPoint(Tr, ip, x_phys);
// ω x r
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
a(1) = m_omega(2) * x_phys(0) - m_omega(0) * x_phys(2);
a(2) = m_omega(0) * x_phys(1) - m_omega(1) * x_phys(0);
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
a(1) = m_omega(2) * x_phys(0) - m_omega(0) * x_phys(2);
a(2) = m_omega(0) * x_phys(1) - m_omega(1) * x_phys(0);
// ω x (ω x r) [centrifugal acceleration]
b(0) = m_omega(1) * a(2) - m_omega(2) * a(1);
b(1) = m_omega(2) * a(0) - m_omega(0) * a(2);
b(2) = m_omega(0) * a(1) - m_omega(1) * a(0);
b(0) = m_omega(1) * a(2) - m_omega(2) * a(1);
b(1) = m_omega(2) * a(0) - m_omega(0) * a(2);
b(2) = m_omega(0) * a(1) - m_omega(1) * a(0);
double rho_val = 0.0;
for (int i = 0; i < dof_rho; ++i) {
@@ -89,30 +102,39 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
if (utils::is_vacuum(Tr, elmats)) {
return;
}
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
mfem::DenseMatrix* dv_dv = elmats(0,0);
mfem::DenseMatrix* dv_drho = elmats(0,1);
mfem::DenseMatrix *dv_dv = elmats(0, 0);
mfem::DenseMatrix *dv_drho = elmats(0, 1);
if (dv_dv) *dv_dv = 0.0;
if (elmats(1, 0)) *elmats(1, 0) = 0.0;
if (elmats(1, 1)) *elmats(1, 1) = 0.0;
if (dv_drho) *dv_drho = 0.0;
if (!dv_drho) return;
if (dv_dv)
*dv_dv = 0.0;
if (elmats(1, 0))
*elmats(1, 0) = 0.0;
if (elmats(1, 1))
*elmats(1, 1) = 0.0;
if (dv_drho)
*dv_drho = 0.0;
if (!dv_drho)
return;
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::Vector x_phys(dim);
mfem::Vector a(dim), b(dim);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -137,10 +159,11 @@ namespace mean_field::integrators {
for (int c = 0; c < dim; ++c) {
const int row = i + c * dof_v;
for (int j = 0; j < dof_rho; ++j) {
(*dv_drho)(row, j) += shape_v(i) * shape_rho(j) * b(c) * weight;
(*dv_drho)(row, j) +=
shape_v(i) * shape_rho(j) * b(c) * weight;
}
}
}
}
}
}
} // namespace mean_field::integrators

View File

@@ -4,38 +4,43 @@ module;
module mean_field;
namespace mean_field::integrators {
CoriolisIntegrator::CoriolisIntegrator(const mapping::DomainMapper& map, const mfem::Vector& omega)
: m_map(map), m_omega(omega) {
CoriolisIntegrator::CoriolisIntegrator(
const mapping::DomainMapper &map,
const mfem::Vector &omega
)
: m_map(map),
m_omega(omega) {
m_omega_mat.SetSize(3, 3);
m_omega_mat = 0.0;
m_omega_mat = 0.0;
m_omega_mat(0, 1) = -m_omega(2);
m_omega_mat(0, 2) = m_omega(1);
m_omega_mat(1, 0) = m_omega(2);
m_omega_mat(0, 2) = m_omega(1);
m_omega_mat(1, 0) = m_omega(2);
m_omega_mat(1, 2) = -m_omega(0);
m_omega_mat(2, 0) = -m_omega(1);
m_omega_mat(2, 1) = m_omega(0);
m_omega_mat(2, 1) = m_omega(0);
}
void CoriolisIntegrator::AssembleElementVector(const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
void CoriolisIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
if (utils::is_vacuum(Tr, elvec)) {
return;
}
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector& v_dofs = *elfun[0];
const mfem::Vector& rho_dofs = *elfun[1];
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &rho_dofs = *elfun[1];
mfem::Vector& r_v = *elvec[0];
mfem::Vector &r_v = *elvec[0];
r_v.SetSize(dof_v * dim);
r_v = 0.0;
if (elvec[1]) {
@@ -44,10 +49,11 @@ namespace mean_field::integrators {
}
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -56,11 +62,14 @@ namespace mean_field::integrators {
fe_rho->CalcShape(ip, shape_rho);
double rho_val = 0.0;
for (int i = 0; i < dof_rho; ++i) rho_val += rho_dofs(i) * shape_rho(i);
for (int i = 0; i < dof_rho; ++i)
rho_val += rho_dofs(i) * shape_rho(i);
mfem::Vector v_val(dim); v_val = 0.0;
mfem::Vector v_val(dim);
v_val = 0.0;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
for (int c = 0; c < dim; ++c)
v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
}
mfem::Vector F_coriolis(dim);
@@ -69,39 +78,44 @@ namespace mean_field::integrators {
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
r_v(i + c * dof_v) += shape_v(i) * rho_val * F_coriolis(c) * weight;
r_v(i + c * dof_v) +=
shape_v(i) * rho_val * F_coriolis(c) * weight;
}
}
}
}
void CoriolisIntegrator::AssembleElementGrad(const mfem::Array<const mfem::FiniteElement*> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
void CoriolisIntegrator::AssembleElementGrad(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector& v_dofs = *elfun[0];
const mfem::Vector& rho_dofs = *elfun[1];
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &rho_dofs = *elfun[1];
mfem::DenseMatrix* dv_dv = elmats(0, 0);
mfem::DenseMatrix* dv_drho = elmats(0, 1);
mfem::DenseMatrix *dv_dv = elmats(0, 0);
mfem::DenseMatrix *dv_drho = elmats(0, 1);
if (dv_dv) *dv_dv = 0.0;
if (dv_drho) *dv_drho = 0.0;
if (dv_dv)
*dv_dv = 0.0;
if (dv_drho)
*dv_drho = 0.0;
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -110,11 +124,14 @@ namespace mean_field::integrators {
fe_rho->CalcShape(ip, shape_rho);
double rho_val = 0.0;
for (int i = 0; i < dof_rho; ++i) rho_val += rho_dofs(i) * shape_rho(i);
for (int i = 0; i < dof_rho; ++i)
rho_val += rho_dofs(i) * shape_rho(i);
mfem::Vector v_val(dim); v_val = 0.0;
mfem::Vector v_val(dim);
v_val = 0.0;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
for (int c = 0; c < dim; ++c)
v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
}
mfem::Vector F_coriolis(dim);
@@ -124,12 +141,14 @@ namespace mean_field::integrators {
if (dv_dv) {
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
int row = i + c * dof_v;
const int row = i + c * dof_v;
for (int j = 0; j < dof_v; ++j) {
for (int d = 0; d < dim; ++d) {
int col = j + d * dof_v;
int col = j + d * dof_v;
double coupling = m_omega_mat(c, d);
(*dv_dv)(row, col) += shape_v(i) * shape_v(j) * 2.0 * rho_val * coupling * weight;
(*dv_dv)(row, col) += shape_v(i) * shape_v(j) *
2.0 * rho_val * coupling *
weight;
}
}
}
@@ -142,11 +161,12 @@ namespace mean_field::integrators {
int row = i + c * dof_v;
for (int j = 0; j < dof_rho; ++j) {
int col = j;
(*dv_drho)(row, col) += shape_v(i) * shape_rho(j) * F_coriolis(c) * weight;
(*dv_drho)(row, col) += shape_v(i) * shape_rho(j) *
F_coriolis(c) * weight;
}
}
}
}
}
}
}
} // namespace mean_field::integrators

View File

@@ -1,124 +1,341 @@
module;
#include <mfem.hpp>
module mean_field;
import :solver.fields;
namespace {
using namespace mean_field;
constexpr int velocity_block =
solver::block_index(solver::FieldBlock::velocity);
constexpr int density_block =
solver::block_index(solver::FieldBlock::density);
constexpr int gravity_gradient_block =
solver::block_index(solver::FieldBlock::gravity_gradient);
constexpr int displacement_block =
solver::block_index(solver::FieldBlock::displacement);
} // namespace
namespace mean_field::integrators {
GravityForceIntegrator::GravityForceIntegrator(
const mapping::DomainMapper& map,
const mfem::GridFunction& phi
): m_map(map), m_phi(&phi) {}
GravityMomentumIntegrator::GravityMomentumIntegrator(
const mapping::DomainMapper &map,
const GravityForceJacobianMode jacobian_mode
)
: m_map(map),
m_jacobian_mode(jacobian_mode) {
}
void GravityForceIntegrator::SetPotential(const mfem::GridFunction& phi) { m_phi = &phi; };
void GravityMomentumIntegrator::SetJacobianMode(
const GravityForceJacobianMode jacobian_mode
) {
m_jacobian_mode = jacobian_mode;
}
void GravityForceIntegrator::AssembleElementVector(
void GravityMomentumIntegrator::SetIntegrationRule(
const mfem::IntegrationRule &integration_rule
) {
m_integration_rule = &integration_rule;
}
GravityForceJacobianMode
GravityMomentumIntegrator::GetJacobianMode() const {
return m_jacobian_mode;
}
void GravityMomentumIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
if (utils::is_vacuum(Tr, elvec)) {
return;
}
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
MFEM_VERIFY(
m_integration_rule,
"GravityForceIntegrator must be configured with an "
"integration rule before assembly."
);
MFEM_VERIFY(
el.Size() > gravity_gradient_block,
"GravityForceIntegrator requires velocity, density, and "
"gravity-gradient finite elements."
);
MFEM_VERIFY(
elfun.Size() > gravity_gradient_block,
"GravityForceIntegrator requires velocity, density, and "
"gravity-gradient element states."
);
MFEM_VERIFY(
elvec.Size() > velocity_block && elvec[velocity_block],
"GravityForceIntegrator requires a velocity residual block."
);
MFEM_VERIFY(
el[velocity_block] && el[density_block] &&
el[gravity_gradient_block],
"GravityForceIntegrator received a null finite element."
);
MFEM_VERIFY(
elfun[density_block] && elfun[gravity_gradient_block],
"GravityForceIntegrator received a null element state."
);
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::FiniteElement *velocity_element = el[velocity_block];
const mfem::FiniteElement *density_element = el[density_block];
const mfem::FiniteElement *gravity_gradient_element =
el[gravity_gradient_block];
const mfem::Vector& rho_dofs = *elfun[1];
const int velocity_dofs_count = velocity_element->GetDof();
const int density_dofs_count = density_element->GetDof();
const int gravity_gradient_dofs_count =
gravity_gradient_element->GetDof();
const int dim = Tr.GetSpaceDim();
mfem::Vector& r_v = *elvec[0];
r_v.SetSize(dof_v * dim);
r_v = 0.0;
if (elvec[1]) {
elvec[1]->SetSize(dof_rho);
*elvec[1] = 0.0;
const mfem::Vector &density_dofs = *elfun[density_block];
const mfem::Vector &gravity_gradient_dofs =
*elfun[gravity_gradient_block];
MFEM_VERIFY(
density_dofs.Size() == density_dofs_count,
"GravityForceIntegrator received an incorrectly sized density "
"state."
);
MFEM_VERIFY(
gravity_gradient_dofs.Size() == gravity_gradient_dofs_count,
"GravityForceIntegrator received an incorrectly sized "
"gravity-gradient "
"state."
);
for (int block = 0; block < elvec.Size(); ++block) {
if (elvec[block]) {
*elvec[block] = 0.0;
}
}
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::Vector grad_phi_ref(dim), grad_phi_phys(dim), grad_phi_elem(dim);
mfem::Vector &velocity_residual = *elvec[velocity_block];
velocity_residual.SetSize(dim * velocity_dofs_count);
velocity_residual = 0.0;
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
if (elvec.Size() > density_block && elvec[density_block]) {
elvec[density_block]->SetSize(density_dofs_count);
*elvec[density_block] = 0.0;
}
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
if (elvec.Size() > gravity_gradient_block &&
elvec[gravity_gradient_block]) {
elvec[gravity_gradient_block]->SetSize(gravity_gradient_dofs_count);
*elvec[gravity_gradient_block] = 0.0;
}
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
m_phi->GetGradient(Tr, grad_phi_elem);
mfem::DenseMatrix J_map(dim, dim), J_map_inv(dim, dim);
m_map.ComputeJacobian(Tr, J_map);
mfem::CalcInverse(J_map, J_map_inv);
J_map_inv.MultTranspose(grad_phi_elem, grad_phi_phys);
mfem::Vector velocity_shape(velocity_dofs_count);
mfem::Vector density_shape(density_dofs_count);
mfem::DenseMatrix gravity_gradient_shape(
gravity_gradient_dofs_count, dim
);
mfem::Vector gravity_gradient_element_value(dim);
mfem::Vector gravity_gradient_physical_value(dim);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
const mfem::IntegrationRule &integration_rule = *m_integration_rule;
double rho_val = 0.0;
for (int i = 0; i < dof_rho; ++i) {
rho_val += rho_dofs(i) * shape_rho(i);
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(q);
Tr.SetIntPoint(&integration_point);
const mapping::VolumeQuadratureContext context =
m_map.GetQuadratureContext(Tr, integration_point);
velocity_element->CalcShape(integration_point, velocity_shape);
density_element->CalcShape(integration_point, density_shape);
gravity_gradient_element->CalcVShape(Tr, gravity_gradient_shape);
gravity_gradient_shape.MultTranspose(
gravity_gradient_dofs, gravity_gradient_element_value
);
context.J_inv.MultTranspose(
gravity_gradient_element_value, gravity_gradient_physical_value
);
double density_value = 0.0;
for (int i = 0; i < density_dofs_count; ++i) {
density_value += density_dofs(i) * density_shape(i);
}
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
r_v(i + c * dof_v) += shape_v(i) * rho_val * grad_phi_phys(c) * weight;
for (int i = 0; i < velocity_dofs_count; ++i) {
for (int component = 0; component < dim; ++component) {
velocity_residual(i + component * velocity_dofs_count) +=
velocity_shape(i) * density_value *
gravity_gradient_physical_value(component) *
context.weight;
}
}
}
}
void GravityForceIntegrator::AssembleElementGrad(
void GravityMomentumIntegrator::AssembleElementGrad(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
if (utils::is_vacuum(Tr, elmats)) {
return;
}
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
MFEM_VERIFY(
m_integration_rule,
"GravityForceIntegrator must be configured with an "
"integration rule before assembly."
);
MFEM_VERIFY(
el.Size() > gravity_gradient_block,
"GravityForceIntegrator requires velocity, density, and "
"gravity-gradient finite elements."
);
MFEM_VERIFY(
elfun.Size() > gravity_gradient_block,
"GravityForceIntegrator requires velocity, density, and "
"gravity-gradient element states."
);
MFEM_VERIFY(
el[velocity_block] && el[density_block] &&
el[gravity_gradient_block],
"GravityForceIntegrator received a null finite element."
);
MFEM_VERIFY(
elfun[density_block] && elfun[gravity_gradient_block],
"GravityForceIntegrator received a null element state."
);
mfem::DenseMatrix* dv_dv = elmats(0, 0);
mfem::DenseMatrix* dv_drho = elmats(0, 1);
for (int row = 0; row < elmats.NumRows(); ++row) {
for (int column = 0; column < elmats.NumCols(); ++column) {
if (elmats(row, column)) {
*elmats(row, column) = 0.0;
}
}
}
if (dv_dv) *dv_dv = 0.0;
if (dv_drho) *dv_drho = 0.0;
if (m_jacobian_mode == GravityForceJacobianMode::exact) {
MFEM_ABORT(
"Exact GravityForceIntegrator geometry Jacobian is unavailable "
"until "
"DomainMapper linearization is "
"implemented."
);
}
if (!dv_drho) return;
const mfem::FiniteElement *velocity_element = el[velocity_block];
const mfem::FiniteElement *density_element = el[density_block];
const mfem::FiniteElement *gravity_gradient_element =
el[gravity_gradient_block];
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::Vector grad_phi_ref(dim), grad_phi_phys(dim), grad_phi_elem(dim);
const int velocity_dofs_count = velocity_element->GetDof();
const int density_dofs_count = density_element->GetDof();
const int gravity_gradient_dofs_count =
gravity_gradient_element->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::Vector &density_dofs = *elfun[density_block];
const mfem::Vector &gravity_gradient_dofs =
*elfun[gravity_gradient_block];
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
MFEM_VERIFY(
density_dofs.Size() == density_dofs_count,
"GravityForceIntegrator received an incorrectly sized density "
"state."
);
MFEM_VERIFY(
gravity_gradient_dofs.Size() == gravity_gradient_dofs_count,
"GravityForceIntegrator received an incorrectly sized "
"gravity-gradient "
"state."
);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
m_phi->GetGradient(Tr, grad_phi_elem);
mfem::DenseMatrix J_map(dim, dim), J_map_inv(dim, dim);
m_map.ComputeJacobian(Tr, J_map);
mfem::CalcInverse(J_map, J_map_inv);
J_map_inv.MultTranspose(grad_phi_elem, grad_phi_phys);
mfem::DenseMatrix *dv_drho = elmats(velocity_block, density_block);
mfem::DenseMatrix *dv_dgrad_phi =
m_jacobian_mode == GravityForceJacobianMode::field_coupled
? elmats(velocity_block, gravity_gradient_block)
: nullptr;
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
if (!dv_drho && !dv_dgrad_phi) {
return;
}
mfem::Vector velocity_shape(velocity_dofs_count);
mfem::Vector density_shape(density_dofs_count);
mfem::DenseMatrix gravity_gradient_shape(
gravity_gradient_dofs_count, dim
);
mfem::Vector gravity_gradient_element_value(dim);
mfem::Vector gravity_gradient_physical_value(dim);
mfem::Vector gravity_basis_element(dim);
mfem::Vector gravity_basis_physical(dim);
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
const int row = i + c * dof_v;
for (int j = 0; j < dof_rho; ++j) {
(*dv_drho)(row, j) += shape_v(i) * shape_rho(j) * grad_phi_phys(c) * weight;
const mfem::IntegrationRule &integration_rule = *m_integration_rule;
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(q);
Tr.SetIntPoint(&integration_point);
const mapping::VolumeQuadratureContext context =
m_map.GetQuadratureContext(Tr, integration_point);
velocity_element->CalcShape(integration_point, velocity_shape);
density_element->CalcShape(integration_point, density_shape);
gravity_gradient_element->CalcVShape(Tr, gravity_gradient_shape);
gravity_gradient_shape.MultTranspose(
gravity_gradient_dofs, gravity_gradient_element_value
);
context.J_inv.MultTranspose(
gravity_gradient_element_value, gravity_gradient_physical_value
);
double density_value = 0.0;
for (int i = 0; i < density_dofs_count; ++i) {
density_value += density_dofs(i) * density_shape(i);
}
if (dv_drho) {
for (int i = 0; i < velocity_dofs_count; ++i) {
for (int component = 0; component < dim; ++component) {
const int row = i + component * velocity_dofs_count;
for (int j = 0; j < density_dofs_count; ++j) {
(*dv_drho)(row, j) +=
velocity_shape(i) * density_shape(j) *
gravity_gradient_physical_value(component) *
context.weight;
}
}
}
}
if (dv_dgrad_phi) {
for (int j = 0; j < gravity_gradient_dofs_count; ++j) {
for (int component = 0; component < dim; ++component) {
gravity_basis_element(component) =
gravity_gradient_shape(j, component);
}
context.J_inv.MultTranspose(
gravity_basis_element, gravity_basis_physical
);
for (int i = 0; i < velocity_dofs_count; ++i) {
for (int component = 0; component < dim; ++component) {
const int row = i + component * velocity_dofs_count;
(*dv_dgrad_phi)(row, j) +=
velocity_shape(i) * density_value *
gravity_basis_physical(component) *
context.weight;
}
}
}
}
}
}
}
} // namespace mean_field::integrators

View File

@@ -4,7 +4,10 @@ module;
module mean_field;
namespace mean_field::integrators {
ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(const mapping::DomainMapper& map) : m_map(map) {};
ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(
const mapping::DomainMapper &map
)
: m_map(map) { };
void ContinuityVolumeIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -16,35 +19,37 @@ namespace mean_field::integrators {
return;
}
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector v_dofs = *elfun[0];
const mfem::Vector rho_dofs = *elfun[1];
const mfem::Vector v_dofs = *elfun[0];
const mfem::Vector rho_dofs = *elfun[1];
void* data_rho_before = elvec[1] ? (void*)elvec[1]->GetData() : nullptr;
void *data_rho_before =
elvec[1] ? (void *)elvec[1]->GetData() : nullptr;
int size_rho_before = elvec[1] ? elvec[1]->Size() : -1;
if (elvec[0]) {
elvec[0]->SetSize(dof_v * dim);
*elvec[0] = 0.0;
}
mfem::Vector& r_rho = *elvec[1];
mfem::Vector &r_rho = *elvec[1];
r_rho.SetSize(dof_rho);
r_rho = 0.0;
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), dshape_rho_phys(dof_rho, dim);
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim),
dshape_rho_phys(dof_rho, dim);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -54,7 +59,8 @@ namespace mean_field::integrators {
fe_rho->CalcDShape(ip, dshape_rho_ref);
mfem::Mult(dshape_rho_ref, J_inv, dshape_rho_phys);
mfem::Vector v_val(dim); v_val = 0.0;
mfem::Vector v_val(dim);
v_val = 0.0;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
const int row = i + c * dof_v;
@@ -83,32 +89,38 @@ namespace mean_field::integrators {
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector& v_dofs = *elfun[0];
const mfem::Vector& rho_dofs = *elfun[1];
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &rho_dofs = *elfun[1];
mfem::DenseMatrix* drho_dv = elmats(1, 0);
mfem::DenseMatrix* drho_drho = elmats(1, 1);
mfem::DenseMatrix *drho_dv = elmats(1, 0);
mfem::DenseMatrix *drho_drho = elmats(1, 1);
if (elmats(0, 0)) *elmats(0, 0) = 0.0;
if (elmats(0, 1)) *elmats(0, 1) = 0.0;
if (elmats(0, 0))
*elmats(0, 0) = 0.0;
if (elmats(0, 1))
*elmats(0, 1) = 0.0;
if (drho_dv) *drho_dv = 0.0;
if (drho_drho) *drho_drho = 0.0;
if (drho_dv)
*drho_dv = 0.0;
if (drho_drho)
*drho_drho = 0.0;
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), dshape_rho_phys(dof_rho, dim);
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim),
dshape_rho_phys(dof_rho, dim);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -118,7 +130,8 @@ namespace mean_field::integrators {
fe_rho->CalcDShape(ip, dshape_rho_ref);
mfem::Mult(dshape_rho_ref, J_inv, dshape_rho_phys);
mfem::Vector v_val(dim); v_val = 0.0;
mfem::Vector v_val(dim);
v_val = 0.0;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
const int row = i + c * dof_v;
@@ -136,7 +149,8 @@ namespace mean_field::integrators {
for (int j = 0; j < dof_v; ++j) {
for (int d = 0; d < dim; ++d) {
const int col = j + d * dof_v;
(*drho_dv)(i, col) -= dshape_rho_phys(i, d) * rho_val * shape_v(j) * weight;
(*drho_dv)(i, col) -= dshape_rho_phys(i, d) *
rho_val * shape_v(j) * weight;
}
}
}
@@ -149,15 +163,19 @@ namespace mean_field::integrators {
grad_psi_dot_v += dshape_rho_phys(i, c) * v_val(c);
}
for (int j = 0; j < dof_rho; ++j) {
(*drho_drho)(i, j) -= grad_psi_dot_v * shape_rho(j) * weight;
(*drho_drho)(i, j) -=
grad_psi_dot_v * shape_rho(j) * weight;
}
}
}
}
}
ContinuityFaceIntegrator::ContinuityFaceIntegrator(const mapping::DomainMapper& map): m_map(map) {}
ContinuityFaceIntegrator::ContinuityFaceIntegrator(
const mapping::DomainMapper &map
)
: m_map(map) {
}
void ContinuityFaceIntegrator::AssembleFaceVector(
const mfem::Array<const mfem::FiniteElement *> &el1,
@@ -166,19 +184,19 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvect
) {
const mfem::FiniteElement *fe_v_minus = el1[0];
const mfem::FiniteElement *fe_v_plus = el2[0];
const mfem::FiniteElement *fe_v_minus = el1[0];
const mfem::FiniteElement *fe_v_plus = el2[0];
const mfem::FiniteElement *fe_rho_minus = el1[1];
const mfem::FiniteElement *fe_rho_plus = el2[1];
const mfem::FiniteElement *fe_rho_plus = el2[1];
const int dof_v_minus = fe_v_minus->GetDof();
const int dof_v_plus = fe_v_plus->GetDof();
const int dof_v_minus = fe_v_minus->GetDof();
const int dof_v_plus = fe_v_plus->GetDof();
const int dof_rho_minus = fe_rho_minus->GetDof();
const int dof_rho_plus = fe_rho_plus->GetDof();
const int dof_rho_minus = fe_rho_minus->GetDof();
const int dof_rho_plus = fe_rho_plus->GetDof();
const int dim = Tr.GetSpaceDim();
const int dim = Tr.GetSpaceDim();
if (elvect[0]) {
elvect[0]->SetSize(dim * dof_v_minus + dim * dof_v_plus);
@@ -186,10 +204,10 @@ namespace mean_field::integrators {
}
mfem::Vector &r_rho = *elvect[1];
r_rho.SetSize(dof_rho_minus + dof_rho_plus);
r_rho = 0.0;
r_rho = 0.0;
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;
if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) {
@@ -200,30 +218,39 @@ namespace mean_field::integrators {
return; // Boundary face,
}
const mfem::Vector &v_dofs = *elfun[0]; // Size: dim * dof_v_minus + dim*dof_v_plus
const mfem::Vector &rho_dofs = *elfun[1]; // Size: dof_rho_minus + dof_rho_plus
const mfem::Vector &v_dofs =
*elfun[0]; // Size: dim * dof_v_minus + dim*dof_v_plus
const mfem::Vector &rho_dofs =
*elfun[1]; // Size: dof_rho_minus + dof_rho_plus
// Helpers to auto offset to the correct point in the dof array
auto rho_minus_dof = [&](const int i) {return rho_dofs(i);};
auto rho_plus_dof = [&](const int i) {return rho_dofs(i + dof_rho_minus);};
auto v_minus_dof = [&](const int k, const int c) {return v_dofs(k + c * dof_v_minus);};
auto rho_minus_dof = [&](const int i) { return rho_dofs(i); };
auto rho_plus_dof = [&](const int i) {
return rho_dofs(i + dof_rho_minus);
};
auto v_minus_dof = [&](const int k, const int c) {
return v_dofs(k + c * dof_v_minus);
};
const int p_v = fe_v_minus->GetOrder();
const int p_rho = fe_rho_minus->GetOrder();
const int p_v = fe_v_minus->GetOrder();
const int p_rho = fe_rho_minus->GetOrder();
const int int_order = 2 * std::max(p_v, p_rho) + 1;
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), shape_rho_plus(dof_rho_plus);
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus),
shape_rho_plus(dof_rho_plus);
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& face_ip = ir->IntPoint(q);
const mfem::IntegrationPoint &face_ip = ir->IntPoint(q);
Tr.SetAllIntPoints(&face_ip);
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_map.GetFaceQuadratureContext(Tr, face_ip);
fe_v_minus->CalcShape(ip_minus, shape_v_minus);
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
@@ -251,12 +278,14 @@ namespace mean_field::integrators {
}
// Upwind density
// I use the convention that the flow is positive when moving from minus to plus
// I use the convention that the flow is positive when moving from
// minus to plus
const double rho_up = (u_n >= 0) ? rho_minus_val : rho_plus_val;
const double flux_weighted = u_n * rho_up * ds;
// Note the normals need to be in opposite directions for these two fluxes
// Note the normals need to be in opposite directions for these two
// fluxes
for (int i = 0; i < dof_rho_minus; ++i) {
r_rho(i) += shape_rho_minus(i) * flux_weighted;
}
@@ -274,25 +303,26 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
const mfem::FiniteElement *fe_v_minus = el1[0];
const mfem::FiniteElement *fe_v_plus = el2[0];
const mfem::FiniteElement *fe_v_minus = el1[0];
const mfem::FiniteElement *fe_v_plus = el2[0];
const mfem::FiniteElement *fe_rho_minus = el1[1];
const mfem::FiniteElement *fe_rho_plus = el2[1];
const mfem::FiniteElement *fe_rho_plus = el2[1];
const int dof_v_minus = fe_v_minus->GetDof();
const int dof_v_plus = fe_v_plus->GetDof();
const int dof_rho_minus = fe_rho_minus->GetDof();
const int dof_rho_plus = fe_rho_plus->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v_minus = fe_v_minus->GetDof();
const int dof_v_plus = fe_v_plus->GetDof();
const int dof_rho_minus = fe_rho_minus->GetDof();
const int dof_rho_plus = fe_rho_plus->GetDof();
const int dim = Tr.GetSpaceDim();
const int N_v_total = dim * (dof_v_minus + dof_v_plus);
const int N_rho_total = dof_rho_minus + dof_rho_plus;
const int N_v_total = dim * (dof_v_minus + dof_v_plus);
const int N_rho_total = dof_rho_minus + dof_rho_plus;
auto size_and_zero_mat = [&](mfem::DenseMatrix* mat, const int r_size, const int c_size) {
if (mat) {
mat->SetSize(r_size, c_size);
*mat = 0.0;
}
auto size_and_zero_mat = [&](mfem::DenseMatrix *mat, const int r_size,
const int c_size) {
if (mat) {
mat->SetSize(r_size, c_size);
*mat = 0.0;
}
};
size_and_zero_mat(elmats(0, 0), N_v_total, N_v_total);
@@ -300,34 +330,41 @@ namespace mean_field::integrators {
size_and_zero_mat(elmats(1, 0), N_rho_total, N_v_total);
size_and_zero_mat(elmats(1, 1), N_rho_total, N_rho_total);
if (skip_face(Tr)) return;
if (skip_face(Tr))
return;
mfem::DenseMatrix *drho_dv = elmats(1, 0);
mfem::DenseMatrix *drho_dv = elmats(1, 0);
mfem::DenseMatrix *drho_drho = elmats(1, 1);
if (!drho_dv && !drho_drho) return;
if (!drho_dv && !drho_drho)
return;
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &v_dofs = *elfun[0];
const mfem::Vector &rho_dofs = *elfun[1];
const int int_order = 2 * std::max(fe_v_minus->GetOrder(), fe_rho_minus->GetOrder()) + 1;
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
const int int_order =
2 * std::max(fe_v_minus->GetOrder(), fe_rho_minus->GetOrder()) + 1;
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), shape_rho_plus(dof_rho_plus);
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus),
shape_rho_plus(dof_rho_plus);
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& face_ip = ir->IntPoint(q);
const mfem::IntegrationPoint &face_ip = ir->IntPoint(q);
Tr.SetAllIntPoints(&face_ip);
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_map.GetFaceQuadratureContext(Tr, face_ip);
fe_v_minus->CalcShape(ip_minus, shape_v_minus);
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
fe_rho_plus->CalcShape(ip_plus, shape_rho_plus);
const double u_n = compute_u_n(v_dofs, shape_v_minus, n_unit, dof_v_minus, dim);
const double u_n =
compute_u_n(v_dofs, shape_v_minus, n_unit, dof_v_minus, dim);
double rho_minus_val = 0.0;
for (int i = 0; i < dof_rho_minus; ++i) {
@@ -353,18 +390,21 @@ namespace mean_field::integrators {
(*drho_drho)(i, ip) += shape_rho_minus(i) * col_w;
}
for (int j = 0; j < dof_rho_plus; ++j) {
(*drho_drho)(dof_rho_minus + j, ip) -= shape_rho_plus(j) * col_w;
(*drho_drho)(dof_rho_minus + j, ip) -=
shape_rho_plus(j) * col_w;
}
}
} else {
for (int jp = 0; jp < dof_rho_plus; ++jp) {
const double col_w = u_w * shape_rho_plus(jp);
const int col_idx = dof_rho_minus + jp;
const int col_idx = dof_rho_minus + jp;
for (int i = 0; i < dof_rho_minus; ++i) {
(*drho_drho)(i, col_idx) += shape_rho_minus(i) * col_w;
(*drho_drho)(i, col_idx) +=
shape_rho_minus(i) * col_w;
}
for (int j = 0; j < dof_rho_plus; ++j) {
(*drho_drho)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w;
(*drho_drho)(dof_rho_minus + j, col_idx) -=
shape_rho_plus(j) * col_w;
}
}
}
@@ -375,13 +415,15 @@ namespace mean_field::integrators {
for (int c = 0; c < dim; ++c) {
const double n_c_rho_w = n_unit(c) * rho_w;
for (int k = 0; k < dof_v_minus; ++k) {
const int col_idx = k + c * dof_v_minus;
const int col_idx = k + c * dof_v_minus;
const double col_w = n_c_rho_w * shape_v_minus(k);
for (int i = 0; i < dof_rho_minus; ++i) {
(*drho_dv)(i, col_idx) += shape_rho_minus(i) * col_w;
(*drho_dv)(i, col_idx) +=
shape_rho_minus(i) * col_w;
}
for (int j = 0; j < dof_rho_plus; ++j) {
(*drho_dv)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w;
(*drho_dv)(dof_rho_minus + j, col_idx) -=
shape_rho_plus(j) * col_w;
}
}
}
@@ -389,9 +431,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;
if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) {
return true; // No flux contribution for vacuum faces
@@ -402,7 +446,13 @@ namespace mean_field::integrators {
return false;
}
double ContinuityFaceIntegrator::compute_u_n(const mfem::Vector& v_dofs, const mfem::Vector& shape_v_minus, const mfem::Vector& n_unit, int dof_v_minus, int dim) {
double ContinuityFaceIntegrator::compute_u_n(
const mfem::Vector &v_dofs,
const mfem::Vector &shape_v_minus,
const mfem::Vector &n_unit,
int dof_v_minus,
int dim
) {
double u_n = 0.0;
for (int c = 0; c < dim; ++c) {
double v_c = 0.0;
@@ -413,5 +463,4 @@ namespace mean_field::integrators {
}
return u_n;
}
}
} // namespace mean_field::integrators

View File

@@ -4,12 +4,18 @@ module mean_field;
namespace mean_field::integrators {
ViscosityIntegrator::ViscosityIntegrator(
const mapping::DomainMapper& map,
const mapping::DomainMapper &map,
const double mu,
const int quad_boost
) : m_map(map), m_mu(mu), m_quad_boost(quad_boost) {}
)
: m_map(map),
m_mu(mu),
m_quad_boost(quad_boost) {
}
void ViscosityIntegrator::SetMu(const double mu) { m_mu = mu; }
void ViscosityIntegrator::SetMu(const double mu) {
m_mu = mu;
}
void ViscosityIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -21,19 +27,19 @@ namespace mean_field::integrators {
return;
}
void* data_before = (void*)elvec[0]->GetData();
int size_before = elvec[0]->Size();
void *data_before = (void *)elvec[0]->GetData();
int size_before = elvec[0]->Size();
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector& v_dofs = *elfun[0];
const mfem::Vector &v_dofs = *elfun[0];
mfem::Vector& r_v = *elvec[0];
mfem::Vector &r_v = *elvec[0];
r_v.SetSize(dof_v * dim);
r_v = 0.0;
if (elvec[1]) {
@@ -43,12 +49,13 @@ namespace mean_field::integrators {
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + m_quad_boost);
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(
fe_v->GetGeomType(), 2 * fe_v->GetOrder() + m_quad_boost
);
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -57,12 +64,13 @@ namespace mean_field::integrators {
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
// ∇v(c,j) = δj v_c
mfem::DenseMatrix grad_v(dim, dim); grad_v = 0.0;
mfem::DenseMatrix grad_v(dim, dim);
grad_v = 0.0;
for (int n = 0; n < dof_v; ++n) {
for (int c = 0; c < dim; ++c) {
const double vn_c = v_dofs(n + c * dof_v);
for (int j = 0; j < dim; ++j) {
grad_v( c, j) += vn_c * dshape_v_phys(n, j);
grad_v(c, j) += vn_c * dshape_v_phys(n, j);
}
}
}
@@ -80,7 +88,8 @@ namespace mean_field::integrators {
double acc = 0.0;
for (int j = 0; j < dim; ++j) {
double D_cj = grad_v(c, j) + grad_v(j, c);
if (c == j) D_cj -= (2.0 / 3.0) * div_v;
if (c == j)
D_cj -= (2.0 / 3.0) * div_v;
acc += dshape_v_phys(i, j) * D_cj;
}
r_v(i + c * dof_v) += mu_w * acc;
@@ -95,27 +104,33 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
mfem::DenseMatrix* dv_dv = elmats(0, 0);
mfem::DenseMatrix* dv_drho = elmats(0, 1);
mfem::DenseMatrix *dv_dv = elmats(0, 0);
mfem::DenseMatrix *dv_drho = elmats(0, 1);
if (dv_drho) *dv_drho =0.0;
if (dv_dv) *dv_dv = 0.0;
if (elmats(1, 0)) *elmats(1, 0) = 0.0;
if (elmats(1, 1)) *elmats(1, 1) = 0.0;
if (!dv_dv) return;
if (dv_drho)
*dv_drho = 0.0;
if (dv_dv)
*dv_dv = 0.0;
if (elmats(1, 0))
*elmats(1, 0) = 0.0;
if (elmats(1, 1))
*elmats(1, 1) = 0.0;
if (!dv_dv)
return;
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -137,20 +152,20 @@ namespace mean_field::integrators {
for (int d = 0; d < dim; ++d) {
const int col = n + d * dof_v;
double val = 0.0;
if (c == d) val += dot_grad;
double val = 0.0;
if (c == d)
val += dot_grad;
val += dshape_v_phys(i, d) * dshape_v_phys(n, c);
val -= (2.0 / 3.0) * dshape_v_phys(i, c) * dshape_v_phys(n, d);
val -= (2.0 / 3.0) * dshape_v_phys(i, c) *
dshape_v_phys(n, d);
(*dv_dv)(row, col) += mu_w * val;
}
}
}
}
}
}
}
} // namespace mean_field::integrators

View File

@@ -10,30 +10,38 @@ namespace mean_field::mapping {
//////////////////////////////
MappedScalarCoefficient::MappedScalarCoefficient(
const DomainMapper &map,
mfem::Coefficient &coeff,
Coefficient &coeff,
const COORDINATE_SPACE coord_space
) : m_map(map),
m_coeff(coeff),
m_coord_space(coord_space) {};
)
: m_map(map),
m_coeff(coeff),
m_coord_space(coord_space) { };
double MappedScalarCoefficient::Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
double MappedScalarCoefficient::Eval(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) {
T.SetIntPoint(&ip);
double f_val = 0.0;
switch (m_coord_space) {
case COORDINATE_SPACE::PHYSICAL: {
f_val = eval_at_point(m_coeff, T, ip);
const double detJ = m_map.ComputeDetJ(T, ip);
return f_val * fabs(detJ);
}
case COORDINATE_SPACE::REFERENCE: {
f_val = m_coeff.Eval(T, ip);
return f_val;
}
case COORDINATE_SPACE::PHYSICAL: {
f_val = eval_at_point(m_coeff, T, ip);
const double detJ = m_map.ComputeDetJ(T, ip);
return f_val * fabs(detJ);
}
case COORDINATE_SPACE::REFERENCE: {
f_val = m_coeff.Eval(T, ip);
return f_val;
}
}
}
double MappedScalarCoefficient::eval_at_point(mfem::Coefficient &c, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
double MappedScalarCoefficient::eval_at_point(
Coefficient &c,
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) {
return c.Eval(T, ip);
}
@@ -45,22 +53,26 @@ namespace mean_field::mapping {
const DomainMapper &map,
mfem::Coefficient &sigma,
const int dim
) : mfem::MatrixCoefficient(dim),
m_map(map),
m_scalar(&sigma),
m_tensor(nullptr) {
};
)
: MatrixCoefficient(dim),
m_map(map),
m_scalar(&sigma),
m_tensor(nullptr) { };
MappedDiffusionCoefficient::MappedDiffusionCoefficient(
const DomainMapper &map,
mfem::MatrixCoefficient &sigma
) : mfem::MatrixCoefficient(sigma.GetHeight()),
m_map(map),
m_scalar(nullptr),
m_tensor(&sigma) {
};
MatrixCoefficient &sigma
)
: MatrixCoefficient(sigma.GetHeight()),
m_map(map),
m_scalar(nullptr),
m_tensor(&sigma) { };
void MappedDiffusionCoefficient::Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
void MappedDiffusionCoefficient::Eval(
mfem::DenseMatrix &K,
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) {
const int dim = height;
T.SetIntPoint(&ip);
@@ -90,13 +102,17 @@ namespace mean_field::mapping {
///////////////////////////////
MappedVectorCoefficient::MappedVectorCoefficient(
const DomainMapper &map,
mfem::VectorCoefficient &coeff
) : mfem::VectorCoefficient(coeff.GetVDim()),
m_map(map),
m_coeff(coeff) {
};
VectorCoefficient &coeff
)
: VectorCoefficient(coeff.GetVDim()),
m_map(map),
m_coeff(coeff) { };
void MappedVectorCoefficient::Eval(mfem::Vector &V, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
void MappedVectorCoefficient::Eval(
mfem::Vector &V,
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) {
const int dim = vdim;
T.SetIntPoint(&ip);
@@ -118,24 +134,32 @@ namespace mean_field::mapping {
PhysicalPositionFunctionCoefficient::PhysicalPositionFunctionCoefficient(
const DomainMapper &map,
Func f // std::function<double(const mfem::Vector&)>
) : m_f(std::move(f)),
m_map(map) {};
)
: m_f(std::move(f)),
m_map(map) { };
double PhysicalPositionFunctionCoefficient::Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
double PhysicalPositionFunctionCoefficient::Eval(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) {
T.SetIntPoint(&ip);
mfem::Vector x;
m_map.GetPhysicalPoint(T, ip, x);
return m_f(x);
}
MappedHDivMassCoefficient::MappedHDivMassCoefficient(const DomainMapper& map, const int dim)
: mfem::MatrixCoefficient(dim),
m_map(map) {}
MappedHDivMassCoefficient::MappedHDivMassCoefficient(
const DomainMapper &map,
const int dim
)
: MatrixCoefficient(dim),
m_map(map) {
}
void MappedHDivMassCoefficient::Eval(
mfem::DenseMatrix& matrix,
mfem::ElementTransformation& transformation,
const mfem::IntegrationPoint& integration_point
mfem::DenseMatrix &matrix,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point
) {
transformation.SetIntPoint(&integration_point);
@@ -144,9 +168,12 @@ namespace mean_field::mapping {
const double map_determinant = map_jacobian.Det();
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."
);
mfem::MultAtB(map_jacobian, map_jacobian, matrix);
matrix *= 1.0 / std::abs(map_determinant);
}
}
} // namespace mean_field::mapping

View File

@@ -0,0 +1,266 @@
module;
#include <cmath>
#include <mfem.hpp>
#include <stdexcept>
module mean_field;
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::compactification {
KelvinCompactification::KelvinCompactification(
options::KelvinCompactificationOptions options
)
: m_options(options) {
if (!std::isfinite(m_options.r_star_ref) ||
!std::isfinite(m_options.r_inf_ref)) {
throw std::invalid_argument(
"Kelvin compactification radii must be finite."
);
}
if (m_options.r_star_ref <= 0.0 ||
m_options.r_inf_ref <= m_options.r_star_ref) {
throw std::invalid_argument(
"Kelvin compactification requires 0 < r_star_ref < r_inf_ref."
);
}
if (!std::isfinite(m_options.coordinate_tolerance) ||
m_options.coordinate_tolerance < 0.0 ||
m_options.coordinate_tolerance >= 1.0) {
throw std::invalid_argument(
"Kelvin compactification coordinate tolerance must be finite "
"and lie "
"in [0, 1)."
);
}
}
MappingStatus KelvinCompactification::ComputeRadialFactors(
const double compactification_coordinate,
RadialFactors &factors
) const {
if (!std::isfinite(compactification_coordinate))
return MappingStatus::non_finite_input;
const double tolerance = m_options.coordinate_tolerance;
if (compactification_coordinate < -tolerance ||
compactification_coordinate > 1.0 + tolerance) {
return MappingStatus::outside_reference_domain;
}
double coordinate = compactification_coordinate;
if (coordinate < 0.0)
coordinate = 0.0;
if (coordinate >= 1.0 - tolerance) {
return MappingStatus::at_compactified_infinity;
}
const double radial_extent = m_options.r_inf_ref - m_options.r_star_ref;
const double computational_radius =
m_options.r_star_ref + coordinate * radial_extent;
if (!std::isfinite(computational_radius) ||
computational_radius <= 0.0) {
return MappingStatus::invalid_reference_radius;
}
const double one_minus_coordinate = 1.0 - coordinate;
const double denominator = computational_radius * one_minus_coordinate;
if (!std::isfinite(denominator) || denominator <= 0.0) {
return MappingStatus::non_finite_result;
}
const double scale = m_options.r_star_ref / denominator;
const double scale_derivative =
scale *
(1.0 / one_minus_coordinate - radial_extent / computational_radius);
if (!std::isfinite(scale) || !std::isfinite(scale_derivative)) {
return MappingStatus::non_finite_result;
}
factors.coordinate = coordinate;
factors.computational_radius = computational_radius;
factors.scale = scale;
factors.scale_derivative = scale_derivative;
return MappingStatus::valid;
}
MappingStatus KelvinCompactification::Evaluate(
const ExteriorMapInput &input,
ExteriorMapResult &result
) const {
const int dimension = input.reference_position.Size();
if (dimension <= 0 || input.displaced_position.Size() != dimension ||
input.compactification_coordinate_gradient.Size() != dimension) {
return MappingStatus::invalid_dimension;
}
if (input.displacement_jacobian.Height() != dimension ||
input.displacement_jacobian.Width() != dimension) {
return MappingStatus::invalid_dimension;
}
if (!vector_is_finite(input.reference_position) ||
!vector_is_finite(input.displaced_position) ||
!vector_is_finite(input.compactification_coordinate_gradient) ||
!matrix_is_finite(input.displacement_jacobian)) {
return MappingStatus::non_finite_input;
}
RadialFactors factors;
const MappingStatus factor_status =
ComputeRadialFactors(input.compactification_coordinate, factors);
if (factor_status != MappingStatus::valid)
return factor_status;
result.physical_position.SetSize(dimension);
result.mapping_jacobian.SetSize(dimension, dimension);
for (int i = 0; i < dimension; ++i) {
result.physical_position(i) =
factors.scale * input.displaced_position(i);
for (int j = 0; j < dimension; ++j) {
const double scale_gradient =
factors.scale_derivative *
input.compactification_coordinate_gradient(j);
result.mapping_jacobian(i, j) =
factors.scale * input.displacement_jacobian(i, j) +
input.displaced_position(i) * scale_gradient;
}
}
if (!vector_is_finite(result.physical_position) ||
!matrix_is_finite(result.mapping_jacobian)) {
return MappingStatus::non_finite_result;
}
const double mapping_determinant = result.mapping_jacobian.Det();
if (!std::isfinite(mapping_determinant))
return MappingStatus::non_finite_result;
if (mapping_determinant <= 0.0)
return MappingStatus::non_positive_determinant;
return MappingStatus::valid;
}
MappingStatus KelvinCompactification::EvaluateVariation(
const ExteriorMapInput &input,
const ExteriorMapResult &result,
const ExteriorMapDirection &direction,
ExteriorMapVariation &variation
) const {
const int dimension = input.reference_position.Size();
if (dimension <= 0 || input.displaced_position.Size() != dimension ||
input.compactification_coordinate_gradient.Size() != dimension) {
return MappingStatus::invalid_dimension;
}
if (input.displacement_jacobian.Height() != dimension ||
input.displacement_jacobian.Width() != dimension) {
return MappingStatus::invalid_dimension;
}
if (result.physical_position.Size() != dimension ||
result.mapping_jacobian.Height() != dimension ||
result.mapping_jacobian.Width() != dimension) {
return MappingStatus::invalid_dimension;
}
if (direction.displaced_position_variation.Size() != dimension ||
direction.displacement_jacobian_variation.Height() != dimension ||
direction.displacement_jacobian_variation.Width() != dimension) {
return MappingStatus::invalid_dimension;
}
if (!vector_is_finite(input.reference_position) ||
!vector_is_finite(input.displaced_position) ||
!vector_is_finite(input.compactification_coordinate_gradient) ||
!matrix_is_finite(input.displacement_jacobian)) {
return MappingStatus::non_finite_input;
}
if (!vector_is_finite(result.physical_position) ||
!matrix_is_finite(result.mapping_jacobian) ||
!vector_is_finite(direction.displaced_position_variation) ||
!matrix_is_finite(direction.displacement_jacobian_variation)) {
return MappingStatus::non_finite_input;
}
RadialFactors factors;
const MappingStatus factor_status =
ComputeRadialFactors(input.compactification_coordinate, factors);
if (factor_status != MappingStatus::valid)
return factor_status;
variation.physical_position_variation.SetSize(dimension);
variation.mapping_jacobian_variation.SetSize(dimension, dimension);
for (int i = 0; i < dimension; ++i) {
variation.physical_position_variation(i) =
factors.scale * direction.displaced_position_variation(i);
for (int j = 0; j < dimension; ++j) {
const double scale_gradient =
factors.scale_derivative *
input.compactification_coordinate_gradient(j);
variation.mapping_jacobian_variation(i, j) =
factors.scale *
direction.displacement_jacobian_variation(i, j) +
direction.displaced_position_variation(i) * scale_gradient;
}
}
if (!vector_is_finite(variation.physical_position_variation) ||
!matrix_is_finite(variation.mapping_jacobian_variation)) {
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
std::string_view KelvinCompactification::GetName() const noexcept {
return "KelvinCompactification";
}
double KelvinCompactification::GetReferenceStellarRadius() const noexcept {
return m_options.r_star_ref;
}
double KelvinCompactification::GetReferenceInfinityRadius() const noexcept {
return m_options.r_inf_ref;
}
double KelvinCompactification::GetCoordinateTolerance() const noexcept {
return m_options.coordinate_tolerance;
}
} // namespace mean_field::mapping::compactification

View File

@@ -2,26 +2,52 @@ module;
#include <mfem.hpp>
module mean_field;
import :mapping.types;
namespace {
double get_positive_map_jacobian(
const mean_field::mapping::DomainMapper &domain_mapper,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
mfem::DenseMatrix &map_jacobian
) {
transformation.SetIntPoint(&integration_point);
domain_mapper.ComputeJacobian(transformation, map_jacobian);
const double map_determinant = map_jacobian.Det();
MFEM_VERIFY(
map_determinant > 0.0,
"Domain mapping has a non-positive Jacobian determinant."
);
return map_determinant;
}
} // namespace
namespace mean_field::mapping {
DomainMapper::DomainMapper(
const double r_star_ref,
const double r_inf_ref
) : m_d(nullptr),
m_r_star_ref(r_star_ref),
m_r_inf_ref(r_inf_ref) {
)
: m_d(nullptr),
m_r_star_ref(r_star_ref),
m_r_inf_ref(r_inf_ref) {
InitAllScratchSpaces();
CalcIsIdentity() ? m_displacement_is_identity = true
: m_displacement_is_identity = false;
}
DomainMapper::DomainMapper(
const mfem::GridFunction &d,
const double r_star_ref,
const double r_inf_ref
) : m_d(&d),
m_dim(d.FESpace()->GetMesh()->Dimension()),
m_r_star_ref(r_star_ref),
m_r_inf_ref(r_inf_ref) {
)
: m_d(&d),
m_dim(d.FESpace()->GetMesh()->Dimension()),
m_r_star_ref(r_star_ref),
m_r_inf_ref(r_inf_ref) {
InitAllScratchSpaces();
CalcIsIdentity() ? m_displacement_is_identity = true
: m_displacement_is_identity = false;
}
bool DomainMapper::is_vacuum(const mfem::ElementTransformation &T) const {
@@ -29,7 +55,9 @@ namespace mean_field::mapping {
return T.Attribute == m_vacuum_attr;
} else if (T.ElementType == mfem::ElementTransformation::BDR_ELEMENT) {
return T.Attribute == m_vacuum_attr - 1;
// TODO: In a more robust code this should really be read from the stroid API to ensure that the vacuum boundary is really 1 - the vacuum material attribute
// TODO: In a more robust code this should really be read from the
// stroid API to ensure that the vacuum boundary is really 1 - the
// vacuum material attribute
}
return false;
}
@@ -37,28 +65,69 @@ namespace mean_field::mapping {
void DomainMapper::SetDisplacement(const mfem::GridFunction &d) {
if (m_dim != d.FESpace()->GetMesh()->Dimension()) {
const std::string err_msg = std::format(
"Dimension mismatch: DomainMapper is initialized for dimension {}, but provided displacement field has dimension {}.",
m_dim, d.FESpace()->GetMesh()->Dimension());
"Dimension mismatch: DomainMapper is initialized for dimension "
"{}, "
"but provided displacement field has "
"dimension {}.",
m_dim, d.FESpace()->GetMesh()->Dimension()
);
throw std::invalid_argument(err_msg);
}
m_d = &d;
InvalidateCache();
CalcIsIdentity() ? m_displacement_is_identity = true
: m_displacement_is_identity = false;
}
bool DomainMapper::IsIdentity() const {
return (m_d == nullptr);
bool DomainMapper::HasCompactification() const noexcept {
return std::isfinite(m_r_star_ref) && std::isfinite(m_r_inf_ref) &&
m_r_star_ref > 0.0 && m_r_inf_ref > m_r_star_ref &&
m_xi_clamp > 0.0 && m_xi_clamp < 1.0;
}
bool DomainMapper::HasDisplacementField() const noexcept {
return m_d != nullptr;
}
bool DomainMapper::CalcIsIdentity() const {
if (m_d == nullptr) {
return true;
}
const int local_identity = m_d->Normlinf() == 0.0 ? 1 : 0;
const auto *parallel_displacement =
dynamic_cast<const mfem::ParGridFunction *>(m_d);
if (parallel_displacement == nullptr) {
return local_identity == 1;
}
int global_identity = 0;
MPI_Allreduce(
&local_identity, &global_identity, 1, MPI_INT, MPI_MIN,
parallel_displacement->ParFESpace()->GetComm()
);
return global_identity == 1;
}
void DomainMapper::ResetDisplacement() {
m_d = nullptr;
InvalidateCache();
CalcIsIdentity() ? m_displacement_is_identity = true
: m_displacement_is_identity = false;
}
void DomainMapper::ComputeJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &J) const {
void DomainMapper::ComputeJacobian(
mfem::ElementTransformation &T,
mfem::DenseMatrix &J
) const {
J.SetSize(m_dim, m_dim);
J = 0.0;
J = 0.0;
m_J_D = 0.0;
if (IsIdentity()) {
if (!HasDisplacementField()) {
for (int i = 0; i < m_dim; ++i) {
m_J_D(i, i) = 1.0; // Identity mapping
}
@@ -76,7 +145,7 @@ namespace mean_field::mapping {
if (is_vacuum(T)) {
T.Transform(T.GetIntPoint(), m_x_ref);
if (IsIdentity()) {
if (!HasDisplacementField()) {
m_x_disp = m_x_ref;
} else {
m_shape.SetSize(m_fe->GetDof());
@@ -91,15 +160,22 @@ namespace mean_field::mapping {
}
}
double DomainMapper::ComputeDetJ(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const {
if (IsIdentity() && !is_vacuum(T)) return 1.0; // If no mapping, the determinant of the Jacobian is 1
double DomainMapper::ComputeDetJ(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) const {
if (!HasDisplacementField() && !is_vacuum(T))
return 1.0; // If no mapping, the determinant of the Jacobian is 1
T.SetIntPoint(&ip);
mfem::DenseMatrix J;
ComputeJacobian(T, J);
return J.Det();
}
void DomainMapper::ComputeMappedDiffusionTensor(mfem::ElementTransformation &T, mfem::DenseMatrix &D) const {
void DomainMapper::ComputeMappedDiffusionTensor(
mfem::ElementTransformation &T,
mfem::DenseMatrix &D
) const {
ComputeJacobian(T, m_J_temp);
const double detJ = m_J_temp.Det();
mfem::CalcInverse(m_J_temp, m_JInv_temp);
@@ -108,41 +184,56 @@ namespace mean_field::mapping {
D *= fabs(detJ);
}
void DomainMapper::ComputeInverseJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &JInv) const {
void DomainMapper::ComputeInverseJacobian(
mfem::ElementTransformation &T,
mfem::DenseMatrix &JInv
) const {
ComputeJacobian(T, m_J_temp);
JInv.SetSize(m_dim, m_dim);
mfem::CalcInverse(m_J_temp, JInv);
}
DomainMapper::VolumeQuadratureContext DomainMapper::GetQuadratureContext(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const {
VolumeQuadratureContext DomainMapper::GetQuadratureContext(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) const {
const int dim = T.GetSpaceDim();
mfem::DenseMatrix J_map(dim, dim), J_inv(dim, dim);
ComputeJacobian(T, J_map);
mfem::DenseMatrix J_full(dim, dim);
mfem::Mult(J_map, T.Jacobian(), J_full);
mfem::CalcInverse(J_full, J_inv);
const double detJ = std::fabs(ComputeDetJ(T, ip));
const double detJ = std::fabs(ComputeDetJ(T, ip));
const double weight = ip.weight * T.Weight() * detJ;
return {.J_inv = J_inv, .detJ = detJ, .weight = weight};
}
DomainMapper::FaceQuadratureContext DomainMapper::GetFaceQuadratureContext(mfem::FaceElementTransformations &T, const mfem::IntegrationPoint &ip) const {
FaceQuadratureContext DomainMapper::GetFaceQuadratureContext(
mfem::FaceElementTransformations &T,
const mfem::IntegrationPoint &ip
) const {
const int dim = T.GetSpaceDim();
T.SetAllIntPoints(&ip);
mfem::Vector n_raw(dim);
mfem::CalcOrtho(T.Jacobian(), n_raw);
if (IsIdentity()) {
if (!HasDisplacementField() && !is_vacuum(T)) {
const double n_raw_mag = n_raw.Norml2();
mfem::Vector n_unit(dim);
n_unit = n_raw;
n_unit /= n_raw_mag;
return FaceQuadratureContext{.normal=n_unit, .ds=ip.weight * n_raw_mag, .v_dot_n_scale = 1.0};
return FaceQuadratureContext{
.normal = n_unit,
.ds = ip.weight * n_raw_mag,
.v_dot_n_scale = 1.0
};
}
// Nanson's Formula (https://en.wikiversity.org/wiki/Continuum_mechanics/Volume_change_and_area_change)
// Since the displacement field lives in H1 it should be irrelevant if we pick Elem1 or Elem2
// Nanson's Formula
// (https://en.wikiversity.org/wiki/Continuum_mechanics/Volume_change_and_area_change)
// Since the displacement field lives in H1 it should be irrelevant if
// we pick Elem1 or Elem2
mfem::DenseMatrix J_map(dim, dim);
ComputeJacobian(*T.Elem1, J_map);
const double detJ_map = J_map.Det();
@@ -162,18 +253,21 @@ namespace mean_field::mapping {
const double n_raw_mag = n_raw.Norml2();
return FaceQuadratureContext{
.normal = n_unit,
.ds = ip.weight * n_raw_mag,
.normal = n_unit,
.ds = ip.weight * n_raw_mag,
.v_dot_n_scale = n_phys_mag / n_raw_mag
};
}
void DomainMapper::GetPhysicalPoint(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip, mfem::Vector &x_phys) const {
void DomainMapper::GetPhysicalPoint(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip,
mfem::Vector &x_phys
) const {
x_phys.SetSize(m_dim);
T.Transform(ip, m_x_ref);
if (IsIdentity()) {
if (!HasDisplacementField()) {
x_phys = m_x_ref;
} else {
UpdateElementCache(T);
@@ -189,11 +283,91 @@ namespace mean_field::mapping {
}
}
void DomainMapper::GetVectorValue(const int i, const mfem::IntegrationPoint &ip, mfem::Vector &val) const {
void DomainMapper::GetVectorValue(
const int i,
const mfem::IntegrationPoint &ip,
mfem::Vector &val
) const {
m_d->GetVectorValue(i, ip, val);
}
const mfem::GridFunction *DomainMapper::GetDisplacement() const { return m_d; }
void DomainMapper::MapHDivFluxToPhysical(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const mfem::Vector &reference_flux,
mfem::Vector &physical_flux
) const {
MFEM_VERIFY(
reference_flux.Size() == m_dim,
"The reference H(div) flux has the wrong dimension."
);
mfem::DenseMatrix map_jacobian(m_dim, m_dim);
const double map_determinant = get_positive_map_jacobian(
*this, transformation, integration_point, map_jacobian
);
mfem::Vector mapped_flux(m_dim);
map_jacobian.Mult(reference_flux, mapped_flux);
mapped_flux /= map_determinant;
physical_flux = mapped_flux;
}
void DomainMapper::MapPhysicalFluxToHDivReference(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const mfem::Vector &physical_flux,
mfem::Vector &reference_flux
) const {
MFEM_VERIFY(
physical_flux.Size() == m_dim,
"The physical flux has the wrong dimension."
);
mfem::DenseMatrix map_jacobian(m_dim, m_dim);
const double map_determinant = get_positive_map_jacobian(
*this, transformation, integration_point, map_jacobian
);
mfem::DenseMatrix inverse_map_jacobian(m_dim, m_dim);
mfem::CalcInverse(map_jacobian, inverse_map_jacobian);
mfem::Vector mapped_flux(m_dim);
inverse_map_jacobian.Mult(physical_flux, mapped_flux);
mapped_flux *= map_determinant;
reference_flux = mapped_flux;
}
void DomainMapper::MapReferenceGradientToPhysical(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const mfem::Vector &reference_gradient,
mfem::Vector &physical_gradient
) const {
MFEM_VERIFY(
reference_gradient.Size() == m_dim,
"The reference gradient has the wrong dimension."
);
mfem::DenseMatrix map_jacobian(m_dim, m_dim);
get_positive_map_jacobian(
*this, transformation, integration_point, map_jacobian
);
mfem::DenseMatrix inverse_map_jacobian(m_dim, m_dim);
mfem::CalcInverse(map_jacobian, inverse_map_jacobian);
mfem::Vector mapped_gradient(m_dim);
inverse_map_jacobian.MultTranspose(reference_gradient, mapped_gradient);
physical_gradient = mapped_gradient;
}
const mfem::GridFunction *DomainMapper::GetDisplacement() const {
return m_d;
}
double DomainMapper::GetPhysInfRadius() const {
return 1.0 - m_xi_clamp;
@@ -208,11 +382,12 @@ namespace mean_field::mapping {
}
double DomainMapper::GetCacheHitRate() const {
return (static_cast<double>(m_cache_hits)) / static_cast<double>(m_cache_misses + m_cache_hits);
return (static_cast<double>(m_cache_hits)) /
static_cast<double>(m_cache_misses + m_cache_hits);
}
void DomainMapper::ResetCacheStats() const {
m_cache_hits = 0;
m_cache_hits = 0;
m_cache_misses = 0;
}
@@ -225,28 +400,36 @@ namespace mean_field::mapping {
m_d_val.SetSize(m_dim);
}
void DomainMapper::ApplyKelvinMapping(const mfem::Vector &x_ref, mfem::Vector &x_phys) const {
void DomainMapper::ApplyKelvinMapping(
const mfem::Vector &x_ref,
mfem::Vector &x_phys
) const {
const double r_ref = x_ref.Norml2();
double xi = (r_ref - m_r_star_ref) / (m_r_inf_ref - m_r_star_ref);
xi = std::clamp(xi, 0.0, m_xi_clamp);
xi = std::clamp(xi, 0.0, m_xi_clamp);
const double factor = m_r_star_ref / (r_ref * (1 - xi));
x_phys *= factor;
}
void DomainMapper::ComputeKelvinJacobian(const mfem::Vector &x_ref, const mfem::Vector &x_disp, const mfem::DenseMatrix &J_D,
mfem::DenseMatrix &J) const {
const double r_ref = x_ref.Norml2();
void DomainMapper::ComputeKelvinJacobian(
const mfem::Vector &x_ref,
const mfem::Vector &x_disp,
const mfem::DenseMatrix &J_D,
mfem::DenseMatrix &J
) const {
const double r_ref = x_ref.Norml2();
const double delta_R = m_r_inf_ref - m_r_star_ref;
double xi = (r_ref - m_r_star_ref) / delta_R;
xi = std::clamp(xi, 0.0, m_xi_clamp);
double xi = (r_ref - m_r_star_ref) / delta_R;
xi = std::clamp(xi, 0.0, m_xi_clamp);
const double denom = 1.0 - xi;
const double denom = 1.0 - xi;
const double k = m_r_star_ref / (r_ref * denom);
const double k = m_r_star_ref / (r_ref * denom);
const double dk_dr = m_r_star_ref * ((1.0 / (delta_R * r_ref * denom * denom)) - (
1.0 / (r_ref * r_ref * denom)));
const double dk_dr =
m_r_star_ref * ((1.0 / (delta_R * r_ref * denom * denom)) -
(1.0 / (r_ref * r_ref * denom)));
J.SetSize(m_dim, m_dim);
const double outer_factor = dk_dr / r_ref;
@@ -262,13 +445,17 @@ namespace mean_field::mapping {
m_cached_elem_id = -1;
}
void DomainMapper::UpdateElementCache(const mfem::ElementTransformation &T) const {
if (IsIdentity()) return;
void DomainMapper::UpdateElementCache(
const mfem::ElementTransformation &T
) const {
if (!HasDisplacementField())
return;
if (T.ElementNo != m_cached_elem_id || T.ElementType != m_cached_elem_type) {
if (T.ElementNo != m_cached_elem_id ||
T.ElementType != m_cached_elem_type) {
m_cache_misses++;
m_cached_elem_id = T.ElementNo;
m_cached_elem_type = T.ElementType;
m_cached_elem_id = T.ElementNo;
m_cached_elem_type = T.ElementType;
const mfem::FiniteElementSpace *fes = m_d->FESpace();
mfem::Array<int> vdofs;
@@ -291,4 +478,4 @@ namespace mean_field::mapping {
m_cache_hits++;
}
}
}
} // namespace mean_field::mapping

View File

@@ -0,0 +1,916 @@
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

@@ -0,0 +1,276 @@
module;
#include <mfem.hpp>
module mean_field;
import :mapping.types;
namespace mean_field::mapping {
void MapHDivFluxToPhysical(
const MappingPointContext &context,
const mfem::Vector &reference_flux,
mfem::Vector &physical_flux
) {
MFEM_VERIFY(
reference_flux.Size() == context.mapping_jacobian.Width(),
"The reference H(div) flux has the wrong dimension."
);
physical_flux.SetSize(reference_flux.Size());
context.mapping_jacobian.Mult(reference_flux, physical_flux);
physical_flux /= context.mapping_determinant;
}
void MapPhysicalFluxToHDivReference(
const MappingPointContext &context,
const mfem::Vector &physical_flux,
mfem::Vector &reference_flux
) {
MFEM_VERIFY(
physical_flux.Size() == context.inverse_mapping_jacobian.Width(),
"The physical H(div) flux has the wrong dimension."
);
reference_flux.SetSize(physical_flux.Size());
context.inverse_mapping_jacobian.Mult(physical_flux, reference_flux);
reference_flux *= context.mapping_determinant;
}
void MapReferenceGradientToPhysical(
const MappingPointContext &context,
const mfem::Vector &reference_gradient,
mfem::Vector &physical_gradient
) {
MFEM_VERIFY(
reference_gradient.Size() ==
context.inverse_mapping_jacobian.Height(),
"The reference scalar gradient has the wrong dimension."
);
physical_gradient.SetSize(reference_gradient.Size());
context.inverse_mapping_jacobian.MultTranspose(
reference_gradient, physical_gradient
);
}
void MapPhysicalGradientToReference(
const MappingPointContext &context,
const mfem::Vector &physical_gradient,
mfem::Vector &reference_gradient
) {
MFEM_VERIFY(
physical_gradient.Size() == context.mapping_jacobian.Height(),
"The physical scalar gradient has the wrong dimension."
);
reference_gradient.SetSize(physical_gradient.Size());
context.mapping_jacobian.MultTranspose(
physical_gradient, reference_gradient
);
}
void MapReferenceVectorGradientToPhysical(
const MappingPointContext &context,
const mfem::DenseMatrix &reference_gradient,
mfem::DenseMatrix &physical_gradient
) {
MFEM_VERIFY(
reference_gradient.Width() ==
context.inverse_mapping_jacobian.Height(),
"The reference vector gradient has the wrong dimension."
);
physical_gradient.SetSize(
reference_gradient.Height(),
context.inverse_mapping_jacobian.Width()
);
mfem::Mult(
reference_gradient, context.inverse_mapping_jacobian,
physical_gradient
);
}
void MapPhysicalVectorGradientToReference(
const MappingPointContext &context,
const mfem::DenseMatrix &physical_gradient,
mfem::DenseMatrix &reference_gradient
) {
MFEM_VERIFY(
physical_gradient.Width() == context.mapping_jacobian.Height(),
"The physical vector gradient has the wrong dimension."
);
reference_gradient.SetSize(
physical_gradient.Height(), context.mapping_jacobian.Width()
);
mfem::Mult(
physical_gradient, context.mapping_jacobian, reference_gradient
);
}
double MapHDivDivergenceToPhysical(
const MappingPointContext &context,
const double reference_divergence
) {
return reference_divergence / context.mapping_determinant;
}
void ComputeHDivMassTensor(
const MappingPointContext &context,
mfem::DenseMatrix &mass_tensor
) {
const int dimension = context.mapping_jacobian.Height();
MFEM_VERIFY(
context.mapping_jacobian.Width() == dimension,
"The mapping Jacobian must be square."
);
MFEM_VERIFY(
context.mapping_determinant > 0.0,
"The mapping determinant must be positive."
);
mass_tensor.SetSize(dimension, dimension);
mfem::MultAtB(
context.mapping_jacobian, context.mapping_jacobian, mass_tensor
);
mass_tensor *= 1 / context.mapping_determinant;
}
void ComputeScalarDiffusionTensor(
const MappingPointContext &context,
mfem::DenseMatrix &diffusion_tensor
) {
const int dimension = context.inverse_mapping_jacobian.Height();
MFEM_VERIFY(
context.inverse_mapping_jacobian.Width() == dimension,
"The inverse mapping Jacobian must be square."
);
MFEM_VERIFY(
context.mapping_determinant > 0.0,
"The mapping determinant must be positive."
);
diffusion_tensor.SetSize(dimension, dimension);
mfem::MultABt(
context.inverse_mapping_jacobian, context.inverse_mapping_jacobian,
diffusion_tensor
);
diffusion_tensor *= context.mapping_determinant;
}
void MapHCurlFieldToPhysical(
const MappingPointContext &context,
const mfem::Vector &reference_field,
mfem::Vector &physical_field
) {
MFEM_VERIFY(
reference_field.Size() == context.inverse_mapping_jacobian.Height(),
"The reference H(curl) field has the wrong dimension."
);
physical_field.SetSize(reference_field.Size());
context.inverse_mapping_jacobian.MultTranspose(
reference_field, physical_field
);
}
void MapPhysicalFieldToHCurlReference(
const MappingPointContext &context,
const mfem::Vector &physical_field,
mfem::Vector &reference_field
) {
MFEM_VERIFY(
physical_field.Size() == context.mapping_jacobian.Height(),
"The physical H(curl) field has the wrong dimension."
);
reference_field.SetSize(physical_field.Size());
context.mapping_jacobian.MultTranspose(physical_field, reference_field);
}
void MapHCurlCurlToPhysical(
const MappingPointContext &context,
const mfem::Vector &reference_curl,
mfem::Vector &physical_curl
) {
MFEM_VERIFY(
reference_curl.Size() == context.mapping_jacobian.Width(),
"The reference H(curl) curl has the wrong dimension."
);
physical_curl.SetSize(reference_curl.Size());
context.mapping_jacobian.Mult(reference_curl, physical_curl);
physical_curl /= context.mapping_determinant;
}
void MapPhysicalCurlToHCurlReference(
const MappingPointContext &context,
const mfem::Vector &physical_curl,
mfem::Vector &reference_curl
) {
MFEM_VERIFY(
physical_curl.Size() == context.inverse_mapping_jacobian.Width(),
"The physical H(curl) curl has the wrong dimension."
);
reference_curl.SetSize(physical_curl.Size());
context.inverse_mapping_jacobian.Mult(physical_curl, reference_curl);
reference_curl *= context.mapping_determinant;
}
// TODO: Investigate these
void ComputeHCurlMassTensor(
const MappingPointContext &context,
mfem::DenseMatrix &mass_tensor
) {
ComputeScalarDiffusionTensor(context, mass_tensor);
}
void ComputeHCurlCurlTensor(
const MappingPointContext &context,
mfem::DenseMatrix &curl_tensor
) {
ComputeHDivMassTensor(context, curl_tensor);
}
void ComputeHDivMassTensorVariation(
const MappingPointContext &context,
const MappingPointVariation &variation,
mfem::DenseMatrix &mass_tensor_variation
) {
const double determinant = context.mapping_determinant;
const double determinant_variation =
variation.mapping_determinant_variation;
const int dimension = context.inverse_mapping_jacobian.Width();
mass_tensor_variation.SetSize(dimension, dimension);
MFEM_VERIFY(
std::isfinite(determinant) && determinant > 0.0,
"The mapping determinant must be positive and finite."
);
MFEM_VERIFY(
std::isfinite(determinant_variation),
"The mapping determinant variation must be finite."
);
mfem::DenseMatrix determinant_correction(dimension, dimension);
ComputeHDivMassTensor(context, determinant_correction);
determinant_correction *= determinant_variation / determinant;
mfem::DenseMatrix right_jacobian_variation(dimension, dimension);
mfem::MultAtB(
context.mapping_jacobian, variation.mapping_jacobian_variation,
right_jacobian_variation
);
mfem::MultAtB(
variation.mapping_jacobian_variation, context.mapping_jacobian,
mass_tensor_variation
);
mass_tensor_variation += right_jacobian_variation;
mass_tensor_variation *= 1 / determinant;
mass_tensor_variation -= determinant_correction;
}
} // namespace mean_field::mapping

View File

@@ -0,0 +1,135 @@
module;
#include <cstdint>
#include <mfem.hpp>
module mean_field;
import :operators.context.barotropic_closure_linearization;
namespace mean_field::operators::context::barotropic {
BarotropicClosureLinearizationContext::
BarotropicClosureLinearizationContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope
)
: m_f(f),
m_operator(
f,
domainMapper,
barotrope
) {
MFEM_VERIFY(
m_f.densityFes != nullptr,
"The closure linearization context requires the "
"density finite-element space."
);
MFEM_VERIFY(
m_f.enthalpyFes != nullptr,
"The closure linearization context requires the "
"enthalpy finite-element space."
);
MFEM_VERIFY(
m_f.displacementFes != nullptr,
"The closure linearization context requires the "
"displacement finite-element space."
);
}
void BarotropicClosureLinearizationContext::Prepare(
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementTrue,
const BarotropicClosureRevisions &revisions
) {
MFEM_VERIFY(
baseDensityTrue.Size() == m_f.densityFes->GetTrueVSize(),
"The closure base-density vector has the wrong size."
);
MFEM_VERIFY(
baseEnthalpyTrue.Size() == m_f.enthalpyFes->GetTrueVSize(),
"The closure base-enthalpy vector has the wrong size."
);
MFEM_VERIFY(
displacementTrue.Size() == m_f.displacementFes->GetTrueVSize(),
"The closure displacement vector has the wrong size."
);
if (m_isPrepared && revisions == m_revisions) {
return;
}
m_operator.Prepare(baseDensityTrue, baseEnthalpyTrue, displacementTrue);
m_baseDensityTrue = baseDensityTrue;
m_baseEnthalpyTrue = baseEnthalpyTrue;
m_displacementTrue = displacementTrue;
m_revisions = revisions;
m_isPrepared = true;
++m_preparationCount;
}
bool BarotropicClosureLinearizationContext::IsPrepared() const noexcept {
return m_isPrepared;
}
bool BarotropicClosureLinearizationContext::MatchesRevisions(
const BarotropicClosureRevisions &revisions
) const noexcept {
return m_isPrepared && revisions == m_revisions;
}
std::uint64_t BarotropicClosureLinearizationContext::
GetPreparationCount() const noexcept {
return m_preparationCount;
}
const BarotropicClosureRevisions &
BarotropicClosureLinearizationContext::GetRevisions() const {
VerifyPrepared();
return m_revisions;
}
const mfem::Vector &
BarotropicClosureLinearizationContext::GetBaseDensityTrue() const {
VerifyPrepared();
return m_baseDensityTrue;
}
const mfem::Vector &
BarotropicClosureLinearizationContext::GetBaseEnthalpyTrue() const {
VerifyPrepared();
return m_baseEnthalpyTrue;
}
const mfem::Vector &
BarotropicClosureLinearizationContext::GetDisplacementTrue() const {
VerifyPrepared();
return m_displacementTrue;
}
const PreparedBarotropicClosureOperator &
BarotropicClosureLinearizationContext::GetOperator() const noexcept {
return m_operator;
}
void BarotropicClosureLinearizationContext::BuildResidual(
mfem::Vector &residual
) const {
VerifyPrepared();
m_operator.BuildResidual(residual);
}
void BarotropicClosureLinearizationContext::VerifyPrepared() const {
MFEM_VERIFY(
m_isPrepared, "The barotropic-closure linearization context "
"has not been prepared."
);
}
} // namespace mean_field::operators::context::barotropic

View File

@@ -0,0 +1,442 @@
module;
#include <cmath>
#include <memory>
#include <mfem.hpp>
module mean_field;
import :operators.context.gravity_field;
namespace {
void validate_displacement(
const mean_field::fem::FEM &f,
const mfem::Vector &displacement_true
) {
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldGeometryContext requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
displacement_true.Size() == f.displacementFes->GetTrueVSize(),
"GravityFieldGeometryContext received a displacement vector with "
"the "
"wrong size."
);
for (int i = 0; i < displacement_true.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement_true(i)),
"GravityFieldGeometryContext received a non-finite "
"displacement "
"value."
);
}
}
void validate_linearization_state(
const mean_field::fem::FEM &f,
const mean_field::operators::context::gravity_field::
GravityFieldStateView &state
) {
MFEM_VERIFY(
f.densityFes != nullptr, "GravityFieldLinearizationContext "
"requires the density finite-element "
"space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldLinearizationContext requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldLinearizationContext requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldLinearizationContext requires "
"the displacement finite-element space."
);
MFEM_VERIFY(
state.density.Size() == f.densityFes->GetTrueVSize(),
"GravityFieldLinearizationContext received a density vector with "
"the "
"wrong size."
);
MFEM_VERIFY(
state.displacement.Size() == f.displacementFes->GetTrueVSize(),
"GravityFieldLinearizationContext received a displacement vector "
"with "
"the wrong size."
);
MFEM_VERIFY(
state.gravity_gradient.Size() == f.gravityFluxFes->GetTrueVSize(),
"GravityFieldLinearizationContext received a gravity-gradient "
"vector "
"with the wrong size."
);
MFEM_VERIFY(
state.gravity_potential.Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"GravityFieldLinearizationContext received a gravity-potential "
"vector "
"with the wrong size."
);
for (int i = 0; i < state.density.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(state.density(i)),
"GravityFieldLinearizationContext received a non-finite "
"density "
"value."
);
}
for (int i = 0; i < state.displacement.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(state.displacement(i)),
"GravityFieldLinearizationContext received a non-finite "
"displacement "
"value."
);
}
for (int i = 0; i < state.gravity_gradient.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(state.gravity_gradient(i)),
"GravityFieldLinearizationContext received a non-finite "
"gravity-gradient value."
);
}
for (int i = 0; i < state.gravity_potential.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(state.gravity_potential(i)),
"GravityFieldLinearizationContext received a non-finite "
"gravity-potential value."
);
}
}
} // namespace
namespace mean_field::operators::context::gravity_field {
GravityFieldGeometryContext::GravityFieldGeometryContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
)
: m_fem(f),
m_domain_mapper(domain_mapper) {
MFEM_VERIFY(
f.mesh != nullptr, "GravityFieldGeometryContext requires a mesh."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldGeometryContext requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"GravityFieldGeometryContext requires the density finite-element "
"space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldGeometryContext requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldGeometryContext requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"GravityFieldGeometryContext requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"GravityFieldGeometryContext requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"GravityFieldGeometryContext requires the quadrature-rule factory."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"The stateless domain-mapper dimension does not match the mesh "
"dimension."
);
}
GravityFieldGeometryPreparation GravityFieldGeometryContext::Prepare(
const mfem::Vector &displacement_true,
const DiscretizationRevision discretization_revision,
const DisplacementRevision displacement_revision
) {
validate_displacement(m_fem, displacement_true);
if (m_is_prepared) {
MFEM_VERIFY(
discretization_revision >= m_discretization_revision,
"GravityFieldGeometryContext received an older discretization "
"revision."
);
MFEM_VERIFY(
displacement_revision >= m_displacement_revision,
"GravityFieldGeometryContext received an older displacement "
"revision."
);
}
const bool discretization_changed =
!m_is_prepared ||
discretization_revision != m_discretization_revision;
const bool displacement_changed =
!m_is_prepared || displacement_revision != m_displacement_revision;
GravityFieldGeometryPreparation preparation;
if (!discretization_changed && !displacement_changed) {
return preparation;
}
if (discretization_changed) {
auto mass_operator =
std::make_unique<PreparedMappedHDivMassOperator>(
m_fem, m_domain_mapper
);
auto source_operator =
std::make_unique<PreparedMappedGravitySourceOperator>(
m_fem, m_domain_mapper
);
mass_operator->Prepare(displacement_true);
source_operator->Prepare(displacement_true);
m_mass_operator = std::move(mass_operator);
m_source_operator = std::move(source_operator);
preparation.reconstructed_operators = true;
preparation.rebuilt_mass_operator = true;
preparation.rebuilt_source_operator = true;
} else {
MFEM_VERIFY(
m_mass_operator != nullptr, "GravityFieldGeometryContext has "
"no prepared H(div) mass operator."
);
MFEM_VERIFY(
m_source_operator != nullptr,
"GravityFieldGeometryContext has no prepared gravity source "
"operator."
);
m_mass_operator->Prepare(displacement_true);
m_source_operator->Prepare(displacement_true);
preparation.rebuilt_mass_operator = true;
preparation.rebuilt_source_operator = true;
}
m_displacement_true = displacement_true;
m_discretization_revision = discretization_revision;
m_displacement_revision = displacement_revision;
m_is_prepared = true;
preparation.refreshed_variation_state = true;
return preparation;
}
const PreparedMappedHDivMassOperator &
GravityFieldGeometryContext::GetMassOperator() const {
MFEM_VERIFY(
m_is_prepared,
"GravityFieldGeometryContext must be prepared before "
"accessing its mass operator."
);
MFEM_VERIFY(
m_mass_operator != nullptr,
"GravityFieldGeometryContext has no prepared H(div) mass operator."
);
return *m_mass_operator;
}
const PreparedMappedGravitySourceOperator &
GravityFieldGeometryContext::GetSourceOperator() const {
MFEM_VERIFY(
m_is_prepared,
"GravityFieldGeometryContext must be prepared before "
"accessing its source operator."
);
MFEM_VERIFY(
m_source_operator != nullptr, "GravityFieldGeometryContext has no "
"prepared gravity source operator."
);
return *m_source_operator;
}
const mfem::Vector &GravityFieldGeometryContext::GetDisplacement() const {
MFEM_VERIFY(
m_is_prepared,
"GravityFieldGeometryContext must be prepared before "
"accessing its displacement."
);
return m_displacement_true;
}
DiscretizationRevision
GravityFieldGeometryContext::GetDiscretizationRevision() const noexcept {
return m_discretization_revision;
}
DisplacementRevision
GravityFieldGeometryContext::GetDisplacementRevision() const noexcept {
return m_displacement_revision;
}
bool GravityFieldGeometryContext::IsPrepared() const noexcept {
return m_is_prepared;
}
GravityFieldLinearizationContext::GravityFieldLinearizationContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
)
: m_fem(f),
m_geometry_context(
f,
domain_mapper
) {
MFEM_VERIFY(
f.densityFes != nullptr, "GravityFieldLinearizationContext "
"requires the density finite-element "
"space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldLinearizationContext requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldLinearizationContext requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldLinearizationContext requires "
"the displacement finite-element space."
);
}
GravityFieldPreparationReport GravityFieldLinearizationContext::Prepare(
const GravityFieldStateView &state,
const GravityFieldRevisions &revisions
) {
validate_linearization_state(m_fem, state);
if (m_is_prepared) {
MFEM_VERIFY(
revisions.discretization >= m_revisions.discretization,
"GravityFieldLinearizationContext received an older "
"discretization "
"revision."
);
MFEM_VERIFY(
revisions.displacement >= m_revisions.displacement,
"GravityFieldLinearizationContext received an older "
"displacement "
"revision."
);
MFEM_VERIFY(
revisions.density >= m_revisions.density,
"GravityFieldLinearizationContext received an older density "
"revision."
);
MFEM_VERIFY(
revisions.gravity_gradient >= m_revisions.gravity_gradient,
"GravityFieldLinearizationContext received an older "
"gravity-gradient "
"revision."
);
MFEM_VERIFY(
revisions.gravity_potential >= m_revisions.gravity_potential,
"GravityFieldLinearizationContext received an older "
"gravity-potential revision."
);
}
const bool discretization_changed =
!m_is_prepared ||
revisions.discretization != m_revisions.discretization;
const bool density_changed = !m_is_prepared || discretization_changed ||
revisions.density != m_revisions.density;
const bool gravity_gradient_changed =
!m_is_prepared || discretization_changed ||
revisions.gravity_gradient != m_revisions.gravity_gradient;
GravityFieldPreparationReport report;
report.geometry = m_geometry_context.Prepare(
state.displacement, revisions.discretization, revisions.displacement
);
if (density_changed) {
m_density_true = state.density;
report.updated_density = true;
}
if (gravity_gradient_changed) {
m_gravity_gradient_true = state.gravity_gradient;
report.updated_gravity_gradient = true;
}
m_revisions = revisions;
m_is_prepared = true;
return report;
}
const GravityFieldGeometryContext &
GravityFieldLinearizationContext::GetGeometryContext() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
"before accessing its geometry context."
);
return m_geometry_context;
}
const mfem::Vector &GravityFieldLinearizationContext::GetDensity() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
"before accessing its density."
);
return m_density_true;
}
const mfem::Vector &
GravityFieldLinearizationContext::GetGravityGradient() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
"before accessing its gravity gradient."
);
return m_gravity_gradient_true;
}
const GravityFieldRevisions &
GravityFieldLinearizationContext::GetRevisions() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
"before accessing its revisions."
);
return m_revisions;
}
bool GravityFieldLinearizationContext::IsPrepared() const noexcept {
return m_is_prepared;
}
} // namespace mean_field::operators::context::gravity_field

View File

@@ -0,0 +1,309 @@
module;
#include <cmath>
#include <mfem.hpp>
module mean_field;
import :operators.context.hydrostatic_equilibrium;
namespace {
void validate_finite_vector(
const mfem::Vector &vector,
const char *message
) {
for (int i = 0; i < vector.Size(); ++i) {
MFEM_VERIFY(std::isfinite(vector(i)), message);
}
}
void validate_state(
const mean_field::fem::FEM &f,
const mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumStateView &state
) {
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"enthalpy finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"gravity-potential finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
state.enthalpy.Size() == f.enthalpyFes->GetTrueVSize(),
"HydrostaticEquilibriumContext received an "
"enthalpy vector with the wrong size."
);
MFEM_VERIFY(
state.gravityPotential.Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"HydrostaticEquilibriumContext received a "
"gravity-potential vector with the wrong size."
);
MFEM_VERIFY(
state.displacement.Size() == f.displacementFes->GetTrueVSize(),
"HydrostaticEquilibriumContext received a "
"displacement vector with the wrong size."
);
validate_finite_vector(
state.enthalpy, "HydrostaticEquilibriumContext received a "
"non-finite enthalpy value."
);
validate_finite_vector(
state.gravityPotential, "HydrostaticEquilibriumContext received a "
"non-finite gravity-potential value."
);
validate_finite_vector(
state.displacement, "HydrostaticEquilibriumContext received a "
"non-finite displacement value."
);
MFEM_VERIFY(
std::isfinite(state.bernoulliConstant),
"HydrostaticEquilibriumContext received a "
"non-finite Bernoulli constant."
);
}
template <typename Stamp>
void validate_dependency_transition(
const Stamp &prepared,
const Stamp &requested,
const char *message
) {
MFEM_VERIFY(requested.CanFollow(prepared), message);
}
} // namespace
namespace mean_field::operators::context::hydrostatic {
HydrostaticEquilibriumContext::HydrostaticEquilibriumContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper
)
: m_f(f),
m_domainMapper(domainMapper) {
MFEM_VERIFY(
m_f.mesh != nullptr,
"HydrostaticEquilibriumContext requires a mesh."
);
MFEM_VERIFY(
m_f.enthalpyFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"enthalpy finite-element space."
);
MFEM_VERIFY(
m_f.gravityPotentialFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"gravity-potential finite-element space."
);
MFEM_VERIFY(
m_f.displacementFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
m_domainMapper.GetDimension() == m_f.mesh->Dimension(),
"The hydrostatic context's stateless "
"domain-mapper dimension does not match the mesh "
"dimension."
);
}
HydrostaticPreparationReport HydrostaticEquilibriumContext::Prepare(
const HydrostaticEquilibriumStateView &state,
const HydrostaticEquilibriumDependencies &dependencies
) {
validate_state(m_f, state);
if (m_isPrepared) {
validate_dependency_transition(
m_dependencies.discretization, dependencies.discretization,
"HydrostaticEquilibriumContext received an older "
"discretization revision for the same identity."
);
validate_dependency_transition(
m_dependencies.enthalpy, dependencies.enthalpy,
"HydrostaticEquilibriumContext received an older "
"enthalpy revision for the same identity."
);
validate_dependency_transition(
m_dependencies.gravityPotential, dependencies.gravityPotential,
"HydrostaticEquilibriumContext received an older "
"gravity-potential revision for the same identity."
);
validate_dependency_transition(
m_dependencies.displacement, dependencies.displacement,
"HydrostaticEquilibriumContext received an older "
"displacement revision for the same identity."
);
validate_dependency_transition(
m_dependencies.rotation, dependencies.rotation,
"HydrostaticEquilibriumContext received an older "
"rotation revision for the same identity."
);
validate_dependency_transition(
m_dependencies.bernoulliConstant,
dependencies.bernoulliConstant,
"HydrostaticEquilibriumContext received an older "
"Bernoulli-constant revision for the same identity."
);
}
const bool staticChanged =
!m_isPrepared ||
dependencies.discretization != m_dependencies.discretization;
const bool enthalpyChanged =
!m_isPrepared || dependencies.enthalpy != m_dependencies.enthalpy;
const bool gravityPotentialChanged =
!m_isPrepared ||
dependencies.gravityPotential != m_dependencies.gravityPotential;
const bool displacementChanged =
!m_isPrepared ||
dependencies.displacement != m_dependencies.displacement;
const bool rotationChanged =
!m_isPrepared || dependencies.rotation != m_dependencies.rotation;
const bool bernoulliConstantChanged =
!m_isPrepared ||
dependencies.bernoulliConstant != m_dependencies.bernoulliConstant;
const bool geometryPreparationRequired =
staticChanged || displacementChanged;
const bool rotationPreparationRequired =
geometryPreparationRequired || rotationChanged;
const bool baseStatePreparationRequired =
rotationPreparationRequired || enthalpyChanged ||
gravityPotentialChanged || bernoulliConstantChanged;
HydrostaticPreparationReport report;
report.preparedStaticDependencies = staticChanged;
report.preparedGeometryState = geometryPreparationRequired;
report.preparedRotationDependencies = rotationPreparationRequired;
report.preparedBaseState = baseStatePreparationRequired;
if (staticChanged || enthalpyChanged) {
m_baseEnthalpyTrue = state.enthalpy;
report.updatedEnthalpy = true;
}
if (staticChanged || gravityPotentialChanged) {
m_baseGravityPotentialTrue = state.gravityPotential;
report.updatedGravityPotential = true;
}
if (geometryPreparationRequired) {
m_displacementTrue = state.displacement;
report.updatedDisplacement = true;
}
if (staticChanged || bernoulliConstantChanged) {
m_bernoulliConstant = state.bernoulliConstant;
report.updatedBernoulliConstant = true;
}
if (report.preparedStaticDependencies) {
++m_statistics.staticPreparations;
}
if (report.preparedGeometryState) {
++m_statistics.geometryPreparations;
}
if (report.preparedRotationDependencies) {
++m_statistics.rotationPreparations;
}
if (report.preparedBaseState) {
++m_statistics.baseStatePreparations;
}
m_dependencies = dependencies;
m_isPrepared = true;
return report;
}
bool HydrostaticEquilibriumContext::IsPrepared() const noexcept {
return m_isPrepared;
}
bool HydrostaticEquilibriumContext::MatchesDependencies(
const HydrostaticEquilibriumDependencies &dependencies
) const noexcept {
return m_isPrepared && dependencies == m_dependencies;
}
const HydrostaticEquilibriumDependencies &
HydrostaticEquilibriumContext::GetDependencies() const {
VerifyPrepared();
return m_dependencies;
}
const HydrostaticPreparationStatistics &
HydrostaticEquilibriumContext::GetPreparationStatistics() const noexcept {
return m_statistics;
}
const mfem::Vector &
HydrostaticEquilibriumContext::GetBaseEnthalpyTrue() const {
VerifyPrepared();
return m_baseEnthalpyTrue;
}
const mfem::Vector &
HydrostaticEquilibriumContext::GetBaseGravityPotentialTrue() const {
VerifyPrepared();
return m_baseGravityPotentialTrue;
}
const mfem::Vector &
HydrostaticEquilibriumContext::GetDisplacementTrue() const {
VerifyPrepared();
return m_displacementTrue;
}
double HydrostaticEquilibriumContext::GetBernoulliConstant() const {
VerifyPrepared();
return m_bernoulliConstant;
}
void HydrostaticEquilibriumContext::VerifyPrepared() const {
MFEM_VERIFY(
m_isPrepared, "HydrostaticEquilibriumContext has not been prepared."
);
}
} // namespace mean_field::operators::context::hydrostatic

View File

@@ -0,0 +1,790 @@
module;
#include "profile.h"
#include <cmath>
#include <cstdint>
#include <limits>
#include <mfem.hpp>
module mean_field;
import :operators.gravity_field;
import :solver.fields;
import :operators.kernels.gravity_field;
namespace {
using namespace mean_field;
int get_state_width(const mfem::Array<int> &state_true_offsets) {
MFEM_VERIFY(
state_true_offsets.Size() >= 2,
"The coupled state requires at least one block."
);
MFEM_VERIFY(
state_true_offsets[0] == 0,
"The coupled state offsets must begin at zero."
);
for (int i = 0; i < state_true_offsets.Size() - 1; ++i) {
MFEM_VERIFY(
state_true_offsets[i + 1] >= state_true_offsets[i],
"The coupled state offsets must be nondecreasing."
);
}
MFEM_VERIFY(
state_true_offsets.Last() > 0, "The coupled state cannot be empty."
);
return state_true_offsets.Last();
}
int get_gravity_residual_height(const fem::FEM &f) {
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldOperator requires the gravity-gradient finite-element "
"space (RT: Raviart-Thomas)."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldOperator requires the gravity-potential "
"finite-element "
"space (L2: Lebesgue "
"space of square-integrable functions)."
);
return f.gravityFluxFes->GetTrueVSize() +
f.gravityPotentialFes->GetTrueVSize();
}
mfem::Array<int> make_gravity_residual_offsets(const fem::FEM &f) {
mfem::Array<int> offsets(operators::gravity_residual_block_count + 1);
offsets[0] = 0;
offsets[1] = f.gravityFluxFes->GetTrueVSize();
offsets[2] = offsets[1] + f.gravityPotentialFes->GetTrueVSize();
return offsets;
}
template <int index>
int get_state_block_size(
const mfem::Array<int> &state_true_offsets,
const utils::blocks::value_block<index>
) {
MFEM_VERIFY(
index + 1 < state_true_offsets.Size(),
"Value block is not present in the state offsets."
);
return state_true_offsets[index + 1] - state_true_offsets[index];
}
void validate_state_offsets(
const fem::FEM &f,
const mfem::Array<int> &state_true_offsets
) {
MFEM_VERIFY(
f.densityFes != nullptr,
"GravityFieldOperator requires the density finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "GravityFieldOperator requires the "
"displacement finite-element space."
);
using form = utils::blocks::gravity_field_form;
constexpr auto density_block = utils::blocks::get_value_block<form>(
utils::blocks::density_field.mass_term
);
constexpr auto displacement_block =
utils::blocks::get_value_block<form>(
utils::blocks::displacement_field.geometry_term
);
constexpr auto gravity_gradient_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_potential_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.poisson_term
);
MFEM_VERIFY(
state_true_offsets.Size() == form::value_block_count + 1,
"The gravity state offsets do not match gravity_field_form."
);
MFEM_VERIFY(
get_state_block_size(state_true_offsets, density_block) ==
f.densityFes->GetTrueVSize(),
"The density block does not match the density finite-element space."
);
MFEM_VERIFY(
get_state_block_size(state_true_offsets, displacement_block) ==
f.displacementFes->GetTrueVSize(),
"The displacement block does not match the displacement "
"finite-element "
"space."
);
MFEM_VERIFY(
get_state_block_size(state_true_offsets, gravity_gradient_block) ==
f.gravityFluxFes->GetTrueVSize(),
"The gravity-gradient block does not match the RT finite-element "
"space."
);
MFEM_VERIFY(
get_state_block_size(state_true_offsets, gravity_potential_block) ==
f.gravityPotentialFes->GetTrueVSize(),
"The gravity-potential block does not match the potential "
"finite-element space."
);
}
void validate_gravity_context(const fem::FEM &f) {
MFEM_VERIFY(
f.gravityContext.b_form != nullptr,
"GravityFieldOperator requires the divergence operator."
);
MFEM_VERIFY(
f.gravityContext.BT != nullptr,
"GravityFieldOperator requires the transpose divergence operator."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"GravityFieldOperator requires the quadrature-rule factory."
);
}
template <int index>
mfem::Vector make_read_only_value_view(
const mfem::Vector &vector,
const mfem::Array<int> &offsets,
const utils::blocks::value_block<index>
) {
MFEM_VERIFY(
index + 1 < offsets.Size(),
"Value block is not present in the supplied offset array."
);
const int begin = offsets[index];
const int size = offsets[index + 1] - begin;
MFEM_VERIFY(
vector.Size() == offsets.Last(),
"Vector size does not match the value-block offsets."
);
return mfem::Vector(
const_cast<mfem::real_t *>(vector.GetData()) + begin, size
);
}
template <int index>
mfem::Vector make_read_only_residual_view(
const mfem::Vector &vector,
const mfem::Array<int> &offsets,
const utils::blocks::residual_block<index> block
) {
const int block_id = block;
const int begin = offsets[block_id];
const int size = offsets[block_id + 1] - begin;
MFEM_VERIFY(
vector.Size() == offsets.Last(),
"The vector does not match the residual-block layout."
);
mfem::Vector view;
view.MakeRef(const_cast<mfem::Vector &>(vector), begin, size);
return view;
}
template <int index>
mfem::Vector make_residual_view(
mfem::Vector &vector,
const mfem::Array<int> &offsets,
const utils::blocks::residual_block<index>
) {
MFEM_VERIFY(
index + 1 < offsets.Size(),
"Residual block is not present in the supplied offset array."
);
const int begin = offsets[index];
const int size = offsets[index + 1] - begin;
MFEM_VERIFY(
vector.Size() == offsets.Last(),
"Vector size does not match the residual-block offsets."
);
return mfem::Vector(vector.GetData() + begin, size);
}
} // namespace
namespace mean_field::operators {
GravityFieldOperator::GravityFieldOperator(
fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
context::gravity_field::GravityFieldLinearizationContext
&linearization_context,
const mfem::Array<int> &state_true_offsets,
GravityFieldJacobianOperator &jacobian
)
: Operator(
get_gravity_residual_height(f),
get_state_width(state_true_offsets)
),
m_fem(f),
m_domain_mapper(domain_mapper),
m_linearization_context(linearization_context),
m_state_true_offsets(state_true_offsets),
m_residual_true_offsets(make_gravity_residual_offsets(f)),
m_jacobian(jacobian) {
MFEM_VERIFY(f.mesh != nullptr, "GravityFieldOperator requires a mesh.");
MFEM_VERIFY(
f.displacementFes != nullptr, "GravityFieldOperator requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.smesh.exterior_coordinate != nullptr,
"GravityFieldOperator requires the STROID exterior coordinate."
);
MFEM_VERIFY(
f.smesh.exterior_coordinate->space != nullptr,
"GravityFieldOperator requires the exterior-coordinate "
"finite-element "
"space."
);
MFEM_VERIFY(
f.smesh.exterior_coordinate->values != nullptr,
"GravityFieldOperator requires the exterior-coordinate values."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"GravityFieldOperator received a domain mapper with the wrong "
"dimension."
);
validate_state_offsets(f, m_state_true_offsets);
validate_gravity_context(f);
bool has_vacuum_domain = false;
for (int i = 0; i < f.mesh->attributes.Size(); ++i) {
if (f.mesh->attributes[i] ==
domain_mapper.GetVacuumElementAttribute()) {
has_vacuum_domain = true;
break;
}
}
MFEM_VERIFY(
has_vacuum_domain,
"GravityFieldOperator requires a compactified vacuum domain."
);
MFEM_VERIFY(
m_residual_true_offsets.Last() == Height(),
"The gravity residual offsets do not match the operator height."
);
MFEM_VERIFY(
m_state_true_offsets.Last() == Width(),
"The coupled state offsets do not match the operator width."
);
}
context::gravity_field::GravityFieldPreparationReport
GravityFieldOperator::Prepare(
const mfem::Vector &state,
const context::gravity_field::GravityFieldRevisions &revisions
) {
using form = utils::blocks::gravity_field_form;
constexpr auto density_block = utils::blocks::get_value_block<form>(
utils::blocks::density_field.mass_term
);
constexpr auto displacement_block =
utils::blocks::get_value_block<form>(
utils::blocks::displacement_field.geometry_term
);
constexpr auto gravity_gradient_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_potential_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.poisson_term
);
MFEM_VERIFY(
state.Size() == Width(), "GravityFieldOperator received a "
"preparation state with the wrong size."
);
const mfem::Vector density = make_read_only_value_view(
state, m_state_true_offsets, density_block
);
const mfem::Vector displacement = make_read_only_value_view(
state, m_state_true_offsets, displacement_block
);
const mfem::Vector gravity_gradient = make_read_only_value_view(
state, m_state_true_offsets, gravity_gradient_block
);
const mfem::Vector gravity_potential = make_read_only_value_view(
state, m_state_true_offsets, gravity_potential_block
);
return m_linearization_context.Prepare(
{.density = density,
.displacement = displacement,
.gravity_gradient = gravity_gradient,
.gravity_potential = gravity_potential},
revisions
);
}
const mfem::Array<int> &
GravityFieldOperator::GetStateTrueOffsets() const noexcept {
return m_state_true_offsets;
}
const mfem::Array<int> &
GravityFieldOperator::GetResidualTrueOffsets() const noexcept {
return m_residual_true_offsets;
}
void GravityFieldOperator::ApplyGravityUnknowns(
const mfem::Vector &gravity_gradient,
const mfem::Vector &gravity_potential,
const context::gravity_field::GravityFieldGeometryContext
&geometry_context,
mfem::Vector &action
) const {
using form = utils::blocks::gravity_field_form;
constexpr auto gravity_gradient_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
);
MFEM_VERIFY(
geometry_context.IsPrepared(),
"GravityFieldOperator received an unprepared geometry context."
);
MFEM_VERIFY(
gravity_gradient.Size() == m_fem.gravityFluxFes->GetTrueVSize(),
"GravityFieldOperator received a gravity-gradient vector with the "
"wrong size."
);
MFEM_VERIFY(
gravity_potential.Size() ==
m_fem.gravityPotentialFes->GetTrueVSize(),
"GravityFieldOperator received a gravity-potential vector with the "
"wrong size."
);
action.SetSize(Height());
action = 0.0;
mfem::Vector gravity_gradient_action = make_residual_view(
action, m_residual_true_offsets, gravity_gradient_residual_block
);
mfem::Vector gravity_poisson_action = make_residual_view(
action, m_residual_true_offsets, gravity_poisson_residual_block
);
mfem::Vector transpose_divergence_action(
gravity_gradient_action.Size()
);
geometry_context.GetMassOperator().Mult(
gravity_gradient, gravity_gradient_action
);
m_fem.gravityContext.BT->Mult(
gravity_potential, transpose_divergence_action
);
gravity_gradient_action += transpose_divergence_action;
m_fem.gravityContext.b_form->Mult(
gravity_gradient, gravity_poisson_action
);
}
void GravityFieldOperator::ApplyDensitySource(
const mfem::Vector &density,
const context::gravity_field::GravityFieldGeometryContext
&geometry_context,
mfem::Vector &action
) const {
using form = utils::blocks::gravity_field_form;
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
);
MFEM_VERIFY(
geometry_context.IsPrepared(),
"GravityFieldOperator received an unprepared geometry context."
);
MFEM_VERIFY(
density.Size() == m_fem.densityFes->GetTrueVSize(),
"GravityFieldOperator received a density vector with the wrong "
"size."
);
action.SetSize(Height());
action = 0.0;
mfem::Vector gravity_poisson_action = make_residual_view(
action, m_residual_true_offsets, gravity_poisson_residual_block
);
geometry_context.GetSourceOperator().Mult(
density, gravity_poisson_action
);
}
void GravityFieldOperator::Mult(
const mfem::Vector &state,
mfem::Vector &residual
) const {
MEAN_FIELD_PROFILE_SCOPE("GravityFieldOperator::Mult");
using form = utils::blocks::gravity_field_form;
constexpr auto density_block = utils::blocks::get_value_block<form>(
utils::blocks::density_field.mass_term
);
constexpr auto gravity_gradient_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_potential_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.poisson_term
);
MFEM_VERIFY(
state.Size() == Width(),
"GravityFieldOperator received a state with the wrong size."
);
MFEM_VERIFY(
m_linearization_context.IsPrepared(),
"GravityFieldOperator must be prepared before Mult is called."
);
const mfem::Vector density = make_read_only_value_view(
state, m_state_true_offsets, density_block
);
const mfem::Vector gravity_gradient = make_read_only_value_view(
state, m_state_true_offsets, gravity_gradient_block
);
const mfem::Vector gravity_potential = make_read_only_value_view(
state, m_state_true_offsets, gravity_potential_block
);
const context::gravity_field::GravityFieldGeometryContext
&geometry_context = m_linearization_context.GetGeometryContext();
mfem::Vector source;
ApplyGravityUnknowns(
gravity_gradient, gravity_potential, geometry_context, residual
);
ApplyDensitySource(density, geometry_context, source);
residual -= source;
}
context::gravity_field::GravityFieldLinearizationContext &
GravityFieldOperator::GetLinearizationContext() noexcept {
return m_linearization_context;
}
const context::gravity_field::GravityFieldLinearizationContext &
GravityFieldOperator::GetLinearizationContext() const noexcept {
return m_linearization_context;
}
mfem::Operator &
GravityFieldOperator::GetGradient(const mfem::Vector &state) const {
MFEM_VERIFY(
state.Size() == Width(), "GravityFieldOperator received a "
"linearization state with the wrong size."
);
MFEM_VERIFY(
m_linearization_context.IsPrepared(),
"GravityFieldOperator must be prepared before GetGradient is "
"called."
);
return m_jacobian;
}
ReducedGravityFieldOperator::ReducedGravityFieldOperator(
GravityFieldOperator &gravity_field_operator,
context::gravity_field::GravityFieldGeometryContext
&gravity_field_geometry_context,
const mfem::Vector &displacement
)
: Operator(
gravity_field_operator.Height(),
gravity_field_operator.Height()
),
m_gravity_field_operator(gravity_field_operator),
m_gravity_true_offsets(
gravity_field_operator.GetResidualTrueOffsets()
),
m_gravity_field_geometry_context(gravity_field_geometry_context) {
using form = utils::blocks::gravity_field_form;
constexpr auto gravity_gradient_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_potential_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.poisson_term
);
constexpr auto gravity_gradient_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
);
const mfem::Array<int> &state_offsets =
m_gravity_field_operator.GetStateTrueOffsets();
MFEM_VERIFY(
state_offsets.Size() == form::value_block_count + 1,
"ReducedGravityFieldOperator received an invalid full-state layout."
);
MFEM_VERIFY(
m_gravity_true_offsets.Size() == form::residual_block_count + 1,
"ReducedGravityFieldOperator received an invalid gravity-residual "
"layout."
);
MFEM_VERIFY(
state_offsets[0] == 0, "The full-state offsets must begin at zero."
);
MFEM_VERIFY(
m_gravity_true_offsets[0] == 0,
"The reduced gravity offsets must begin at zero."
);
MFEM_VERIFY(
state_offsets.Last() == m_gravity_field_operator.Width(),
"The full-state offsets do not match the gravity-field operator "
"width."
);
MFEM_VERIFY(
m_gravity_true_offsets.Last() == m_gravity_field_operator.Height(),
"The reduced gravity offsets do not match the gravity-field "
"operator "
"height."
);
MFEM_VERIFY(
Width() == Height(), "ReducedGravityFieldOperator must be square."
);
const int full_gradient_size =
state_offsets[static_cast<int>(gravity_gradient_block) + 1] -
state_offsets[gravity_gradient_block];
const int full_potential_size =
state_offsets[static_cast<int>(gravity_potential_block) + 1] -
state_offsets[gravity_potential_block];
const int reduced_gradient_size =
m_gravity_true_offsets
[static_cast<int>(gravity_gradient_residual_block) + 1] -
m_gravity_true_offsets[gravity_gradient_residual_block];
const int reduced_potential_size =
m_gravity_true_offsets
[static_cast<int>(gravity_poisson_residual_block) + 1] -
m_gravity_true_offsets[gravity_poisson_residual_block];
MFEM_VERIFY(
full_gradient_size == reduced_gradient_size,
"The reduced gravity-gradient block does not match the full-state "
"gravity-gradient block."
);
MFEM_VERIFY(
full_potential_size == reduced_potential_size,
"The reduced gravity-potential block does not match the Poisson "
"residual block."
);
SetDisplacement(displacement);
}
void ReducedGravityFieldOperator::SetDisplacement(
const mfem::Vector &displacement
) {
ValidateDisplacement(displacement);
context::gravity_field::DiscretizationRevision discretization_revision;
context::gravity_field::DisplacementRevision displacement_revision;
if (m_gravity_field_geometry_context.IsPrepared()) {
discretization_revision =
m_gravity_field_geometry_context.GetDiscretizationRevision();
displacement_revision =
m_gravity_field_geometry_context.GetDisplacementRevision();
MFEM_VERIFY(
displacement_revision.value <
std::numeric_limits<std::uint64_t>::max(),
"The reduced gravity displacement revision has overflowed."
);
++displacement_revision.value;
}
m_gravity_field_geometry_context.Prepare(
displacement, discretization_revision, displacement_revision
);
}
const mfem::Vector &ReducedGravityFieldOperator::GetDisplacement() const {
return m_gravity_field_geometry_context.GetDisplacement();
}
void ReducedGravityFieldOperator::BuildRightHandSide(
const mfem::Vector &density,
mfem::Vector &right_hand_side
) const {
ValidateDensity(density);
m_gravity_field_operator.ApplyDensitySource(
density, m_gravity_field_geometry_context, right_hand_side
);
MFEM_VERIFY(
right_hand_side.Size() == Height(),
"ReducedGravityFieldOperator produced a right-hand side with the "
"wrong "
"size."
);
}
void ReducedGravityFieldOperator::Mult(
const mfem::Vector &gravity_state,
mfem::Vector &action
) const {
MEAN_FIELD_PROFILE_SCOPE("ReducedGravityFieldOperator::Mult");
using form = utils::blocks::gravity_field_form;
constexpr auto gravity_gradient_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
);
ValidateGravityState(gravity_state);
const mfem::Vector gravity_gradient_true = make_read_only_residual_view(
gravity_state, m_gravity_true_offsets,
gravity_gradient_residual_block
);
const mfem::Vector gravity_potential_true =
make_read_only_residual_view(
gravity_state, m_gravity_true_offsets,
gravity_poisson_residual_block
);
m_gravity_field_operator.ApplyGravityUnknowns(
gravity_gradient_true, gravity_potential_true,
m_gravity_field_geometry_context, action
);
MFEM_VERIFY(
action.Size() == Height(), "ReducedGravityFieldOperator produced "
"an action with the wrong size."
);
}
GravityFieldOperator &
ReducedGravityFieldOperator::GetGravityFieldOperator() noexcept {
return m_gravity_field_operator;
}
const GravityFieldOperator &
ReducedGravityFieldOperator::GetGravityFieldOperator() const noexcept {
return m_gravity_field_operator;
}
context::gravity_field::GravityFieldGeometryContext &
ReducedGravityFieldOperator::GetGeometryContext() noexcept {
return m_gravity_field_geometry_context;
}
const context::gravity_field::GravityFieldGeometryContext &
ReducedGravityFieldOperator::GetGeometryContext() const noexcept {
return m_gravity_field_geometry_context;
}
const mfem::Array<int> &
ReducedGravityFieldOperator::GetGravityTrueOffsets() const noexcept {
return m_gravity_true_offsets;
}
void ReducedGravityFieldOperator::ValidateDisplacement(
const mfem::Vector &displacement
) const {
using form = utils::blocks::gravity_field_form;
constexpr auto displacement_block =
utils::blocks::get_value_block<form>(
utils::blocks::displacement_field.geometry_term
);
const mfem::Array<int> &state_offsets =
m_gravity_field_operator.GetStateTrueOffsets();
const int expected_size =
state_offsets[static_cast<int>(displacement_block) + 1] -
state_offsets[displacement_block];
MFEM_VERIFY(
displacement.Size() == expected_size,
"ReducedGravityFieldOperator received a displacement with the "
"wrong "
"size."
);
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement(i)),
"ReducedGravityFieldOperator received a non-finite "
"displacement "
"value."
);
}
}
void ReducedGravityFieldOperator::ValidateDensity(
const mfem::Vector &density
) const {
using form = utils::blocks::gravity_field_form;
constexpr auto density_block = utils::blocks::get_value_block<form>(
utils::blocks::density_field.mass_term
);
const mfem::Array<int> &state_offsets =
m_gravity_field_operator.GetStateTrueOffsets();
const int expected_size =
state_offsets[static_cast<int>(density_block) + 1] -
state_offsets[density_block];
MFEM_VERIFY(
density.Size() == expected_size,
"ReducedGravityFieldOperator received a density with the wrong "
"size."
);
}
void ReducedGravityFieldOperator::ValidateGravityState(
const mfem::Vector &gravity_state
) const {
MFEM_VERIFY(
gravity_state.Size() == Width(),
"ReducedGravityFieldOperator received "
"a gravity state with the wrong size."
);
}
} // namespace mean_field::operators

View File

@@ -0,0 +1,317 @@
module;
#include <mfem.hpp>
module mean_field;
import :operators.gravity_field_jacobian;
import :operators.kernels.gravity_field;
import :utils.blocks;
namespace {
template <int index>
mfem::Vector make_read_only_value_view(
const mfem::Vector &vector,
const mfem::Array<int> &offsets,
const mean_field::utils::blocks::value_block<index>
) {
const int offset = offsets[index];
const int size = offsets[index + 1] - offset;
return mfem::Vector(
const_cast<mfem::real_t *>(vector.GetData()) + offset, size
);
}
template <int index>
mfem::Vector make_residual_view(
mfem::Vector &vector,
const mfem::Array<int> &offsets,
const mean_field::utils::blocks::residual_block<index>
) {
const int offset = offsets[index];
const int size = offsets[index + 1] - offset;
return mfem::Vector(vector.GetData() + offset, size);
}
template <int index>
int get_block_size(
const mfem::Array<int> &offsets,
const mean_field::utils::blocks::value_block<index>
) {
return offsets[index + 1] - offsets[index];
}
template <int index>
int get_block_size(
const mfem::Array<int> &offsets,
const mean_field::utils::blocks::residual_block<index>
) {
return offsets[index + 1] - offsets[index];
}
void validate_offsets(
const mfem::Array<int> &offsets,
const int block_count,
const char *message
) {
MFEM_VERIFY(offsets.Size() == block_count + 1, message);
MFEM_VERIFY(offsets[0] == 0, "Block offsets must begin at zero.");
for (int i = 0; i < block_count; ++i)
MFEM_VERIFY(
offsets[i + 1] >= offsets[i],
"Block offsets must be nondecreasing."
);
}
void validate_layout(
const mean_field::fem::FEM &f,
const mfem::Array<int> &state_offsets,
const mfem::Array<int> &residual_offsets
) {
using form = mean_field::utils::blocks::gravity_field_form;
constexpr auto density_block =
mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::density_field.mass_term
);
constexpr auto displacement_block =
mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravity_gradient_block =
mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_potential_block =
mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::gravity_field.poisson_term
);
constexpr auto gravity_gradient_residual_block =
mean_field::utils::blocks::get_residual_block<form>(
mean_field::utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_poisson_residual_block =
mean_field::utils::blocks::get_residual_block<form>(
mean_field::utils::blocks::gravity_field.poisson_term
);
validate_offsets(
state_offsets, form::value_block_count,
"Gravity Jacobian state offsets do not match the gravity field "
"form."
);
validate_offsets(
residual_offsets, form::residual_block_count,
"Gravity Jacobian residual offsets do not match the gravity field "
"form."
);
MFEM_VERIFY(
get_block_size(state_offsets, density_block) ==
f.densityFes->GetTrueVSize(),
"The Jacobian density block has the wrong size."
);
MFEM_VERIFY(
get_block_size(state_offsets, displacement_block) ==
f.displacementFes->GetTrueVSize(),
"The Jacobian displacement block has the wrong size."
);
MFEM_VERIFY(
get_block_size(state_offsets, gravity_gradient_block) ==
f.gravityFluxFes->GetTrueVSize(),
"The Jacobian gravity-gradient block has the wrong size."
);
MFEM_VERIFY(
get_block_size(state_offsets, gravity_potential_block) ==
f.gravityPotentialFes->GetTrueVSize(),
"The Jacobian gravity-potential block has the wrong size."
);
MFEM_VERIFY(
get_block_size(residual_offsets, gravity_gradient_residual_block) ==
f.gravityFluxFes->GetTrueVSize(),
"The Jacobian gradient-residual block has the wrong size."
);
MFEM_VERIFY(
get_block_size(residual_offsets, gravity_poisson_residual_block) ==
f.gravityPotentialFes->GetTrueVSize(),
"The Jacobian Poisson-residual block has the wrong size."
);
}
} // namespace
namespace mean_field::operators {
GravityFieldJacobianOperator::GravityFieldJacobianOperator(
fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
const context::gravity_field::GravityFieldLinearizationContext
&linearization_context,
const mfem::Array<int> &state_true_offsets,
const mfem::Array<int> &residual_true_offsets
)
: Operator(
residual_true_offsets.Last(),
state_true_offsets.Last()
),
m_fem(f),
m_domain_mapper(domain_mapper),
m_linearization_context(linearization_context),
m_state_true_offsets(state_true_offsets),
m_residual_true_offsets(residual_true_offsets) {
MFEM_VERIFY(
f.densityFes != nullptr,
"GravityFieldJacobianOperator requires the density finite-element "
"space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldJacobianOperator requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldJacobianOperator requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldJacobianOperator requires the "
"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(
f.quadratureFactory != nullptr,
"GravityFieldJacobianOperator requires the quadrature-rule factory."
);
MFEM_VERIFY(
domain_mapper.GetDimension() == f.mesh->Dimension(),
"GravityFieldJacobianOperator received a domain mapper with the "
"wrong "
"dimension."
);
validate_layout(f, m_state_true_offsets, m_residual_true_offsets);
}
void GravityFieldJacobianOperator::Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_linearization_context.IsPrepared(),
"GravityFieldJacobianOperator requires a prepared linearization "
"context."
);
MFEM_VERIFY(
direction.Size() == Width(),
"GravityFieldJacobianOperator received a direction with the wrong "
"size."
);
using form = utils::blocks::gravity_field_form;
constexpr auto density_block = utils::blocks::get_value_block<form>(
utils::blocks::density_field.mass_term
);
constexpr auto displacement_block =
utils::blocks::get_value_block<form>(
utils::blocks::displacement_field.geometry_term
);
constexpr auto gravity_gradient_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_potential_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.poisson_term
);
constexpr auto gravity_gradient_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
);
const context::gravity_field::GravityFieldGeometryContext
&geometry_context = m_linearization_context.GetGeometryContext();
const mfem::Vector &density = m_linearization_context.GetDensity();
const mfem::Vector &displacement = geometry_context.GetDisplacement();
const mfem::Vector &gravity_gradient =
m_linearization_context.GetGravityGradient();
const mfem::Vector density_direction = make_read_only_value_view(
direction, m_state_true_offsets, density_block
);
const mfem::Vector displacement_direction = make_read_only_value_view(
direction, m_state_true_offsets, displacement_block
);
const mfem::Vector gravity_gradient_direction =
make_read_only_value_view(
direction, m_state_true_offsets, gravity_gradient_block
);
const mfem::Vector gravity_potential_direction =
make_read_only_value_view(
direction, m_state_true_offsets, gravity_potential_block
);
action.SetSize(Height());
action = 0.0;
mfem::Vector gravity_gradient_action = make_residual_view(
action, m_residual_true_offsets, gravity_gradient_residual_block
);
mfem::Vector gravity_poisson_action = make_residual_view(
action, m_residual_true_offsets, gravity_poisson_residual_block
);
mfem::Vector transpose_divergence_action;
mfem::Vector source_action;
mfem::Vector mass_variation_action;
mfem::Vector source_variation_action;
geometry_context.GetMassOperator().Mult(
gravity_gradient_direction, gravity_gradient_action
);
geometry_context.GetSourceOperator().Mult(
density_direction, source_action
);
kernels::apply_mapped_hdiv_mass_variation(
m_fem, m_domain_mapper, gravity_gradient, displacement,
displacement_direction, mass_variation_action
);
kernels::apply_mapped_source_variation(
m_fem, m_domain_mapper, density, displacement,
displacement_direction, source_variation_action
);
transpose_divergence_action.SetSize(gravity_gradient_action.Size());
m_fem.gravityContext.BT->Mult(
gravity_potential_direction, transpose_divergence_action
);
gravity_gradient_action += transpose_divergence_action;
gravity_gradient_action += mass_variation_action;
m_fem.gravityContext.b_form->Mult(
gravity_gradient_direction, gravity_poisson_action
);
gravity_poisson_action -= source_action;
gravity_poisson_action -= source_variation_action;
}
const context::gravity_field::GravityFieldLinearizationContext &
GravityFieldJacobianOperator::GetLinearizationContext() const noexcept {
return m_linearization_context;
}
} // namespace mean_field::operators

View File

@@ -0,0 +1,811 @@
module;
#include <array>
#include <cmath>
#include <limits>
#include <mfem.hpp>
module mean_field;
import :operators.kernels.barotropic_closure;
namespace {
enum class ClosureAction { residual, density, enthalpy };
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"True vector has the wrong size."
);
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
} else {
localVector = trueVector;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(
localVector.Size() == finiteElementSpace.GetVSize(),
"Local vector has the wrong size."
);
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
} else {
trueVector = localVector;
}
}
int get_eos_extra_order(
const mean_field::physics::PolytropicBarotrope &barotrope
) {
const double extraOrder =
(barotrope.polytropic_index() - 1.0) *
static_cast<double>(
mean_field::field::Enthalpy::Scalar::familyOrder
);
MFEM_VERIFY(
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
extraOrder <=
static_cast<double>(std::numeric_limits<int>::max()),
"The EOS effective polynomial order is invalid."
);
return static_cast<int>(std::ceil(extraOrder));
}
const mfem::IntegrationRule &get_eos_rule(
const mean_field::fem::FEM &f,
const mean_field::physics::PolytropicBarotrope &barotrope,
const mfem::FiniteElement &densityElement,
const mfem::FiniteElement &enthalpyElement,
const mfem::ElementTransformation &transformation
) {
using EnthalpyField =
mean_field::field::Field<mean_field::field::Enthalpy>;
MFEM_VERIFY(
densityElement.GetOrder() ==
mean_field::field::Density::Scalar::familyOrder,
"The EOS test element does not match the "
"registered density field."
);
MFEM_VERIFY(
enthalpyElement.GetOrder() ==
mean_field::field::Enthalpy::Scalar::familyOrder,
"The EOS trial element does not match the "
"registered enthalpy field."
);
const mean_field::quadrature::Query query = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EosClosureSource>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(),
std::array<int, 1>{get_eos_extra_order(barotrope)},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto resolution =
f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
resolution.integration_rule != nullptr,
"The quadrature policy did not return an "
"EOS-closure integration rule."
);
return *resolution.integration_rule;
}
void validate_common_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(
f.mesh != nullptr, "The EOS closure kernel requires a mesh."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"The EOS closure kernel requires the density "
"finite-element space."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The EOS closure kernel requires the enthalpy "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The EOS closure kernel requires the displacement "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The EOS closure kernel requires the "
"compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The EOS closure kernel requires the "
"compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The EOS closure kernel requires the quadrature "
"rule factory."
);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The displacement vector has the wrong size."
);
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match "
"the mesh dimension."
);
}
void apply_closure_action(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mean_field::physics::PolytropicBarotrope &barotrope,
const ClosureAction closureAction,
const mfem::Vector *densityInputTrue,
const mfem::Vector *baseEnthalpyTrue,
const mfem::Vector *enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
validate_common_inputs(f, domainMapper, displacementTrue);
if (closureAction == ClosureAction::residual ||
closureAction == ClosureAction::density) {
MFEM_VERIFY(
densityInputTrue != nullptr &&
densityInputTrue->Size() == f.densityFes->GetTrueVSize(),
"The density input has the wrong size."
);
}
if (closureAction == ClosureAction::residual ||
closureAction == ClosureAction::enthalpy) {
MFEM_VERIFY(
baseEnthalpyTrue != nullptr &&
baseEnthalpyTrue->Size() == f.enthalpyFes->GetTrueVSize(),
"The base enthalpy has the wrong size."
);
}
if (closureAction == ClosureAction::enthalpy) {
MFEM_VERIFY(
enthalpyVariationTrue != nullptr &&
enthalpyVariationTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The enthalpy variation has the wrong size."
);
}
mfem::Vector densityInputLocal;
mfem::Vector baseEnthalpyLocal;
mfem::Vector enthalpyVariationLocal;
mfem::Vector displacementLocal;
if (densityInputTrue != nullptr) {
true_to_local(*f.densityFes, *densityInputTrue, densityInputLocal);
}
if (baseEnthalpyTrue != nullptr) {
true_to_local(*f.enthalpyFes, *baseEnthalpyTrue, baseEnthalpyLocal);
}
if (enthalpyVariationTrue != nullptr) {
true_to_local(
*f.enthalpyFes, *enthalpyVariationTrue, enthalpyVariationLocal
);
}
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
mfem::Vector localAction(f.densityFes->GetVSize());
localAction = 0.0;
mean_field::mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementDensityInput;
mfem::Vector elementBaseEnthalpy;
mfem::Vector elementEnthalpyVariation;
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mfem::Vector elementAction;
mfem::Vector densityShape;
mfem::Vector enthalpyShape;
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"The EOS closure kernel received a null "
"element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &densityElement =
*f.densityFes->GetFE(elementId);
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *densityDofTransformation =
f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
if (densityInputTrue != nullptr) {
densityInputLocal.GetSubVector(
densityDofs, elementDensityInput
);
if (densityDofTransformation != nullptr) {
densityDofTransformation->InvTransformPrimal(
elementDensityInput
);
}
}
if (baseEnthalpyTrue != nullptr) {
baseEnthalpyLocal.GetSubVector(
enthalpyDofs, elementBaseEnthalpy
);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementBaseEnthalpy
);
}
}
if (enthalpyVariationTrue != nullptr) {
enthalpyVariationLocal.GetSubVector(
enthalpyDofs, elementEnthalpyVariation
);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementEnthalpyVariation
);
}
}
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
f.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mean_field::mapping::ElementDisplacementData
displacementData = mean_field::mapping::
ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mean_field::mapping::ElementCompactificationData
compactificationData(
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
densityShape.SetSize(densityElement.GetDof());
enthalpyShape.SetSize(enthalpyElement.GetDof());
elementAction.SetSize(densityElement.GetDof());
elementAction = 0.0;
const mfem::IntegrationRule &integrationRule = get_eos_rule(
f, barotrope, densityElement, enthalpyElement, *transformation
);
for (int quadratureIndex = 0;
quadratureIndex < integrationRule.GetNPoints();
++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
mean_field::mapping::VolumeMappingContext mappingContext;
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping failed in the EOS "
"closure kernel. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(mappingStatus)
);
densityElement.CalcShape(integrationPoint, densityShape);
double integrand = 0.0;
if (closureAction == ClosureAction::density) {
integrand = elementDensityInput * densityShape;
} else {
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double baseEnthalpy =
elementBaseEnthalpy * enthalpyShape;
if (closureAction == ClosureAction::residual) {
const double density =
elementDensityInput * densityShape;
integrand =
density -
barotrope.density_from_enthalpy(baseEnthalpy);
} else {
const double enthalpyVariation =
elementEnthalpyVariation * enthalpyShape;
integrand = -barotrope.density_derivative_from_enthalpy(
baseEnthalpy
) *
enthalpyVariation;
}
}
const double weightedIntegrand =
mappingContext.quadrature.weight * integrand;
for (int densityDof = 0; densityDof < densityElement.GetDof();
++densityDof) {
elementAction(densityDof) +=
weightedIntegrand * densityShape(densityDof);
}
}
if (densityDofTransformation != nullptr) {
densityDofTransformation->TransformDual(elementAction);
}
localAction.AddElementVector(densityDofs, elementAction);
}
local_to_true(*f.densityFes, localAction, action);
}
} // namespace
namespace mean_field::operators::kernels {
void apply_barotropic_closure(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &densityTrue,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residual
) {
apply_closure_action(
f, domainMapper, barotrope, ClosureAction::residual, &densityTrue,
&enthalpyTrue, nullptr, displacementTrue, residual
);
}
void apply_barotropic_closure_density_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
apply_closure_action(
f, domainMapper, barotrope, ClosureAction::density,
&densityVariationTrue, nullptr, nullptr, displacementTrue, action
);
}
void apply_barotropic_closure_enthalpy_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
apply_closure_action(
f, domainMapper, barotrope, ClosureAction::enthalpy, nullptr,
&baseEnthalpyTrue, &enthalpyVariationTrue, displacementTrue, action
);
}
void apply_barotropic_closure_displacement_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) {
MFEM_VERIFY(
f.mesh != nullptr, "The barotropic-closure displacement action "
"requires a mesh."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"The barotropic-closure displacement action "
"requires the density finite-element space."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The barotropic-closure displacement action "
"requires the enthalpy finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The barotropic-closure displacement action "
"requires the displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The barotropic-closure displacement action "
"requires the compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The barotropic-closure displacement action "
"requires the compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The barotropic-closure displacement action "
"requires the quadrature-rule factory."
);
MFEM_VERIFY(
baseDensityTrue.Size() == f.densityFes->GetTrueVSize(),
"The base-density vector has the wrong size."
);
MFEM_VERIFY(
baseEnthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The base-enthalpy vector has the wrong size."
);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The displacement vector has the wrong size."
);
MFEM_VERIFY(
displacementVariationTrue.Size() ==
f.displacementFes->GetTrueVSize(),
"The displacement-variation vector has the wrong size."
);
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match the "
"mesh dimension."
);
mfem::Vector baseDensityLocal;
mfem::Vector baseEnthalpyLocal;
mfem::Vector displacementLocal;
mfem::Vector displacementVariationLocal;
true_to_local(*f.densityFes, baseDensityTrue, baseDensityLocal);
true_to_local(*f.enthalpyFes, baseEnthalpyTrue, baseEnthalpyLocal);
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
true_to_local(
*f.displacementFes, displacementVariationTrue,
displacementVariationLocal
);
mfem::Vector localAction(f.densityFes->GetVSize());
localAction = 0.0;
mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseDensity;
mfem::Vector elementBaseEnthalpy;
mfem::Vector elementDisplacement;
mfem::Vector elementDisplacementVariation;
mfem::Vector elementCompactification;
mfem::Vector densityShape;
mfem::Vector enthalpyShape;
mfem::Vector elementAction;
mapping::VolumeMappingContext mappingContext;
mapping::VolumeMappingVariation mappingVariation;
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"The barotropic-closure displacement action "
"received a null element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &densityElement =
*f.densityFes->GetFE(elementId);
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *densityDofTransformation =
f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
baseDensityLocal.GetSubVector(densityDofs, elementBaseDensity);
baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy);
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
displacementVariationLocal.GetSubVector(
displacementDofs, elementDisplacementVariation
);
f.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (densityDofTransformation != nullptr) {
densityDofTransformation->InvTransformPrimal(
elementBaseDensity
);
}
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementBaseEnthalpy
);
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation
);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mapping::ElementDisplacementData displacementVariationData =
mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation
);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
densityShape.SetSize(densityElement.GetDof());
enthalpyShape.SetSize(enthalpyElement.GetDof());
elementAction.SetSize(densityElement.GetDof());
elementAction = 0.0;
const mfem::IntegrationRule &integrationRule = get_eos_rule(
f, barotrope, densityElement, enthalpyElement, *transformation
);
for (int quadraturePoint = 0;
quadraturePoint < integrationRule.GetNPoints();
++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
const mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"The base mapping is invalid while applying "
"the barotropic-closure displacement action. "
"Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(mappingStatus)
);
const mapping::MappingStatus variationStatus =
domainMapper.EvaluateVolumeVariation(
mappingData, displacementVariationData, *transformation,
integrationPoint, mappingContext, workspace,
mappingVariation
);
MFEM_VERIFY(
variationStatus == mapping::MappingStatus::valid,
"The mapping variation is invalid while "
"applying the barotropic-closure "
"displacement action. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(variationStatus)
);
densityElement.CalcShape(integrationPoint, densityShape);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double densityValue = elementBaseDensity * densityShape;
const double enthalpyValue =
elementBaseEnthalpy * enthalpyShape;
const double closureValue =
densityValue -
barotrope.density_from_enthalpy(enthalpyValue);
const double geometryActionValue =
closureValue * mappingVariation.weight_variation;
MFEM_VERIFY(
std::isfinite(closureValue) &&
std::isfinite(geometryActionValue),
"The barotropic-closure displacement action "
"encountered a non-finite quadrature value."
);
elementAction.Add(geometryActionValue, densityShape);
}
if (densityDofTransformation != nullptr) {
densityDofTransformation->TransformDual(elementAction);
}
localAction.AddElementVector(densityDofs, elementAction);
}
local_to_true(*f.densityFes, localAction, action);
}
} // namespace mean_field::operators::kernels

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,775 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <mfem.hpp>
#include <optional>
module mean_field;
import :operators.kernels.hydrostatic_equilibrium;
namespace {
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"True vector has the wrong size."
);
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
} else {
localVector = trueVector;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(
localVector.Size() == finiteElementSpace.GetVSize(),
"Local vector has the wrong size."
);
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
} else {
trueVector = localVector;
}
}
void validate_fem(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper
) {
MFEM_VERIFY(
f.mesh != nullptr, "The hydrostatic kernel requires a mesh."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr, "The hydrostatic kernel requires the "
"enthalpy finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"The hydrostatic kernel requires the "
"gravity-potential finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "The hydrostatic kernel requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The hydrostatic kernel requires the "
"compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The hydrostatic kernel requires the "
"compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The hydrostatic kernel requires the "
"quadrature-rule factory."
);
MFEM_VERIFY(
f.mesh->Dimension() == 3,
"The rigid-rotation hydrostatic kernel "
"currently requires a three-dimensional mesh."
);
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match "
"the mesh dimension."
);
}
const mfem::IntegrationRule &get_hydrostatic_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &enthalpyElement,
const mfem::FiniteElement &potentialElement,
const mfem::ElementTransformation &transformation
) {
using EnthalpyField =
mean_field::field::Field<mean_field::field::Enthalpy>;
MFEM_VERIFY(
enthalpyElement.GetOrder() ==
mean_field::field::Enthalpy::Scalar::familyOrder,
"The hydrostatic test element does not match "
"the registered enthalpy field."
);
MFEM_VERIFY(
potentialElement.GetOrder() ==
mean_field::field::Gravity::Potential::familyOrder,
"The hydrostatic potential element does not "
"match the registered gravity-potential field."
);
const auto enthalpyQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumEnthalpy>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto gravityQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumGravity>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto rotationQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumRotation>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), std::array<int, 1>{2},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto constantQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumConstant>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
int integrationOrder = 0;
const auto update_order = [&f, &transformation, &integrationOrder](
const mean_field::quadrature::Query &query
) {
const auto rule = f.quadratureFactory->get(
query, transformation.GetGeometryType()
);
MFEM_VERIFY(
rule.integration_rule != nullptr,
"The quadrature policy did not return "
"a hydrostatic-equilibrium rule."
);
integrationOrder =
std::max(integrationOrder, rule.resolution.order);
};
update_order(enthalpyQuery);
update_order(gravityQuery);
update_order(rotationQuery);
update_order(constantQuery);
return mfem::IntRules.Get(
transformation.GetGeometryType(), integrationOrder
);
}
struct HydrostaticAssemblyRequest {
const mean_field::physics::RigidRotation *rotation{nullptr};
const mfem::Vector *baseEnthalpyTrue{nullptr};
const mfem::Vector *basePotentialTrue{nullptr};
const mfem::Vector *enthalpyVariationTrue{nullptr};
const mfem::Vector *potentialVariationTrue{nullptr};
const mfem::Vector *displacementVariationTrue{nullptr};
double bernoulliConstant{0.0};
double constantVariation{0.0};
bool buildResidual{false};
};
void assemble_hydrostatic_form(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &displacementTrue,
const HydrostaticAssemblyRequest &request,
mfem::Vector &result
) {
validate_fem(f, domainMapper);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The hydrostatic displacement vector has "
"the wrong size."
);
MFEM_VERIFY(
std::isfinite(request.bernoulliConstant),
"The Bernoulli constant is non-finite."
);
MFEM_VERIFY(
std::isfinite(request.constantVariation),
"The Bernoulli-constant variation is non-finite."
);
const bool requiresBaseState =
request.buildResidual ||
request.displacementVariationTrue != nullptr;
if (requiresBaseState) {
MFEM_VERIFY(
request.rotation != nullptr,
"The hydrostatic residual or geometry "
"action requires the rotation model."
);
MFEM_VERIFY(
request.baseEnthalpyTrue != nullptr,
"The hydrostatic residual or geometry "
"action requires the base enthalpy."
);
MFEM_VERIFY(
request.basePotentialTrue != nullptr,
"The hydrostatic residual or geometry "
"action requires the base potential."
);
}
if (request.baseEnthalpyTrue != nullptr) {
MFEM_VERIFY(
request.baseEnthalpyTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The base enthalpy vector has the wrong size."
);
}
if (request.basePotentialTrue != nullptr) {
MFEM_VERIFY(
request.basePotentialTrue->Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"The base potential vector has the wrong size."
);
}
if (request.enthalpyVariationTrue != nullptr) {
MFEM_VERIFY(
request.enthalpyVariationTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The enthalpy variation has the wrong size."
);
}
if (request.potentialVariationTrue != nullptr) {
MFEM_VERIFY(
request.potentialVariationTrue->Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"The potential variation has the wrong size."
);
}
if (request.displacementVariationTrue != nullptr) {
MFEM_VERIFY(
request.displacementVariationTrue->Size() ==
f.displacementFes->GetTrueVSize(),
"The displacement variation has the wrong size."
);
}
mfem::Vector displacementLocal;
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
mfem::Vector baseEnthalpyLocal;
mfem::Vector basePotentialLocal;
mfem::Vector enthalpyVariationLocal;
mfem::Vector potentialVariationLocal;
mfem::Vector displacementVariationLocal;
if (request.baseEnthalpyTrue != nullptr) {
true_to_local(
*f.enthalpyFes, *request.baseEnthalpyTrue, baseEnthalpyLocal
);
}
if (request.basePotentialTrue != nullptr) {
true_to_local(
*f.gravityPotentialFes, *request.basePotentialTrue,
basePotentialLocal
);
}
if (request.enthalpyVariationTrue != nullptr) {
true_to_local(
*f.enthalpyFes, *request.enthalpyVariationTrue,
enthalpyVariationLocal
);
}
if (request.potentialVariationTrue != nullptr) {
true_to_local(
*f.gravityPotentialFes, *request.potentialVariationTrue,
potentialVariationLocal
);
}
if (request.displacementVariationTrue != nullptr) {
true_to_local(
*f.displacementFes, *request.displacementVariationTrue,
displacementVariationLocal
);
}
mfem::Vector localResult(f.enthalpyFes->GetVSize());
localResult = 0.0;
mean_field::mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mfem::Array<int> enthalpyDofs;
mfem::Array<int> potentialDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseEnthalpy;
mfem::Vector elementBasePotential;
mfem::Vector elementEnthalpyVariation;
mfem::Vector elementPotentialVariation;
mfem::Vector elementDisplacement;
mfem::Vector elementDisplacementVariation;
mfem::Vector elementCompactification;
mfem::Vector elementResult;
mfem::Vector enthalpyShape;
mfem::Vector potentialShape;
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"The hydrostatic kernel received a null "
"element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &potentialElement =
*f.gravityPotentialFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *potentialDofTransformation =
f.gravityPotentialFes->GetElementDofs(elementId, potentialDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
f.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (request.baseEnthalpyTrue != nullptr) {
baseEnthalpyLocal.GetSubVector(
enthalpyDofs, elementBaseEnthalpy
);
}
if (request.basePotentialTrue != nullptr) {
basePotentialLocal.GetSubVector(
potentialDofs, elementBasePotential
);
}
if (request.enthalpyVariationTrue != nullptr) {
enthalpyVariationLocal.GetSubVector(
enthalpyDofs, elementEnthalpyVariation
);
}
if (request.potentialVariationTrue != nullptr) {
potentialVariationLocal.GetSubVector(
potentialDofs, elementPotentialVariation
);
}
if (request.displacementVariationTrue != nullptr) {
displacementVariationLocal.GetSubVector(
displacementDofs, elementDisplacementVariation
);
}
if (enthalpyDofTransformation != nullptr) {
if (request.baseEnthalpyTrue != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementBaseEnthalpy
);
}
if (request.enthalpyVariationTrue != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementEnthalpyVariation
);
}
}
if (potentialDofTransformation != nullptr) {
if (request.basePotentialTrue != nullptr) {
potentialDofTransformation->InvTransformPrimal(
elementBasePotential
);
}
if (request.potentialVariationTrue != nullptr) {
potentialDofTransformation->InvTransformPrimal(
elementPotentialVariation
);
}
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
if (request.displacementVariationTrue != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation
);
}
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mean_field::mapping::ElementDisplacementData
displacementData = mean_field::mapping::
ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mean_field::mapping::ElementCompactificationData
compactificationData(
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
std::optional<mean_field::mapping::ElementDisplacementData>
displacementVariationData;
if (request.displacementVariationTrue != nullptr) {
displacementVariationData.emplace(
mean_field::mapping::
ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation
)
);
}
elementResult.SetSize(enthalpyElement.GetDof());
elementResult = 0.0;
enthalpyShape.SetSize(enthalpyElement.GetDof());
potentialShape.SetSize(potentialElement.GetDof());
const mfem::IntegrationRule &integrationRule = get_hydrostatic_rule(
f, enthalpyElement, potentialElement, *transformation
);
for (int quadraturePoint = 0;
quadraturePoint < integrationRule.GetNPoints();
++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
mean_field::mapping::VolumeMappingContext mappingContext;
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"The base mapping is invalid in the "
"hydrostatic kernel. Element: "
<< elementId
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(mappingStatus)
);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
potentialElement.CalcShape(integrationPoint, potentialShape);
double baseIntegrand = 0.0;
if (requiresBaseState) {
const double enthalpyValue =
elementBaseEnthalpy * enthalpyShape;
const double potentialValue =
elementBasePotential * potentialShape;
const double rotationPotential =
request.rotation->potential(
mappingContext.mapping.physical_position
);
baseIntegrand = enthalpyValue + potentialValue -
rotationPotential -
request.bernoulliConstant;
}
if (request.buildResidual) {
elementResult.Add(
mappingContext.quadrature.weight * baseIntegrand,
enthalpyShape
);
continue;
}
double materialVariation = -request.constantVariation;
if (request.enthalpyVariationTrue != nullptr) {
materialVariation +=
elementEnthalpyVariation * enthalpyShape;
}
if (request.potentialVariationTrue != nullptr) {
materialVariation +=
elementPotentialVariation * potentialShape;
}
double weightedVariation =
mappingContext.quadrature.weight * materialVariation;
if (request.displacementVariationTrue != nullptr) {
mean_field::mapping::VolumeMappingVariation
mappingVariation;
const mean_field::mapping::MappingStatus variationStatus =
domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData,
*transformation, integrationPoint, mappingContext,
workspace, mappingVariation
);
MFEM_VERIFY(
variationStatus ==
mean_field::mapping::MappingStatus::valid,
"The mapping variation is invalid "
"in the hydrostatic kernel."
);
const double rotationVariation =
request.rotation->potential_directional_derivative(
mappingContext.mapping.physical_position,
mappingVariation.mapping.physical_position_variation
);
weightedVariation +=
baseIntegrand * mappingVariation.weight_variation -
rotationVariation * mappingContext.quadrature.weight;
}
elementResult.Add(weightedVariation, enthalpyShape);
}
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->TransformDual(elementResult);
}
localResult.AddElementVector(enthalpyDofs, elementResult);
}
local_to_true(*f.enthalpyFes, localResult, result);
}
} // namespace
namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &potentialTrue,
const mfem::Vector &displacementTrue,
const double bernoulliConstant,
mfem::Vector &residual
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
request.baseEnthalpyTrue = &enthalpyTrue;
request.basePotentialTrue = &potentialTrue;
request.bernoulliConstant = bernoulliConstant;
request.buildResidual = true;
assemble_hydrostatic_form(
f, domainMapper, displacementTrue, request, residual
);
}
void apply_hydrostatic_equilibrium_enthalpy_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.enthalpyVariationTrue = &enthalpyVariationTrue;
assemble_hydrostatic_form(
f, domainMapper, displacementTrue, request, action
);
}
void apply_hydrostatic_equilibrium_potential_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &potentialVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.potentialVariationTrue = &potentialVariationTrue;
assemble_hydrostatic_form(
f, domainMapper, displacementTrue, request, action
);
}
void apply_hydrostatic_equilibrium_constant_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const double constantVariation,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.constantVariation = constantVariation;
assemble_hydrostatic_form(
f, domainMapper, displacementTrue, request, action
);
}
void apply_hydrostatic_equilibrium_displacement_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseDisplacementTrue,
const double baseBernoulliConstant,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
request.baseEnthalpyTrue = &baseEnthalpyTrue;
request.basePotentialTrue = &basePotentialTrue;
request.displacementVariationTrue = &displacementVariationTrue;
request.bernoulliConstant = baseBernoulliConstant;
assemble_hydrostatic_form(
f, domainMapper, baseDisplacementTrue, request, action
);
}
void apply_hydrostatic_equilibrium_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseDisplacementTrue,
const double baseBernoulliConstant,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &potentialVariationTrue,
const double constantVariation,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
request.baseEnthalpyTrue = &baseEnthalpyTrue;
request.basePotentialTrue = &basePotentialTrue;
request.enthalpyVariationTrue = &enthalpyVariationTrue;
request.potentialVariationTrue = &potentialVariationTrue;
request.displacementVariationTrue = &displacementVariationTrue;
request.bernoulliConstant = baseBernoulliConstant;
request.constantVariation = constantVariation;
assemble_hydrostatic_form(
f, domainMapper, baseDisplacementTrue, request, action
);
}
} // namespace mean_field::operators::kernels

View File

@@ -0,0 +1,464 @@
module;
#include <array>
#include <cmath>
#include <limits>
#include <mfem.hpp>
module mean_field;
import :operators.kernels.pressure_force;
namespace {
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"The pressure-force true vector has the wrong size."
);
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
} else {
localVector = trueVector;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(
localVector.Size() == finiteElementSpace.GetVSize(),
"The pressure-force local vector has the wrong size."
);
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
} else {
trueVector = localVector;
}
}
[[nodiscard]] int vector_dof_index(
const mfem::Ordering::Type ordering,
const int scalarDof,
const int component,
const int scalarDofCount,
const int dimension
) {
if (ordering == mfem::Ordering::byNODES) {
return scalarDof + component * scalarDofCount;
}
if (ordering == mfem::Ordering::byVDIM) {
return component + scalarDof * dimension;
}
MFEM_ABORT("The displacement space uses an unsupported ordering.");
return -1;
}
[[nodiscard]] int get_pressure_extra_order(
const mean_field::physics::PolytropicBarotrope &barotrope
) {
/*
* Pressure has the enthalpy dependence
*
* P(h) proportional to h^(n + 1).
*
* The registered enthalpy operand already contributes one factor
* of the enthalpy polynomial order. The remaining dynamic
* contribution is therefore n times that order.
*/
const double extraOrder =
barotrope.polytropic_index() *
static_cast<double>(
mean_field::field::Enthalpy::Scalar::familyOrder
);
MFEM_VERIFY(
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
extraOrder <=
static_cast<double>(std::numeric_limits<int>::max()),
"The pressure EOS effective polynomial order is invalid."
);
return static_cast<int>(std::ceil(extraOrder));
}
[[nodiscard]] const mfem::IntegrationRule &get_pressure_force_rule(
const mean_field::fem::FEM &f,
const mean_field::physics::PolytropicBarotrope &barotrope,
const mfem::FiniteElement &enthalpyElement,
const mfem::FiniteElement &displacementElement,
const mfem::ElementTransformation &transformation
) {
using EnthalpyField =
mean_field::field::Field<mean_field::field::Enthalpy>;
MFEM_VERIFY(
enthalpyElement.GetOrder() ==
mean_field::field::Enthalpy::Scalar::familyOrder,
"The pressure-force enthalpy element does not match the "
"registered enthalpy field."
);
MFEM_VERIFY(
displacementElement.GetOrder() ==
mean_field::field::Displacement::Vector::familyOrder,
"The pressure-force test element does not match the "
"registered displacement field."
);
const mean_field::quadrature::Query query = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(),
std::array<int, 1>{get_pressure_extra_order(barotrope)},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule =
f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
rule.integration_rule != nullptr,
"The quadrature policy did not return a pressure-force "
"integration rule."
);
return *rule.integration_rule;
}
void validate_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(
f.mesh != nullptr, "The pressure-force kernel requires a mesh."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The pressure-force kernel requires the enthalpy "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The pressure-force kernel requires the displacement "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The pressure-force kernel requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The pressure-force kernel requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The pressure-force kernel requires the quadrature "
"rule factory."
);
MFEM_VERIFY(
enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The pressure-force enthalpy vector has the wrong size."
);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The pressure-force displacement vector has the wrong size."
);
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The pressure-force domain-mapper dimension does not match "
"the mesh dimension."
);
MFEM_VERIFY(
f.displacementFes->GetVDim() == f.mesh->Dimension(),
"The displacement vector dimension does not match the "
"mesh dimension."
);
MFEM_VERIFY(
f.displacementFes->GetOrdering() == mfem::Ordering::byNODES,
"The pressure-force kernel requires the registered byNODES "
"displacement ordering."
);
}
} // namespace
namespace mean_field::operators::kernels {
void apply_pressure_force_residual(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residualTrue
) {
validate_inputs(f, domainMapper, enthalpyTrue, displacementTrue);
mfem::Vector enthalpyLocal;
mfem::Vector displacementLocal;
true_to_local(*f.enthalpyFes, enthalpyTrue, enthalpyLocal);
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
mfem::Vector localResidual(f.displacementFes->GetVSize());
localResidual = 0.0;
mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementEnthalpy;
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mfem::Vector elementResidual;
mfem::Vector enthalpyShape;
mfem::DenseMatrix displacementDShapeReference;
mfem::DenseMatrix displacementDShapePhysical;
mapping::VolumeMappingContext mappingContext;
const int dimension = f.mesh->Dimension();
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
const mfem::Ordering::Type displacementOrdering =
f.displacementFes->GetOrdering();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"The pressure-force kernel received a null element "
"transformation."
);
/*
* Skip vacuum before constructing or evaluating any mapping
* data for the element.
*/
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
enthalpyLocal.GetSubVector(enthalpyDofs, elementEnthalpy);
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
f.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy);
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
const int scalarDisplacementDofCount = displacementElement.GetDof();
MFEM_VERIFY(
displacementDofs.Size() ==
scalarDisplacementDofCount * dimension,
"The pressure-force element displacement vector has "
"the wrong size."
);
enthalpyShape.SetSize(enthalpyElement.GetDof());
displacementDShapeReference.SetSize(
scalarDisplacementDofCount, dimension
);
displacementDShapePhysical.SetSize(
scalarDisplacementDofCount, dimension
);
elementResidual.SetSize(displacementDofs.Size());
elementResidual = 0.0;
const mfem::IntegrationRule &integrationRule =
get_pressure_force_rule(
f, barotrope, enthalpyElement, displacementElement,
*transformation
);
for (int quadratureIndex = 0;
quadratureIndex < integrationRule.GetNPoints();
++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
const mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"Stateless mapping failed in the pressure-force "
"kernel. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(mappingStatus)
);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double enthalpyValue = elementEnthalpy * enthalpyShape;
const double pressureValue =
barotrope.pressure_from_enthalpy(enthalpyValue);
displacementElement.CalcDShape(
integrationPoint, displacementDShapeReference
);
/*
* Row i of DShape is grad_reference(N_i). Multiplication
* by the complete inverse element Jacobian gives
*
* grad_physical(N_i)
* = grad_reference(N_i) J^{-1}.
*/
mfem::Mult(
displacementDShapeReference,
mappingContext.quadrature.J_inv, displacementDShapePhysical
);
const double weightedPressure =
pressureValue * mappingContext.quadrature.weight;
MFEM_VERIFY(
std::isfinite(pressureValue) &&
std::isfinite(weightedPressure),
"The pressure-force kernel encountered a non-finite "
"quadrature value."
);
/*
* For the vector basis N_i e_c,
*
* div(N_i e_c) = partial_c N_i.
*
* Therefore
*
* R_(i,c)
* = -integral P partial_c N_i dV.
*/
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount;
++scalarDof) {
for (int component = 0; component < dimension;
++component) {
const int vectorDof = vector_dof_index(
displacementOrdering, scalarDof, component,
scalarDisplacementDofCount, dimension
);
elementResidual(vectorDof) -=
weightedPressure *
displacementDShapePhysical(scalarDof, component);
}
}
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->TransformDual(elementResidual);
}
localResidual.AddElementVector(displacementDofs, elementResidual);
}
local_to_true(*f.displacementFes, localResidual, residualTrue);
}
} // namespace mean_field::operators::kernels

View File

@@ -0,0 +1,718 @@
module;
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_barotropic_closure;
namespace {
int get_density_size(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.densityFes != nullptr,
"PreparedBarotropicClosureOperator requires the "
"density finite-element space."
);
return f.densityFes->GetTrueVSize();
}
int get_enthalpy_size(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"PreparedBarotropicClosureOperator requires the "
"enthalpy finite-element space."
);
return f.enthalpyFes->GetTrueVSize();
}
void validate_finite_vector(
const mfem::Vector &vector,
const char *message
) {
for (int i = 0; i < vector.Size(); ++i) {
MFEM_VERIFY(std::isfinite(vector(i)), message);
}
}
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"True vector has the wrong size."
);
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
} else {
localVector = trueVector;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(
localVector.Size() == finiteElementSpace.GetVSize(),
"Local vector has the wrong size."
);
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
} else {
trueVector = localVector;
}
}
int get_eos_extra_order(
const mean_field::physics::PolytropicBarotrope &barotrope
) {
const double extraOrder =
(barotrope.polytropic_index() - 1.0) *
static_cast<double>(
mean_field::field::Enthalpy::Scalar::familyOrder
);
MFEM_VERIFY(
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
extraOrder <=
static_cast<double>(std::numeric_limits<int>::max()),
"The EOS effective polynomial order is invalid."
);
return static_cast<int>(std::ceil(extraOrder));
}
const mfem::IntegrationRule &get_eos_rule(
const mean_field::fem::FEM &f,
const mean_field::physics::PolytropicBarotrope &barotrope,
const mfem::FiniteElement &densityElement,
const mfem::FiniteElement &enthalpyElement,
const mfem::ElementTransformation &transformation
) {
using EnthalpyField =
mean_field::field::Field<mean_field::field::Enthalpy>;
MFEM_VERIFY(
densityElement.GetOrder() ==
mean_field::field::Density::Scalar::familyOrder,
"The prepared EOS test element does not match "
"the registered density field."
);
MFEM_VERIFY(
enthalpyElement.GetOrder() ==
mean_field::field::Enthalpy::Scalar::familyOrder,
"The prepared EOS trial element does not match "
"the registered enthalpy field."
);
const mean_field::quadrature::Query query = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EosClosureSource>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(),
std::array<int, 1>{get_eos_extra_order(barotrope)},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto resolution =
f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
resolution.integration_rule != nullptr,
"The quadrature policy did not return a prepared "
"EOS-closure integration rule."
);
return *resolution.integration_rule;
}
} // namespace
namespace mean_field::operators {
PreparedBarotropicClosureOperator::PreparedBarotropicClosureOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope
)
: mfem::Operator(
f.densityFes->GetTrueVSize(),
f.densityFes->GetTrueVSize() + f.enthalpyFes->GetTrueVSize() +
f.displacementFes->GetTrueVSize()
),
m_fem(f),
m_domainMapper(domainMapper),
m_barotrope(barotrope),
m_densitySize(f.densityFes->GetTrueVSize()),
m_enthalpySize(f.enthalpyFes->GetTrueVSize()) {
MFEM_VERIFY(
m_fem.densityFes != nullptr,
"PreparedBarotropicClosureOperator requires "
"a density finite-element space."
);
MFEM_VERIFY(
m_fem.enthalpyFes != nullptr,
"PreparedBarotropicClosureOperator requires "
"an enthalpy finite-element space."
);
MFEM_VERIFY(
m_fem.displacementFes != nullptr,
"PreparedBarotropicClosureOperator requires "
"a displacement finite-element space."
);
}
void PreparedBarotropicClosureOperator::Prepare(
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(
baseDensityTrue.Size() == m_densitySize,
"PreparedBarotropicClosureOperator received a "
"base-density vector with the wrong size."
);
MFEM_VERIFY(
baseEnthalpyTrue.Size() == m_enthalpySize,
"PreparedBarotropicClosureOperator received a "
"base-enthalpy vector with the wrong size."
);
MFEM_VERIFY(
displacementTrue.Size() == m_fem.displacementFes->GetTrueVSize(),
"PreparedBarotropicClosureOperator received a "
"displacement vector with the wrong size."
);
MFEM_VERIFY(
baseDensityTrue.Size() == m_fem.densityFes->GetTrueVSize(),
"The base density true vector has the wrong size."
);
MFEM_VERIFY(
baseEnthalpyTrue.Size() == m_fem.enthalpyFes->GetTrueVSize(),
"The base enthalpy true vector has the wrong size."
);
MFEM_VERIFY(
displacementTrue.Size() == m_fem.displacementFes->GetTrueVSize(),
"The base displacement true vector has the wrong size."
);
validate_finite_vector(
baseDensityTrue, "PreparedBarotropicClosureOperator received a "
"non-finite base-density value."
);
validate_finite_vector(
baseEnthalpyTrue, "PreparedBarotropicClosureOperator received a "
"non-finite base-enthalpy value."
);
validate_finite_vector(
displacementTrue, "PreparedBarotropicClosureOperator received a "
"non-finite displacement value."
);
m_isPrepared = false;
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
mfem::Vector baseDensityLocal;
mfem::Vector baseEnthalpyLocal;
mfem::Vector displacementLocal;
true_to_local(*m_fem.densityFes, baseDensityTrue, baseDensityLocal);
true_to_local(*m_fem.enthalpyFes, baseEnthalpyTrue, baseEnthalpyLocal);
true_to_local(
*m_fem.displacementFes, displacementTrue, displacementLocal
);
mapping::DomainMapperStateless::Workspace workspace(
m_fem.mesh->Dimension()
);
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseDensity;
mfem::Vector elementBaseEnthalpy;
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mfem::Vector densityShape;
mfem::Vector enthalpyShape;
const int vacuumAttribute = m_domainMapper.GetVacuumElementAttribute();
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"PreparedBarotropicClosureOperator received "
"a null element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
data.densityDofTransformation =
m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
data.enthalpyDofTransformation =
m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(
elementId, displacementDofs
);
mfem::DofTransformation *compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
baseDensityLocal.GetSubVector(data.densityDofs, elementBaseDensity);
baseEnthalpyLocal.GetSubVector(
data.enthalpyDofs, elementBaseEnthalpy
);
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
m_fem.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(
elementBaseDensity
);
}
if (data.enthalpyDofTransformation != nullptr) {
data.enthalpyDofTransformation->InvTransformPrimal(
elementBaseEnthalpy
);
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mfem::FiniteElement &densityElement =
*m_fem.densityFes->GetFE(elementId);
const mfem::FiniteElement &enthalpyElement =
*m_fem.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*m_fem.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*m_fem.compactificationFes->GetFE(elementId);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
const mfem::IntegrationRule &integrationRule = get_eos_rule(
m_fem, m_barotrope, densityElement, enthalpyElement,
*transformation
);
const int quadraturePointCount = integrationRule.GetNPoints();
const int densityDofCount = densityElement.GetDof();
const int enthalpyDofCount = enthalpyElement.GetDof();
data.densityBasis.SetSize(quadraturePointCount, densityDofCount);
data.enthalpyBasis.SetSize(quadraturePointCount, enthalpyDofCount);
data.weightedResidual.SetSize(quadraturePointCount);
data.quadratureWeights.SetSize(quadraturePointCount);
data.weightedEnthalpyDerivative.SetSize(quadraturePointCount);
densityShape.SetSize(densityDofCount);
enthalpyShape.SetSize(enthalpyDofCount);
for (int quadraturePoint = 0;
quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
mapping::VolumeMappingContext mappingContext;
const mapping::MappingStatus mappingStatus =
m_domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"Stateless mapping failed while preparing "
"the barotropic closure operator. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(mappingStatus)
);
densityElement.CalcShape(integrationPoint, densityShape);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
for (int densityDof = 0; densityDof < densityDofCount;
++densityDof) {
data.densityBasis(quadraturePoint, densityDof) =
densityShape(densityDof);
}
for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount;
++enthalpyDof) {
data.enthalpyBasis(quadraturePoint, enthalpyDof) =
enthalpyShape(enthalpyDof);
}
const double density = elementBaseDensity * densityShape;
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
const double quadratureWeight =
mappingContext.quadrature.weight;
const double eosDensity =
m_barotrope.density_from_enthalpy(enthalpy);
const double enthalpyDerivative =
m_barotrope.density_derivative_from_enthalpy(enthalpy);
MFEM_VERIFY(
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 &&
std::isfinite(eosDensity) &&
std::isfinite(enthalpyDerivative),
"PreparedBarotropicClosureOperator "
"encountered invalid quadrature data."
);
data.quadratureWeights(quadraturePoint) = quadratureWeight;
data.weightedResidual(quadraturePoint) =
quadratureWeight * (density - eosDensity);
data.weightedEnthalpyDerivative(quadraturePoint) =
quadratureWeight * enthalpyDerivative;
}
}
MFEM_VERIFY(
!m_elements.empty(), "PreparedBarotropicClosureOperator found no "
"stellar elements."
);
m_baseDensityTrue = baseDensityTrue;
m_baseEnthalpyTrue = baseEnthalpyTrue;
m_baseDisplacementTrue = displacementTrue;
m_isPrepared = true;
++m_preparationCount;
}
void PreparedBarotropicClosureOperator::BuildResidual(
mfem::Vector &residual
) const {
MFEM_VERIFY(
m_isPrepared, "PreparedBarotropicClosureOperator must be "
"prepared before BuildResidual is called."
);
mfem::Vector localResidual(m_fem.densityFes->GetVSize());
localResidual = 0.0;
mfem::Vector elementResidual;
for (const ElementPAData &data : m_elements) {
elementResidual.SetSize(data.densityDofs.Size());
data.densityBasis.MultTranspose(
data.weightedResidual, elementResidual
);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->TransformDual(elementResidual);
}
localResidual.AddElementVector(data.densityDofs, elementResidual);
}
local_to_true(*m_fem.densityFes, localResidual, residual);
}
void PreparedBarotropicClosureOperator::Mult(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
densityVariationTrue.Size() == m_densitySize,
"The density-variation true vector has "
"the wrong size."
);
MFEM_VERIFY(
enthalpyVariationTrue.Size() == m_enthalpySize,
"The enthalpy-variation true vector has "
"the wrong size."
);
MFEM_VERIFY(
displacementVariationTrue.Size() ==
m_fem.displacementFes->GetTrueVSize(),
"The displacement-variation true vector has "
"the wrong size."
);
Mult(densityVariationTrue, enthalpyVariationTrue, action);
mfem::Vector displacementAction;
kernels::apply_barotropic_closure_displacement_action(
m_fem, m_domainMapper, m_barotrope, m_baseDensityTrue,
m_baseEnthalpyTrue, m_baseDisplacementTrue,
displacementVariationTrue, displacementAction
);
MFEM_VERIFY(
displacementAction.Size() == m_densitySize,
"The barotropic-closure displacement action "
"returned a vector with the wrong size."
);
action += displacementAction;
}
void PreparedBarotropicClosureOperator::Mult(
const mfem::Vector &combinedVariation,
mfem::Vector &action
) const {
VerifyPrepared();
const int displacementSize = m_fem.displacementFes->GetTrueVSize();
const int combinedSize =
m_densitySize + m_enthalpySize + displacementSize;
MFEM_VERIFY(
combinedVariation.Size() == combinedSize,
"The combined barotropic-closure variation "
"vector has the wrong size. Expected "
<< combinedSize << " entries but received "
<< combinedVariation.Size() << "."
);
mfem::real_t *combinedData =
const_cast<mfem::real_t *>(combinedVariation.HostRead());
const mfem::Vector densityVariationTrue(combinedData, m_densitySize);
const mfem::Vector enthalpyVariationTrue(
combinedData + m_densitySize, m_enthalpySize
);
const mfem::Vector displacementVariationTrue(
combinedData + m_densitySize + m_enthalpySize, displacementSize
);
Mult(
densityVariationTrue, enthalpyVariationTrue,
displacementVariationTrue, action
);
}
void PreparedBarotropicClosureOperator::Mult(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &enthalpyVariationTrue,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_isPrepared, "PreparedBarotropicClosureOperator must be "
"prepared before Mult is called."
);
MFEM_VERIFY(
densityVariationTrue.Size() == m_densitySize,
"PreparedBarotropicClosureOperator received a "
"density variation with the wrong size."
);
MFEM_VERIFY(
enthalpyVariationTrue.Size() == m_enthalpySize,
"PreparedBarotropicClosureOperator received an "
"enthalpy variation with the wrong size."
);
mfem::Vector densityVariationLocal;
mfem::Vector enthalpyVariationLocal;
true_to_local(
*m_fem.densityFes, densityVariationTrue, densityVariationLocal
);
true_to_local(
*m_fem.enthalpyFes, enthalpyVariationTrue, enthalpyVariationLocal
);
mfem::Vector localAction(m_fem.densityFes->GetVSize());
localAction = 0.0;
mfem::Vector elementDensityVariation;
mfem::Vector elementEnthalpyVariation;
mfem::Vector quadratureDensityVariation;
mfem::Vector quadratureEnthalpyVariation;
mfem::Vector quadratureAction;
mfem::Vector elementAction;
for (const ElementPAData &data : m_elements) {
densityVariationLocal.GetSubVector(
data.densityDofs, elementDensityVariation
);
enthalpyVariationLocal.GetSubVector(
data.enthalpyDofs, elementEnthalpyVariation
);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(
elementDensityVariation
);
}
if (data.enthalpyDofTransformation != nullptr) {
data.enthalpyDofTransformation->InvTransformPrimal(
elementEnthalpyVariation
);
}
quadratureDensityVariation.SetSize(data.quadratureWeights.Size());
quadratureEnthalpyVariation.SetSize(data.quadratureWeights.Size());
quadratureAction.SetSize(data.quadratureWeights.Size());
data.densityBasis.Mult(
elementDensityVariation, quadratureDensityVariation
);
data.enthalpyBasis.Mult(
elementEnthalpyVariation, quadratureEnthalpyVariation
);
for (int quadraturePoint = 0;
quadraturePoint < quadratureAction.Size(); ++quadraturePoint) {
quadratureAction(quadraturePoint) =
data.quadratureWeights(quadraturePoint) *
quadratureDensityVariation(quadraturePoint) -
data.weightedEnthalpyDerivative(quadraturePoint) *
quadratureEnthalpyVariation(quadraturePoint);
}
elementAction.SetSize(data.densityDofs.Size());
data.densityBasis.MultTranspose(quadratureAction, elementAction);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->TransformDual(elementAction);
}
localAction.AddElementVector(data.densityDofs, elementAction);
}
local_to_true(*m_fem.densityFes, localAction, action);
}
bool PreparedBarotropicClosureOperator::IsPrepared() const noexcept {
return m_isPrepared;
}
std::uint64_t
PreparedBarotropicClosureOperator::GetPreparationCount() const noexcept {
return m_preparationCount;
}
int PreparedBarotropicClosureOperator::GetDensitySize() const noexcept {
return m_densitySize;
}
int PreparedBarotropicClosureOperator::GetEnthalpySize() const noexcept {
return m_enthalpySize;
}
void PreparedBarotropicClosureOperator::VerifyPrepared() const {
MFEM_VERIFY(
m_isPrepared, "PreparedBarotropicClosureOperator must be "
"prepared before this operation is called."
);
}
} // namespace mean_field::operators

View File

@@ -0,0 +1,617 @@
module;
#include <cmath>
#include <cstdint>
#include <memory>
#include <mfem.hpp>
#include <numbers>
module mean_field;
import :operators.prepared_gravity_source;
namespace {
int get_operator_height(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space."
);
return f.gravityPotentialFes->GetTrueVSize();
}
int get_operator_width(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.densityFes != nullptr,
"PreparedMappedGravitySourceOperator requires the density "
"finite-element space."
);
return f.densityFes->GetTrueVSize();
}
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;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &local_vector,
mfem::Vector &true_vector
) {
MFEM_VERIFY(
local_vector.Size() == finite_element_space.GetVSize(),
"Local vector has the wrong size."
);
true_vector.SetSize(finite_element_space.GetTrueVSize());
true_vector = 0.0;
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector);
} else {
true_vector = local_vector;
}
}
const mfem::IntegrationRule &get_source_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &density_element,
const mfem::FiniteElement &potential_element,
const mfem::ElementTransformation &transformation
) {
using GravityField =
mean_field::field::Field<mean_field::field::Gravity>;
MFEM_VERIFY(
density_element.GetOrder() ==
mean_field::field::Density::Scalar::familyOrder,
"The prepared source trial element does not match the registered "
"density field."
);
MFEM_VERIFY(
potential_element.GetOrder() ==
mean_field::field::Gravity::Potential::familyOrder,
"The prepared source test element does not match the registered "
"gravity potential."
);
const mean_field::quadrature::Query query = GravityField::make_query<
mean_field::field::Gravity::Form::SourceProjection>(
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;
}
class FrozenMappedGravitySourceCoefficient final
: public mfem::Coefficient {
public:
FrozenMappedGravitySourceCoefficient(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domain_mapper,
const mfem::Vector &displacement_true
)
: m_fem(f),
m_domain_mapper(domain_mapper),
m_workspace(domain_mapper.GetDimension()) {
true_to_local(
*m_fem.displacementFes, displacement_true, m_displacement_local
);
}
double Eval(
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 gravity source coefficient received an invalid element "
"ID."
);
if (transformation.Attribute ==
m_domain_mapper.GetVacuumElementAttribute()) {
return 0.0;
}
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
);
if (status != mean_field::mapping::MappingStatus::valid) {
const mfem::FiniteElement &displacement_element =
*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 compactification_shape(
compactification_element.GetDof()
);
mfem::Vector reference_position(m_domain_mapper.GetDimension());
mfem::Vector displacement_value(m_domain_mapper.GetDimension());
displacement_element.CalcShape(
integration_point, displacement_shape
);
compactification_element.CalcShape(
integration_point, compactification_shape
);
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;
MFEM_ABORT(
"Stateless domain mapping failed while preparing the "
"gravity "
"source operator."
<< "\nMapping status = " << static_cast<int>(status)
<< "\nElement ID = " << element_id
<< "\nElement attribute = " << transformation.Attribute
<< "\nIntegration-point index = " << integration_point.index
<< "\nIntegration point = <" << integration_point.x << ", "
<< integration_point.y << ", " << integration_point.z << ">"
<< "\nReference position = <" << reference_position(0)
<< ", " << reference_position(1) << ", "
<< reference_position(2) << ">"
<< "\nReference radius = " << reference_position.Norml2()
<< "\nDisplacement value = <" << displacement_value(0)
<< ", " << displacement_value(1) << ", "
<< displacement_value(2) << ">"
<< "\nDisplacement magnitude = "
<< displacement_value.Norml2()
<< "\nCompactification coordinate = "
<< compactification_coordinate
<< "\nDisplacement ordering = "
<< static_cast<int>(m_fem.displacementFes->GetOrdering())
);
}
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 "
"or "
"non-finite mapping determinant."
);
return 4.0 * std::numbers::pi * mean_field::utils::G *
mapping_determinant;
}
private:
void LoadElement(const int element_id) {
if (element_id == m_cached_element_id) {
return;
}
const mfem::FiniteElement &displacement_element =
*m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
mfem::DofTransformation *displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(
element_id, m_displacement_dofs
);
mfem::DofTransformation *compactification_dof_transformation =
m_fem.compactificationFes->GetElementDofs(
element_id, m_compactification_dofs
);
m_displacement_local.GetSubVector(
m_displacement_dofs, m_element_displacement
);
m_fem.compactificationCoordinate->GetSubVector(
m_compactification_dofs, m_element_compactification
);
if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(
m_element_displacement
);
}
if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal(
m_element_compactification
);
}
m_displacement_data = std::make_unique<
mean_field::mapping::ElementDisplacementData>(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacement_element, m_element_displacement
)
);
m_compactification_data = std::make_unique<
mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification
);
m_cached_element_id = element_id;
}
const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapperStateless &m_domain_mapper;
mfem::Vector m_displacement_local;
mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData>
m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
mean_field::mapping::DomainMapperStateless::Workspace m_workspace;
int m_cached_element_id{-1};
};
} // namespace
namespace mean_field::operators {
PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
)
: Operator(
get_operator_height(f),
get_operator_width(f)
),
m_fem(f),
m_domain_mapper(domain_mapper) {
MFEM_VERIFY(
f.mesh != nullptr,
"PreparedMappedGravitySourceOperator requires a mesh."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"PreparedMappedGravitySourceOperator requires the density "
"finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"PreparedMappedGravitySourceOperator requires "
"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
);
}
void PreparedMappedGravitySourceOperator::Prepare(
const mfem::Vector &displacement_true
) {
MFEM_VERIFY(
displacement_true.Size() == m_fem.displacementFes->GetTrueVSize(),
"PreparedMappedGravitySourceOperator received a displacement "
"vector "
"with the wrong size."
);
for (int i = 0; i < displacement_true.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement_true(i)),
"PreparedMappedGravitySourceOperator received a non-finite "
"displacement value."
);
}
m_is_prepared = false;
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
FrozenMappedGravitySourceCoefficient source_coefficient(
m_fem, m_domain_mapper, 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;
}
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
data.element_id = element_id;
data.density_dof_transformation =
m_fem.densityFes->GetElementDofs(element_id, data.density_dofs);
data.potential_dof_transformation =
m_fem.gravityPotentialFes->GetElementDofs(
element_id, data.potential_dofs
);
const mfem::FiniteElement &density_element =
*m_fem.densityFes->GetFE(element_id);
const mfem::FiniteElement &potential_element =
*m_fem.gravityPotentialFes->GetFE(element_id);
mfem::ElementTransformation &transformation =
*m_fem.mesh->GetElementTransformation(element_id);
const mfem::IntegrationRule &integration_rule = get_source_rule(
m_fem, density_element, potential_element, transformation
);
const int quadrature_point_count = integration_rule.GetNPoints();
const int density_dof_count = density_element.GetDof();
const int potential_dof_count = potential_element.GetDof();
data.density_basis.SetSize(
quadrature_point_count, density_dof_count
);
data.potential_basis.SetSize(
quadrature_point_count, potential_dof_count
);
data.quadrature_data.SetSize(quadrature_point_count);
mfem::Vector density_shape(density_dof_count);
mfem::Vector potential_shape(potential_dof_count);
for (int quadrature_point = 0;
quadrature_point < quadrature_point_count;
++quadrature_point) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(quadrature_point);
transformation.SetIntPoint(&integration_point);
// CalcPhysShape matches the scalar mixed-mass discretization,
// including the finite-element map type.
density_element.CalcPhysShape(transformation, density_shape);
potential_element.CalcPhysShape(
transformation, potential_shape
);
for (int i = 0; i < density_dof_count; ++i) {
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_true,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"Mult is called."
);
MFEM_VERIFY(
density_true.Size() == Width(),
"PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size."
);
mfem::Vector density_local;
true_to_local(*m_fem.densityFes, density_true, density_local);
mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize());
local_action = 0.0;
mfem::Vector element_density;
mfem::Vector quadrature_density;
mfem::Vector element_action;
for (const ElementPAData &data : m_elements) {
density_local.GetSubVector(data.density_dofs, element_density);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->InvTransformPrimal(
element_density
);
}
quadrature_density.SetSize(data.quadrature_data.Size());
// B_density * x_e
data.density_basis.Mult(element_density, quadrature_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, action);
}
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential_true,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called."
);
MFEM_VERIFY(
potential_true.Size() == Height(),
"PreparedMappedGravitySourceOperator received a potential vector "
"with the wrong size."
);
mfem::Vector potential_local;
true_to_local(
*m_fem.gravityPotentialFes, 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, action);
}
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
} // namespace mean_field::operators

View File

@@ -0,0 +1,441 @@
module;
#include <cmath>
#include <cstdint>
#include <memory>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_hdiv_mass;
namespace {
int get_operator_size(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space."
);
return f.gravityFluxFes->GetTrueVSize();
}
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;
}
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 "
"element "
"geometry."
);
MFEM_VERIFY(
element.GetOrder() == representative_element.GetOrder(),
"Prepared H(div) mass domains currently require a uniform "
"finite-element order."
);
MFEM_VERIFY(
transformation.OrderW() ==
representative_transformation.OrderW(),
"Prepared H(div) mass domains currently require a uniform "
"geometry-weight order."
);
}
}
class FrozenMappedHDivMassCoefficient final
: public mfem::MatrixCoefficient {
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(
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 =
transformation.Attribute ==
m_domain_mapper.GetVacuumElementAttribute();
if (element_is_vacuum != m_elevates_vacuum) {
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) "
"mass "
"operator. Mapping status = "
<< static_cast<int>(status)
<< ", element ID = " << element_id
<< ", element attribute = " << transformation.Attribute
<< ", coefficient domain = "
<< (m_elevates_vacuum ? "vacuum" : "stellar")
);
const mfem::DenseMatrix &mapping_jacobian =
mapping_context.mapping.mapping_jacobian;
const double mapping_determinant =
mapping_context.mapping.mapping_determinant;
MFEM_VERIFY(
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
"Prepared H(div) mass operator encountered a non-positive or "
"non-finite mapping determinant."
);
mfem::MultAtB(mapping_jacobian, mapping_jacobian, mass_tensor);
mass_tensor *= 1.0 / mapping_determinant;
}
private:
void LoadElement(const int element_id) {
if (element_id == m_cached_element_id) {
return;
}
const mfem::FiniteElement &displacement_element =
*m_fem.displacementFes->GetFE(element_id);
const mfem::FiniteElement &compactification_element =
*m_fem.compactificationFes->GetFE(element_id);
mfem::DofTransformation *displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(
element_id, m_displacement_dofs
);
mfem::DofTransformation *compactification_dof_transformation =
m_fem.compactificationFes->GetElementDofs(
element_id, m_compactification_dofs
);
m_displacement_local.GetSubVector(
m_displacement_dofs, m_element_displacement
);
m_fem.compactificationCoordinate->GetSubVector(
m_compactification_dofs, m_element_compactification
);
if (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(
m_element_displacement
);
}
if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal(
m_element_compactification
);
}
m_displacement_data = std::make_unique<
mean_field::mapping::ElementDisplacementData>(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacement_element, m_element_displacement
)
);
m_compactification_data = std::make_unique<
mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification
);
m_cached_element_id = element_id;
}
const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapperStateless &m_domain_mapper;
mfem::Vector m_displacement_local;
mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData>
m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
mean_field::mapping::DomainMapperStateless::Workspace m_workspace;
int m_cached_element_id{-1};
bool m_elevates_vacuum;
};
} // namespace
namespace mean_field::operators {
PreparedMappedHDivMassOperator::PreparedMappedHDivMassOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
)
: Operator(get_operator_size(f)),
m_fem(f),
m_domain_mapper(domain_mapper) {
MFEM_VERIFY(
f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"PreparedMappedHDivMassOperator requires the compactification "
"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
);
utils::populate_element_mask(
f.mesh.get(), utils::DOMAINS::VACUUM, m_vacuum_marker
);
const int stellar_element_id =
find_representative_element(f, m_stellar_marker);
const int vacuum_element_id =
find_representative_element(f, m_vacuum_marker);
MFEM_VERIFY(
stellar_element_id >= 0, "PreparedMappedHDivMassOperator requires "
"at least one stellar element."
);
MFEM_VERIFY(
vacuum_element_id >= 0,
"PreparedMappedHDivMassOperator requires at "
"least one compactified vacuum element."
);
validate_uniform_domain_discretization(
f, m_stellar_marker, stellar_element_id
);
validate_uniform_domain_discretization(
f, m_vacuum_marker, vacuum_element_id
);
}
void PreparedMappedHDivMassOperator::Prepare(
const mfem::Vector &displacement_true
) {
MFEM_VERIFY(
displacement_true.Size() == m_fem.displacementFes->GetTrueVSize(),
"PreparedMappedHDivMassOperator received a displacement vector "
"with "
"the wrong size."
);
for (int i = 0; i < displacement_true.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement_true(i)),
"PreparedMappedHDivMassOperator received a non-finite "
"displacement "
"value."
);
}
const int stellar_element_id =
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 &vacuum_element =
*m_fem.gravityFluxFes->GetFE(vacuum_element_id);
mfem::ElementTransformation &stellar_transformation =
*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_coefficient.reset();
m_vacuum_mass_coefficient.reset();
m_stellar_mass_coefficient =
std::make_unique<FrozenMappedHDivMassCoefficient>(
m_fem, m_domain_mapper, displacement_true, false
);
m_vacuum_mass_coefficient =
std::make_unique<FrozenMappedHDivMassCoefficient>(
m_fem, m_domain_mapper, displacement_true, true
);
m_mass_form =
std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto stellar_integrator =
std::make_unique<mfem::VectorFEMassIntegrator>(
*m_stellar_mass_coefficient
);
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(
*m_vacuum_mass_coefficient
);
m_fem.quadratureFactory->configure_gravity_hdiv_mass(
*stellar_integrator, quadrature::QuadratureRole::discretization,
stellar_element, stellar_transformation, utils::DOMAINS::STELLAR,
quadrature::MappingKind::general
);
m_fem.quadratureFactory->configure_gravity_hdiv_mass(
*vacuum_integrator, quadrature::QuadratureRole::discretization,
vacuum_element, vacuum_transformation, utils::DOMAINS::VACUUM,
quadrature::MappingKind::kelvin
);
m_mass_form->AddDomainIntegrator(
stellar_integrator.release(), m_stellar_marker
);
m_mass_form->AddDomainIntegrator(
vacuum_integrator.release(), m_vacuum_marker
);
m_mass_form->Assemble();
m_is_prepared = true;
++m_preparation_count;
}
void PreparedMappedHDivMassOperator::Mult(
const mfem::Vector &gravity_gradient_true,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before Mult is called."
);
MFEM_VERIFY(
m_mass_form != nullptr, "PreparedMappedHDivMassOperator has no "
"assembled partial-assembly form."
);
MFEM_VERIFY(
gravity_gradient_true.Size() == Width(),
"PreparedMappedHDivMassOperator received a gravity-gradient vector "
"with the wrong size."
);
action.SetSize(Height());
m_mass_form->Mult(gravity_gradient_true, action);
}
bool PreparedMappedHDivMassOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
} // namespace mean_field::operators

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,55 @@
module;
#include "mfem.hpp"
#include <source_location>
#include <array>
#include <cmath>
#include <format>
#include <source_location>
#include <string_view>
#include <unordered_map>
#include <format>
module mean_field;
import :mapping.coefficients;
import :analysis.integral;
namespace {
double centrifugal_potential(const mfem::Vector &phys_x, const double omega) {
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 {
GravitySolution grav_potential(
@@ -24,68 +58,106 @@ namespace mean_field::physics {
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 = 0;
outer_bdr_marker[1] = 1;
mfem::ParLinearForm g_rhs(f.RT_fes.get());
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) {
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.RT_fes->GetTypicalTraceElement();
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.quadrature_factory->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);
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.L2_fes.get());
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;
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);
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.L2_fes->GetTypicalFE();
const mfem::ElementTransformation& source_transformation = *f.mesh->GetElementTransformation(0);
const int source_coefficient_order = f.L2_fes->GetMaxElementOrder();
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.quadrature_factory->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.gravity_context.stellar_mask);
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.gravity_block_true_offsets);
mfem::BlockVector RHS(f.gravityBlockTrueOffsets);
RHS.GetBlock(0) = *g_rhs.ParallelAssemble();
RHS.GetBlock(1) = *f_rhs.ParallelAssemble();
mfem::BlockVector X(f.gravity_block_true_offsets);
mfem::BlockVector X(f.gravityBlockTrueOffsets);
X = 0.0;
f.gravity_context.minres->SetOperator(*f.gravity_context.block_A);
f.gravity_context.minres->Mult(RHS, X);
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(
@@ -105,18 +177,21 @@ namespace mean_field::physics {
std::unique_ptr<mfem::Coefficient> centrifugal_coeff;
if (fem.has_mapping()) {
centrifugal_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, rot);
centrifugal_coeff = std::make_unique<
mapping::PhysicalPositionFunctionCoefficient>(
*fem.mapping, rot
);
} else {
centrifugal_coeff = std::make_unique<mfem::FunctionCoefficient>(rot);
centrifugal_coeff =
std::make_unique<mfem::FunctionCoefficient>(rot);
}
mfem::GridFunction centrifugal_gf(fem.H1_fes.get());
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(
@@ -128,11 +203,23 @@ namespace mean_field::physics {
mfem::DenseMatrix local_Q(dim, dim);
local_Q = 0.0;
for (int i = 0; i < fem.H1_fes->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3) continue;
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3)
continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = *fem.int_rule;
mfem::ElementTransformation *trans =
fem.mesh->GetElementTransformation(i);
using DensityField = field::Field<field::Density>;
const quadrature::Query query =
DensityField::make_query<field::Density::Form::Quadrupole>(
quadrature::QuadratureRole::diagnostic, trans->OrderW(),
std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
fem.has_mapping() ? quadrature::MappingKind::general
: quadrature::MappingKind::none
);
const mfem::IntegrationRule &ir =
*fem.quadratureFactory->get(query, trans->GetGeometryType())
.integration_rule;
for (int j = 0; j < ir.GetNPoints(); ++j) {
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
@@ -164,7 +251,8 @@ namespace mean_field::physics {
for (int m = 0; m < dim; ++m) {
for (int n = 0; n < dim; ++n) {
const double delta = (m == n) ? 1.0 : 0.0;
const double contrib = 3.0 * x_prime(m) * x_prime(n) - delta * r_sq;
const double contrib =
3.0 * x_prime(m) * x_prime(n) - delta * r_sq;
local_Q(m, n) += rho_val * contrib * weight;
}
}
@@ -172,7 +260,10 @@ namespace mean_field::physics {
}
mfem::DenseMatrix global_Q(dim, dim);
MPI_Allreduce(local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
MPI_Allreduce(
local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE,
MPI_SUM, fem.mesh->GetComm()
);
return global_Q;
}
@@ -183,7 +274,8 @@ namespace mean_field::physics {
const mfem::Vector &phys_x
) {
const double r = phys_x.Norml2();
if (r < 1e-12) return 0.0;
if (r < 1e-12)
return 0.0;
const int dim = fem.mesh->Dimension();
@@ -197,7 +289,8 @@ namespace mean_field::physics {
}
}
const double l2_contrib = -(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor;
const double l2_contrib =
-(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor;
const double l0_contrib = -utils::G * total_mass / r;
@@ -211,87 +304,331 @@ namespace mean_field::physics {
// ==========================================
// 1. Partially Assemble the High-Order Mass Block
// ==========================================
f.gravity_context.m_form = std::make_unique<mfem::ParBilinearForm>(f.RT_fes.get());
f.gravity_context.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
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.gravity_context.mapped_hdiv_mass_coeff = std::make_unique<mapping::MappedHDivMassCoefficient>(*f.mapping, f.mesh->Dimension());
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*f.gravity_context.mapped_hdiv_mass_coeff);
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.gravity_context.mapped_hdiv_mass_coeff.reset();
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>();
f.gravityContext.mapped_hdiv_mass_coeff.reset();
hdiv_mass_integrator =
std::make_unique<mfem::VectorFEMassIntegrator>();
}
const mfem::FiniteElement& hdiv_element = *f.RT_fes->GetTypicalFE();
const mfem::ElementTransformation& hdiv_transformation = *f.mesh->GetElementTransformation(0);
const quadrature::MappingKind mapping_kind = f.has_mapping() ? quadrature::MappingKind::general : quadrature::MappingKind::none;
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.quadrature_factory->configure_gravity_hdiv_mass(*hdiv_mass_integrator, quadrature::QuadratureRole::discretization, hdiv_element, hdiv_transformation, utils::DOMAINS::ALL, mapping_kind);
f.gravity_context.m_form->AddDomainIntegrator(hdiv_mass_integrator.release());
f.gravity_context.m_form->Assemble();
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.gravity_context.b_form = std::make_unique<mfem::ParMixedBilinearForm>(f.RT_fes.get(), f.L2_fes.get());
f.gravity_context.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
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.L2_fes->GetTypicalFE();
auto divergence_discretization_integrator =
std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement &divergence_discretization_test_element =
*f.gravityPotentialFes->GetTypicalFE();
f.quadrature_factory->configure_gravity_divergence(*divergence_discretization_integrator, quadrature::QuadratureRole::discretization, hdiv_element, divergence_discretization_test_element, hdiv_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none);
f.gravity_context.b_form->AddDomainIntegrator(divergence_discretization_integrator.release());
f.gravity_context.b_form->Assemble();
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
);
source_form->Prepare(displacement_true);
f.gravityContext.source_form = std::move(source_form);
// ==========================================
// 3. Assemble Global Block Operator
// ==========================================
f.gravity_context.BT = std::make_unique<mfem::TransposeOperator>(f.gravity_context.b_form.get());
f.gravityContext.BT = std::make_unique<mfem::TransposeOperator>(
f.gravityContext.b_form.get()
);
f.gravity_context.block_A = std::make_unique<mfem::BlockOperator>(f.gravity_block_true_offsets);
f.gravity_context.block_A->SetBlock(0, 0, f.gravity_context.m_form.get());
f.gravity_context.block_A->SetBlock(0, 1, f.gravity_context.BT.get());
f.gravity_context.block_A->SetBlock(1, 0, f.gravity_context.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.RT_fes->GetTrueVSize());
f.gravity_context.m_form->AssembleDiagonal(mass_diagonal);
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.");
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.RT_fes.get(), f.L2_fes.get());
auto divergence_preconditioner_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
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.RT_fes->GetTypicalFE();
const mfem::FiniteElement& divergence_test_element = *f.L2_fes->GetTypicalFE();
const mfem::ElementTransformation& divergence_transformation = *f.mesh->GetElementTransformation(0);
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.quadrature_factory->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());
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());
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.gravity_context.Schur.reset(mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get()));
f.gravityContext.Schur.reset(
mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get())
);
// ==========================================
// 5. Wire Up the preconditioners
// ==========================================
f.gravity_context.prec_M = std::make_unique<mfem::OperatorJacobiSmoother>(mass_diagonal, empty_tdofs);
f.gravity_context.prec_Phi->SetOperator(*f.gravity_context.Schur);
f.gravity_context.block_prec->SetDiagonalBlock(0, f.gravity_context.prec_M.get());
f.gravity_context.block_prec->SetDiagonalBlock(1, f.gravity_context.prec_Phi.get());
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,
const utils::Args &args,
const mfem::GridFunction &rho,
const mfem::GridFunction &displacement
) {
MFEM_VERIFY(
f.mesh != nullptr,
"Gravity initialization requires a parallel mesh."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"Gravity initialization requires the density finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"Gravity initialization requires the gravity-potential "
"finite-element "
"space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"Gravity initialization requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "Gravity initialization requires the "
"displacement finite-element space."
);
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(
rho.FESpace() == f.densityFes.get(),
"Gravity initialization requires density to use the FEM density "
"space."
);
MFEM_VERIFY(
displacement.FESpace() == f.displacementFes.get(),
"Gravity initialization requires displacement to use the FEM "
"Vec_H1 "
"space."
);
using form = utils::blocks::gravity_field_form;
constexpr auto gravity_gradient_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
);
const std::array<int, form::value_block_count> value_sizes{
f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(),
f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize()
};
const std::array<int, form::residual_block_count> residual_sizes{
f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize()
};
const utils::blocks::form_layout<form> layout(
value_sizes, residual_sizes
);
mfem::Vector density_true;
mfem::Vector displacement_true;
grid_function_to_true_dofs(*f.densityFes, rho, density_true);
grid_function_to_true_dofs(
*f.displacementFes, displacement, displacement_true
);
operators::context::gravity_field::GravityFieldLinearizationContext
linearization_context(f, *f.domainMapperStateless);
operators::GravityFieldJacobianOperator gravity_jacobian(
f, *f.domainMapperStateless, linearization_context,
layout.value_offsets(), layout.residual_offsets()
);
operators::GravityFieldOperator gravity_operator(
f, *f.domainMapperStateless, linearization_context,
layout.value_offsets(), gravity_jacobian
);
operators::context::gravity_field::GravityFieldGeometryContext
reduced_geometry_context(f, *f.domainMapperStateless);
operators::ReducedGravityFieldOperator reduced_operator(
gravity_operator, reduced_geometry_context, displacement_true
);
mfem::Vector right_hand_side;
reduced_operator.BuildRightHandSide(density_true, right_hand_side);
MFEM_VERIFY(
right_hand_side.Size() == reduced_operator.Height(),
"The reduced gravity right-hand side has the wrong size."
);
mfem::BlockVector gravity_state(
reduced_operator.GetGravityTrueOffsets()
);
gravity_state = 0.0;
mfem::MINRESSolver minres(f.mesh->GetComm());
minres.SetOperator(reduced_operator);
minres.SetPreconditioner(*f.gravityContext.block_prec);
minres.SetRelTol(args.p.rtol);
minres.SetAbsTol(args.p.atol);
minres.SetMaxIter(args.p.max_iters);
minres.SetPrintLevel(1);
minres.Mult(right_hand_side, gravity_state);
MFEM_VERIFY(
minres.GetConverged(),
"The reduced gravity solve failed to converge."
);
GravitySolution solution(f);
solution.gradPhi.SetFromTrueDofs(
gravity_state.GetBlock(gravity_gradient_residual_block)
);
solution.phi.SetFromTrueDofs(
gravity_state.GetBlock(gravity_poisson_residual_block)
);
return solution;
}
} // namespace mean_field::physics

View File

@@ -1,17 +1,32 @@
module;
#include "mean_field.h"
#include <array>
module mean_field;
namespace mean_field::physics {
double compute_moment_of_inertia(const fem::FEM &fem, const mfem::GridFunction &rho_ref) {
double compute_moment_of_inertia(
const fem::FEM &fem,
const mfem::GridFunction &rho_ref
) {
double local_I = 0.0;
for (int i = 0; i < fem.mesh->GetNE(); i++) {
if (fem.mesh->GetAttribute(i) == 3) continue;
if (fem.mesh->GetAttribute(i) == 3)
continue;
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = *fem.int_rule;
mfem::ElementTransformation *T =
fem.mesh->GetElementTransformation(i);
using DensityField = field::Field<field::Density>;
const quadrature::Query query =
DensityField::make_query<field::Density::Form::Quadrupole>(
quadrature::QuadratureRole::diagnostic, T->OrderW(),
std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
quadrature::MappingKind::general
);
const mfem::IntegrationRule &ir =
*fem.quadratureFactory->get(query, T->GetGeometryType())
.integration_rule;
for (int j = 0; j < ir.GetNPoints(); j++) {
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
@@ -22,7 +37,8 @@ namespace mean_field::physics {
mfem::Vector x_phys;
fem.mapping->GetPhysicalPoint(*T, ip, x_phys);
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 = T->Weight() * ip.weight * detJ;
@@ -31,8 +47,10 @@ namespace mean_field::physics {
}
double global_I = 0.0;
MPI_Allreduce(&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
MPI_Allreduce(
&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()
);
return global_I;
}
}
} // namespace mean_field::physics

View File

@@ -11,7 +11,7 @@ namespace mean_field::utils {
mfem::Vector &x_ref
) {
const int dim = fem.mesh->Dimension();
x_ref = x_phys_target;
x_ref = x_phys_target;
mfem::Array<int> init_elem;
mfem::Array<mfem::IntegrationPoint> init_ip;
@@ -29,8 +29,10 @@ namespace mean_field::utils {
mfem::Array<mfem::IntegrationPoint> origin_ip;
fem.mesh->FindPoints(P_origin, origin_elem, origin_ip, false);
if (origin_elem.Size() > 0 && origin_elem[0] >= 0 && !fem.mapping->IsIdentity()) {
mfem::ElementTransformation *T0 = fem.mesh->GetElementTransformation(origin_elem[0]);
if (origin_elem.Size() > 0 && origin_elem[0] >= 0 &&
fem.mapping->HasDisplacementField()) {
mfem::ElementTransformation *T0 =
fem.mesh->GetElementTransformation(origin_elem[0]);
T0->SetIntPoint(&origin_ip[0]);
mfem::DenseMatrix J0(dim, dim), J0_inv(dim, dim);
@@ -81,7 +83,8 @@ namespace mean_field::utils {
if (elem_ids.Size() == 0 || elem_ids[0] < 0) {
find_failures++;
if (find_failures > 10) return false;
if (find_failures > 10)
return false;
double norm = x_ref.Norml2();
if (norm > 1e-15) {
@@ -92,10 +95,11 @@ namespace mean_field::utils {
continue;
}
int elemID = elem_ids[0];
int elemID = elem_ids[0];
const mfem::IntegrationPoint &ip = ips[0];
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(elemID);
mfem::ElementTransformation *T =
fem.mesh->GetElementTransformation(elemID);
T->SetIntPoint(&ip);
mfem::Vector current_x_phys(dim);
@@ -135,7 +139,8 @@ namespace mean_field::utils {
x_ref = x_ref_candidate;
} else {
find_failures++;
if (find_failures > 10) return false;
if (find_failures > 10)
return false;
if (double norm = x_ref.Norml2(); norm > 1e-15) {
x_ref *= 0.5 * RADIUS / norm;
} else {
@@ -155,7 +160,8 @@ namespace mean_field::utils {
const mapping::COORDINATE_SPACE rspace
) {
mfem::Vector x_search;
if (vspace == mapping::COORDINATE_SPACE::PHYSICAL && fem.has_mapping()) {
if (vspace == mapping::COORDINATE_SPACE::PHYSICAL &&
fem.has_mapping()) {
GetReferencePoint(fem, x, x_search);
} else {
x_search = x;
@@ -171,18 +177,22 @@ namespace mean_field::utils {
double local_val = 0.0;
if (elem_ids.Size() > 0 && elem_ids[0] >= 0) {
const double val = u.GetValue(elem_ids[0], ips[0]);
if (rspace == mapping::COORDINATE_SPACE::PHYSICAL && !fem.has_mapping()) {
MFEM_ABORT("Physical evaluation mode requested but no mapping provided. Check domain bounds and mapping setup.");
if (rspace == mapping::COORDINATE_SPACE::PHYSICAL &&
!fem.has_mapping()) {
MFEM_ABORT(
"Physical evaluation mode requested but no mapping "
"provided. Check "
"domain bounds and mapping setup."
);
}
local_val = val;
}
double global_val = 0.0;
MPI_Allreduce(&local_val, &global_val, 1, MPI_DOUBLE, MPI_MAX, fem.H1_fes->GetComm());
MPI_Allreduce(
&local_val, &global_val, 1, MPI_DOUBLE, MPI_MAX, fem.mesh->GetComm()
);
return global_val;
}
}
} // namespace mean_field::utils

View File

@@ -1,6 +1,6 @@
module;
#include <mfem.hpp>
#include <expected>
#include <mfem.hpp>
module mean_field;
import :boundary.contexts;
@@ -10,18 +10,22 @@ namespace mean_field::utils {
DOMAINS lhs,
DOMAINS rhs
) {
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
return static_cast<DOMAINS>(
static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)
);
}
DOMAINS operator&(
DOMAINS lhs,
DOMAINS rhs
) {
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
return static_cast<DOMAINS>(
static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs)
);
}
void populate_element_mask(
const mfem::Mesh* mesh,
const mfem::Mesh *mesh,
const DOMAINS domain,
mfem::Array<int> &mask
) {
@@ -33,7 +37,8 @@ namespace mean_field::utils {
mask[0] = 1;
}
if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE && max_attr >= 2) {
if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE &&
max_attr >= 2) {
mask[1] = 1;
}
@@ -59,7 +64,8 @@ namespace mean_field::utils {
for (int j = 0; j < dofs.Size(); j++) {
int index = dofs[j];
if (index < 0) index = -1 - index;
if (index < 0)
index = -1 - index;
vdof_marker[index] = 1;
}
}
@@ -68,23 +74,28 @@ namespace mean_field::utils {
fes->MarkerToList(vdof_marker, ess_tdof);
}
std::expected<boundary::Bounds, boundary::BoundsError> discover_bounds(
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;
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) {
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]);
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);
}
@@ -93,16 +104,22 @@ namespace mean_field::utils {
double global_min_r, global_max_r;
int global_found_vacuum;
int l_found = found_vacuum ? 1 : 0;
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);
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);
@@ -110,15 +127,11 @@ namespace mean_field::utils {
return std::unexpected(boundary::BoundsError::CANNOT_FIND_VACUUM);
}
int get_mesh_order(
const mfem::Mesh &mesh
) {
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

@@ -0,0 +1,194 @@
#pragma once
#include <algorithm>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <mutex>
#include <string>
#include <vector>
#include <mpi.h>
namespace mean_field::profiling {
struct Statistics {
unsigned long long observations{0};
unsigned long long warmups{0};
unsigned long long samples{0};
unsigned long long warmup_target{0};
double total_seconds{0.0};
double minimum_seconds{std::numeric_limits<double>::infinity()};
double maximum_seconds{0.0};
};
class Registry {
public:
static Registry& Get() {
static Registry registry;
return registry;
}
void Record(const std::string& label, const double seconds, const unsigned long long warmup_count) {
std::scoped_lock lock(m_mutex);
Statistics& statistics = m_statistics[label];
statistics.warmup_target = std::max(statistics.warmup_target, warmup_count);
const bool is_warmup = statistics.observations < statistics.warmup_target;
++statistics.observations;
if (is_warmup) {
++statistics.warmups;
return;
}
++statistics.samples;
statistics.total_seconds += seconds;
statistics.minimum_seconds = std::min(statistics.minimum_seconds, seconds);
statistics.maximum_seconds = std::max(statistics.maximum_seconds, seconds);
}
void Reset() {
std::scoped_lock lock(m_mutex);
m_statistics.clear();
}
void Print(MPI_Comm communicator) const {
const std::map<std::string, Statistics> snapshot = GetSnapshot();
int mpi_initialized = 0;
int mpi_finalized = 0;
MPI_Initialized(&mpi_initialized);
if (mpi_initialized) MPI_Finalized(&mpi_finalized);
const bool use_mpi = mpi_initialized && !mpi_finalized;
int rank = 0;
int communicator_size = 1;
if (use_mpi) {
MPI_Comm_rank(communicator, &rank);
MPI_Comm_size(communicator, &communicator_size);
}
if (rank == 0) {
std::cout << '\n';
std::cout << std::left << std::setw(42) << "Profile Region"
<< std::right << std::setw(11) << "Samples"
<< std::setw(10) << "Warmups"
<< std::setw(14) << "Avg Max ms"
<< std::setw(14) << "Min ms"
<< std::setw(14) << "Max ms"
<< std::setw(14) << "Total Max s" << '\n';
std::cout << std::string(119, '-') << '\n';
}
for (const auto& [label, local_statistics] : snapshot) {
unsigned long long minimum_samples = local_statistics.samples;
unsigned long long maximum_samples = local_statistics.samples;
unsigned long long maximum_warmups = local_statistics.warmups;
double local_average = local_statistics.samples > 0 ? local_statistics.total_seconds / static_cast<double>(local_statistics.samples) : 0.0;
double local_minimum = local_statistics.samples > 0 ? local_statistics.minimum_seconds : std::numeric_limits<double>::infinity();
double local_maximum = local_statistics.maximum_seconds;
double local_total = local_statistics.total_seconds;
double maximum_rank_average = local_average;
double global_minimum = local_minimum;
double global_maximum = local_maximum;
double maximum_rank_total = local_total;
if (use_mpi) {
MPI_Allreduce(&local_statistics.samples, &minimum_samples, 1, MPI_UNSIGNED_LONG_LONG, MPI_MIN, communicator);
MPI_Allreduce(&local_statistics.samples, &maximum_samples, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
MPI_Allreduce(&local_statistics.warmups, &maximum_warmups, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
MPI_Allreduce(&local_average, &maximum_rank_average, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&local_minimum, &global_minimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
MPI_Allreduce(&local_maximum, &global_maximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&local_total, &maximum_rank_total, 1, MPI_DOUBLE, MPI_MAX, communicator);
}
if (!std::isfinite(global_minimum)) global_minimum = 0.0;
if (rank == 0) {
const std::string sample_string = minimum_samples == maximum_samples
? std::to_string(minimum_samples)
: std::to_string(minimum_samples) + "-" + std::to_string(maximum_samples);
std::cout << std::left << std::setw(100) << label
<< std::right << std::setw(11) << sample_string
<< std::setw(10) << maximum_warmups
<< std::setw(14) << std::fixed << std::setprecision(3) << 1.0e3 * maximum_rank_average
<< std::setw(14) << 1.0e3 * global_minimum
<< std::setw(14) << 1.0e3 * global_maximum
<< std::setw(14) << std::setprecision(6) << maximum_rank_total << '\n';
}
}
if (rank == 0) {
std::cout << std::string(119, '=') << '\n';
std::cout << "MPI ranks: " << communicator_size << "\n\n";
}
}
private:
[[nodiscard]] std::map<std::string, Statistics> GetSnapshot() const {
std::scoped_lock lock(m_mutex);
return m_statistics;
}
private:
mutable std::mutex m_mutex;
std::map<std::string, Statistics> m_statistics;
};
class ScopedTimer {
public:
ScopedTimer(std::string label, const unsigned long long warmup_count)
: m_label(std::move(label)),
m_warmup_count(warmup_count),
m_start(std::chrono::steady_clock::now()) {}
ScopedTimer(const ScopedTimer&) = delete;
ScopedTimer& operator=(const ScopedTimer&) = delete;
ScopedTimer(ScopedTimer&&) = delete;
ScopedTimer& operator=(ScopedTimer&&) = delete;
~ScopedTimer() {
try {
const auto stop = std::chrono::steady_clock::now();
const double seconds = std::chrono::duration<double>(stop - m_start).count();
Registry::Get().Record(m_label, seconds, m_warmup_count);
} catch (...) {}
}
private:
std::string m_label;
unsigned long long m_warmup_count;
std::chrono::steady_clock::time_point m_start;
};
}
#define MEAN_FIELD_PROFILE_JOIN_IMPL(left, right) left##right
#define MEAN_FIELD_PROFILE_JOIN(left, right) MEAN_FIELD_PROFILE_JOIN_IMPL(left, right)
#define MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count) \
::mean_field::profiling::ScopedTimer MEAN_FIELD_PROFILE_JOIN(mean_field_profile_timer_, __COUNTER__)(label, warmup_count)
#define MEAN_FIELD_PROFILE_SCOPE(label) \
MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, 1)
#define MEAN_FIELD_PROFILE_CALL_WARMUP(label, warmup_count, ...) \
do { \
MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count); \
__VA_ARGS__; \
} while (false)
#define MEAN_FIELD_PROFILE_CALL(label, ...) \
MEAN_FIELD_PROFILE_CALL_WARMUP(label, 1, __VA_ARGS__)
#define MEAN_FIELD_PROFILE_RESET() \
::mean_field::profiling::Registry::Get().Reset()
#define MEAN_FIELD_PROFILE_PRINT(communicator) \
::mean_field::profiling::Registry::Get().Print(communicator)

View File

@@ -12,7 +12,8 @@ export namespace mean_field::analysis {
const fem::FEM &fem,
const mfem::GridFunction &gf,
utils::DOMAINS domain = utils::DOMAINS::ALL,
mapping::COORDINATE_SPACE coord_space = mapping::COORDINATE_SPACE::PHYSICAL
mapping::COORDINATE_SPACE coord_space =
mapping::COORDINATE_SPACE::PHYSICAL
);
mfem::Vector get_com(
@@ -32,10 +33,9 @@ export namespace mean_field::analysis {
);
double get_mesh_volume(
const fem::FEM& fem,
mapping::COORDINATE_SPACE coordinate_space = mapping::COORDINATE_SPACE::PHYSICAL,
const fem::FEM &fem,
mapping::COORDINATE_SPACE coordinate_space =
mapping::COORDINATE_SPACE::PHYSICAL,
utils::DOMAINS domain = utils::DOMAINS::STELLAR
);
}
} // namespace mean_field::analysis

View File

@@ -5,20 +5,19 @@ export module mean_field:boundary.contexts;
export namespace mean_field::boundary {
struct BoundaryContext {
mfem::Array<int> inf_bounds;
mfem::Array<int> stellar_bounds;
mfem::Array<int> inf_bounds;
mfem::Array<int> stellar_bounds;
};
enum class Boundaries : uint8_t {
STELLAR_SURFACE = 1,
INF_SURFACE = 2
};
enum class Boundaries : uint8_t { STELLAR_SURFACE = 1, INF_SURFACE = 2 };
int operator-(
Boundaries b,
const int a
) {
return static_cast<int>(static_cast<uint8_t>(b) - static_cast<uint8_t>(a));
return static_cast<int>(
static_cast<uint8_t>(b) - static_cast<uint8_t>(a)
);
}
struct Bounds {
@@ -26,9 +25,6 @@ export namespace mean_field::boundary {
double r_inf_ref;
};
enum BoundsError : uint8_t {
CANNOT_FIND_VACUUM
};
enum BoundsError : uint8_t { CANNOT_FIND_VACUUM };
}
} // namespace mean_field::boundary

View File

@@ -1,95 +1,183 @@
module;
#include <stroid/stroid.h>
#include <memory>
#include <string>
#include <mfem.hpp>
#include <stroid/stroid.h>
export module mean_field:fem;
export import :physics.contexts;
export import :boundary.contexts;
export import :mapping.domain_mapper;
export import :utils.misc;
export import :utils.user;
export import :quadrature.mfem;
export import :field.mfem;
export namespace mean_field::fem {
using GravityField = field::Field<field::Gravity>;
using DisplacementField = field::Field<field::Displacement>;
using DensityField = field::Field<field::Density>;
using EnthalpyField = field::Field<field::Enthalpy>;
struct FEM {
// =====================================================================
// Mesh
// =====================================================================
stroid::StroidMesh smesh;
std::unique_ptr<mfem::ParMesh> mesh;
// =====================================
// 2. Finite Element Collections
// =====================================
// H1 (Continuous): For Gravitational Potential (Phi) and Velocity (v)
std::unique_ptr<mfem::FiniteElementCollection> H1_fec;
// =====================================================================
// Compile-time field descriptors
// =====================================================================
// L2 (Discontinuous): For Density (rho) to fix O-grid boundary scalloping
std::unique_ptr<mfem::FiniteElementCollection> L2_fec;
GravityField gravityField;
DisplacementField displacementField;
DensityField densityField;
EnthalpyField enthalpyField;
// H(div)/RT space for gravitational field
std::unique_ptr<mfem::RT_FECollection> RT_fec;
// =====================================================================
// Gravity field
//
// Collection members are declared before their corresponding spaces so
// that the spaces are destroyed first.
// =====================================================================
std::unique_ptr<mfem::FiniteElementCollection> gravityPotentialFec;
// =====================================
// 3. Finite Element Spaces
// =====================================
std::unique_ptr<mfem::ParFiniteElementSpace> H1_fes; // Scalar continuous (Gravity)
std::unique_ptr<mfem::ParFiniteElementSpace> Vec_H1_fes; // Vector continuous (Velocity field)
std::unique_ptr<mfem::ParFiniteElementSpace> L2_fes; // Scalar discontinuous (Density)
std::unique_ptr<mfem::ParFiniteElementSpace> RT_fes; // H(div)/RT space for gravitational field
std::unique_ptr<mfem::ParFiniteElementSpace> gravityPotentialFes;
// Preconditioning for Gravity
std::unique_ptr<mfem::ParLORDiscretization> H1_lor_disc;
const mfem::ParFiniteElementSpace *H1_lor_fes{nullptr};
std::unique_ptr<mfem::FiniteElementCollection> gravityFluxFec;
std::unique_ptr<mfem::ParFiniteElementSpace> gravityFluxFes;
// =====================================================================
// Displacement field
// =====================================================================
std::unique_ptr<mfem::FiniteElementCollection> displacementFec;
std::unique_ptr<mfem::ParFiniteElementSpace> displacementFes;
std::unique_ptr<mfem::ParGridFunction> displacement;
// =====================================================================
// Density field
// =====================================================================
std::unique_ptr<mfem::FiniteElementCollection> densityFec;
std::unique_ptr<mfem::ParFiniteElementSpace> densityFes;
// =====================================================================
// Specific-enthalpy field
// =====================================================================
std::unique_ptr<mfem::FiniteElementCollection> enthalpyFec;
std::unique_ptr<mfem::ParFiniteElementSpace> enthalpyFes;
// =====================================================================
// Compactification coordinate
// =====================================================================
std::unique_ptr<mfem::H1_FECollection> compactificationFec;
std::unique_ptr<mfem::ParFiniteElementSpace> compactificationFes;
std::unique_ptr<mfem::ParGridFunction> compactificationCoordinate;
// =====================================================================
// 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.
// =====================================================================
// =====================================
// 4. Domain Mapping
// =====================================
std::unique_ptr<mapping::DomainMapper> mapping;
// =====================================
// 5. Global System Tracking
// =====================================
// [ Velocity | Density | Mapping Parameters (Surface) ]
mfem::Array<int> block_true_offsets;
std::unique_ptr<mapping::DomainMapperStateless> domainMapperStateless;
mfem::Array<int> gravity_block_true_offsets;
// =====================================================================
// 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]
// =====================================================================
// Essential Boundary Conditions for the fluid (e.g., surface stress-free)
mfem::Array<int> ess_v_tdofs;
mfem::Array<int> blockTrueOffsets;
mfem::Array<int> gravityBlockTrueOffsets;
// Elements entirely in the vacuum domain where fluid equations are not solved
mfem::Array<int> vacuum_tdof_rho;
mfem::Array<int> vacuum_tdof_v;
// =====================================================================
// Boundary conditions and domain masks
// =====================================================================
mfem::Array<int> essentialDisplacementTdofs;
mfem::Array<int> vacuumDensityTdofs;
mfem::Array<int> vacuumEnthalpyTdofs;
mfem::Array<int> vacuumDisplacementTdofs;
// =====================================================================
// Global diagnostics
// =====================================================================
// =====================================
// 6. Multiphysics State & Integration
// =====================================
mfem::Vector com;
mfem::DenseMatrix Q;
int int_order{3};
std::unique_ptr<mfem::IntegrationRule> int_rule;
// =====================================================================
// Physics and boundary contexts
// =====================================================================
physics::GravityContext gravity_context;
boundary::BoundaryContext boundary_context;
physics::GravityContext gravityContext;
boundary::BoundaryContext boundaryContext;
std::unique_ptr<quadrature::RuleFactory> quadrature_factory;
std::unique_ptr<quadrature::RuleFactory> quadratureFactory;
// =====================================================================
// Validation
// =====================================================================
// =====================================
// 7. Utilities
// =====================================
[[nodiscard]] bool okay() const {
return (mesh != nullptr) &&
(H1_fec != nullptr) && (L2_fec != nullptr) && (RT_fec != nullptr) &&
(H1_fes != nullptr) && (Vec_H1_fes != nullptr) && (L2_fes != nullptr) && (RT_fes != nullptr);
return mesh != nullptr &&
gravityPotentialFec != nullptr &&
gravityPotentialFes != nullptr &&
gravityFluxFec != nullptr && gravityFluxFes != nullptr &&
displacementFec != nullptr && displacementFes != nullptr &&
displacement != nullptr &&
densityFec != nullptr && densityFes != nullptr &&
enthalpyFec != nullptr && enthalpyFes != nullptr &&
compactificationFec != nullptr &&
compactificationFes != nullptr &&
compactificationCoordinate != nullptr &&
mapping != nullptr && domainMapperStateless != nullptr &&
quadratureFactory != nullptr &&
blockTrueOffsets.Size() == 3 &&
gravityBlockTrueOffsets.Size() == 3;
}
[[nodiscard]] bool has_mapping() const { return mapping != nullptr; }
[[nodiscard]] bool has_mapping() const {
return mapping != nullptr;
}
};
FEM setup_fem(const std::string &filename, const utils::Args &args, int extra_refine = 0);
}
FEM setup_fem(
const std::string &filename,
const utils::Args &args,
int extraRefine = 0
);
} // namespace mean_field::fem

View File

@@ -0,0 +1,356 @@
module;
#include <concepts>
#include <cstddef>
#include <string_view>
#include <type_traits>
export module mean_field:field.base;
export namespace mean_field::field {
template <typename... Ts> struct TypeList { };
template <typename T, typename ListT> struct TypeListContains;
template <typename T, typename... Ts>
struct TypeListContains<T, TypeList<Ts...>>
: std::bool_constant<(std::same_as<T, Ts> || ...)> { };
template <typename T, typename ListT>
inline constexpr bool typeListContains = TypeListContains<T, ListT>::value;
enum class StorageKind { finite_element, global_scalar };
inline constexpr int dynamicBlockSize = -1;
// -------------------------------------------------------------------------
// Function-space tags
// -------------------------------------------------------------------------
struct L2 {
static constexpr std::string_view name = "L2";
};
struct H1 {
static constexpr std::string_view name = "H1";
};
struct RT {
static constexpr std::string_view name = "RT";
};
struct ND {
static constexpr std::string_view name = "ND";
};
template <typename SpaceT>
concept SpaceTag = std::same_as<SpaceT, L2> || std::same_as<SpaceT, H1> ||
std::same_as<SpaceT, RT> || std::same_as<SpaceT, ND>;
template <SpaceTag SpaceT, int RankV>
inline constexpr bool spaceSupportsRank =
(std::same_as<SpaceT, H1> && (RankV == 0 || RankV == 1)) ||
(std::same_as<SpaceT, L2> && (RankV == 0 || RankV == 1)) ||
(std::same_as<SpaceT, RT> && RankV == 1) ||
(std::same_as<SpaceT, ND> && RankV == 1);
// -------------------------------------------------------------------------
// Discretization descriptors
//
// familyOrder is the order passed to the backend's FE collection
// constructor. It is deliberately not called polynomialOrder because those
// values differ for some spaces, notably Raviart-Thomas elements in MFEM.
// -------------------------------------------------------------------------
template <SpaceTag SpaceT, int FamilyOrderV> struct Disc {
using Space = SpaceT;
static constexpr int familyOrder = FamilyOrderV;
static_assert(
FamilyOrderV >= 0,
"Finite-element family order must be non-negative."
);
};
template <typename T>
concept DiscretizationTag = requires {
typename T::Space;
{ T::familyOrder } -> std::convertible_to<int>;
} && SpaceTag<typename T::Space>;
// -------------------------------------------------------------------------
// Physical relations between quantities
// -------------------------------------------------------------------------
struct FieldRelation {
struct Independent { };
template <typename SourceT> struct Gradient {
using Source = SourceT;
};
template <typename SourceT> struct Divergence {
using Source = SourceT;
};
template <typename SourceT> struct Curl {
using Source = SourceT;
};
};
template <typename T> struct IsGradient : std::false_type { };
template <typename T> struct IsDivergence : std::false_type { };
template <typename T> struct IsCurl : std::false_type { };
template <typename SourceT>
struct IsGradient<FieldRelation::Gradient<SourceT>> : std::true_type { };
template <typename SourceT>
struct IsDivergence<FieldRelation::Divergence<SourceT>> : std::true_type {
};
template <typename SourceT>
struct IsCurl<FieldRelation::Curl<SourceT>> : std::true_type { };
template <typename RelationT>
concept ValidRelation =
std::same_as<RelationT, FieldRelation::Independent> ||
IsGradient<RelationT>::value || IsDivergence<RelationT>::value ||
IsCurl<RelationT>::value;
template <typename RelationT> struct RelationTarget {
using Type = void;
};
template <typename SourceT>
struct RelationTarget<FieldRelation::Gradient<SourceT>> {
using Type = SourceT;
};
template <typename SourceT>
struct RelationTarget<FieldRelation::Divergence<SourceT>> {
using Type = SourceT;
};
template <typename SourceT>
struct RelationTarget<FieldRelation::Curl<SourceT>> {
using Type = SourceT;
};
template <typename QuantityT>
using RelationTargetT =
typename RelationTarget<typename QuantityT::Relation>::Type;
// -------------------------------------------------------------------------
// Field quantities
// -------------------------------------------------------------------------
template <int RankV, ValidRelation RelationT, DiscretizationTag DiscT>
struct Quantity {
using Relation = RelationT;
using Discretization = DiscT;
using Space = typename DiscT::Space;
static constexpr int rankValue = RankV;
static constexpr int familyOrder = DiscT::familyOrder;
static constexpr StorageKind storageKind = StorageKind::finite_element;
static constexpr int staticBlockSize = dynamicBlockSize;
static_assert(
RankV >= 0,
"A field quantity cannot have a negative tensor rank."
);
static_assert(
spaceSupportsRank<
Space,
RankV>,
"This function space cannot represent a quantity of this rank."
);
};
template <ValidRelation RelationT, DiscretizationTag DiscT>
using ScalarQ = Quantity<0, RelationT, DiscT>;
template <ValidRelation RelationT, DiscretizationTag DiscT>
using VectorQ = Quantity<1, RelationT, DiscT>;
struct GlobalScalarQ {
using Relation = FieldRelation::Independent;
static constexpr int rankValue = 0;
static constexpr StorageKind storageKind = StorageKind::global_scalar;
static constexpr int staticBlockSize = 1;
};
template <typename T>
concept FieldQuantity =
requires {
typename T::Relation;
typename T::Discretization;
typename T::Space;
{ T::rankValue } -> std::convertible_to<int>;
{ T::familyOrder } -> std::convertible_to<int>;
{ T::storageKind } -> std::convertible_to<StorageKind>;
{ T::staticBlockSize } -> std::convertible_to<int>;
} && SpaceTag<typename T::Space> &&
T::storageKind == StorageKind::finite_element;
template <typename T>
concept GlobalScalarQuantity =
requires {
typename T::Relation;
{ T::rankValue } -> std::convertible_to<int>;
{ T::storageKind } -> std::convertible_to<StorageKind>;
{ T::staticBlockSize } -> std::convertible_to<int>;
} && T::rankValue == 0 &&
T::storageKind == StorageKind::global_scalar && T::staticBlockSize == 1;
template <typename T>
concept RegisteredQuantity = FieldQuantity<T> || GlobalScalarQuantity<T>;
template <typename QuantityT>
concept DerivedQuantity = FieldQuantity<QuantityT> &&
(!std::same_as<RelationTargetT<QuantityT>, void>);
// -------------------------------------------------------------------------
// Compile-time discretization constraints
// -------------------------------------------------------------------------
template <FieldQuantity FluxT, FieldQuantity PotentialT>
struct RtL2StablePair {
static consteval void validate() {
static_assert(
std::same_as<typename FluxT::Space, RT>,
"The flux in an RT/L2 pair must use Raviart-Thomas elements."
);
static_assert(
std::same_as<typename PotentialT::Space, L2>,
"The potential in an RT/L2 pair must use L2 elements."
);
static_assert(
FluxT::rankValue == 1,
"The flux in an RT/L2 pair must be vector-valued."
);
static_assert(
PotentialT::rankValue == 0,
"The potential in an RT/L2 pair must be scalar-valued."
);
static_assert(
FluxT::familyOrder == PotentialT::familyOrder,
"The MFEM RT and L2 family orders must match."
);
}
};
template <typename... ConstraintTs>
consteval bool validate_constraints(TypeList<ConstraintTs...>) {
(ConstraintTs::validate(), ...);
return true;
}
// -------------------------------------------------------------------------
// Operations applied to quantities inside weak forms
//
// These describe the mathematics. Backend-specific polynomial-order rules
// are provided by field.mfem.
// -------------------------------------------------------------------------
struct FieldOperation {
struct Value { };
struct Gradient { };
struct Divergence { };
struct Curl { };
struct NormalTrace { };
};
template <typename OperationT>
concept FieldOperationTag =
std::same_as<OperationT, FieldOperation::Value> ||
std::same_as<OperationT, FieldOperation::Gradient> ||
std::same_as<OperationT, FieldOperation::Divergence> ||
std::same_as<OperationT, FieldOperation::Curl> ||
std::same_as<OperationT, FieldOperation::NormalTrace>;
template <
RegisteredQuantity QuantityT,
FieldOperationTag OperationT = FieldOperation::Value>
struct Operand {
using Quantity = QuantityT;
using Operation = OperationT;
static_assert(
FieldQuantity<QuantityT> || std::same_as<
OperationT,
FieldOperation::Value>,
"Global scalar quantities support only the value operation."
);
};
template <typename T>
concept FieldOperand =
requires {
typename T::Quantity;
typename T::Operation;
} && RegisteredQuantity<typename T::Quantity> &&
FieldOperationTag<typename T::Operation>;
// -------------------------------------------------------------------------
// Weak-form descriptions
//
// PolicyKeyV associates the form with a runtime quadrature-policy key.
//
// DynamicOrderCountV is the number of polynomial-order contributions that
// cannot yet be derived from registered quantities. For example, a source
// coefficient supplied at runtime contributes one dynamic order.
// -------------------------------------------------------------------------
template <
auto PolicyKeyV,
std::size_t DynamicOrderCountV,
FieldOperand... OperandTs>
struct FormSpec {
static constexpr auto policyKey = PolicyKeyV;
static constexpr std::size_t dynamicOrderCount = DynamicOrderCountV;
using Operands = TypeList<OperandTs...>;
};
template <typename T>
concept FieldForm = requires {
typename T::Operands;
T::policyKey;
{ T::dynamicOrderCount } -> std::convertible_to<std::size_t>;
};
template <typename ListT>
struct IsRegisteredQuantityList : std::false_type { };
template <RegisteredQuantity... QuantityTs>
struct IsRegisteredQuantityList<TypeList<QuantityTs...>> : std::true_type {
};
template <typename ListT>
inline constexpr bool isRegisteredQuantityList =
IsRegisteredQuantityList<ListT>::value;
template <typename ListT> struct IsFieldFormList : std::false_type { };
template <FieldForm... FormTs>
struct IsFieldFormList<TypeList<FormTs...>> : std::true_type { };
template <typename ListT>
inline constexpr bool isFieldFormList = IsFieldFormList<ListT>::value;
} // namespace mean_field::field

View File

@@ -0,0 +1,380 @@
module;
#include <array>
#include <concepts>
#include <cstddef>
#include <memory>
#include <stdexcept>
#include <mfem.hpp>
export module mean_field:field.mfem;
export import :field.registry;
namespace mean_field::field::detail {
template <typename T> inline constexpr bool alwaysFalse = false;
// -------------------------------------------------------------------------
// MFEM finite-element collection construction
// -------------------------------------------------------------------------
template <typename SpaceT> struct FecFor;
template <> struct FecFor<L2> {
static std::unique_ptr<mfem::FiniteElementCollection> make(
int familyOrder,
int dimension
) {
return std::make_unique<mfem::L2_FECollection>(
familyOrder, dimension
);
}
};
template <> struct FecFor<H1> {
static std::unique_ptr<mfem::FiniteElementCollection> make(
int familyOrder,
int dimension
) {
return std::make_unique<mfem::H1_FECollection>(
familyOrder, dimension
);
}
};
template <> struct FecFor<RT> {
static std::unique_ptr<mfem::FiniteElementCollection> make(
int familyOrder,
int dimension
) {
return std::make_unique<mfem::RT_FECollection>(
familyOrder, dimension
);
}
};
template <> struct FecFor<ND> {
static std::unique_ptr<mfem::FiniteElementCollection> make(
int familyOrder,
int dimension
) {
return std::make_unique<mfem::ND_FECollection>(
familyOrder, dimension
);
}
};
// -------------------------------------------------------------------------
// MFEM polynomial-order interpretation
//
// familyOrder is the collection constructor argument.
//
// For RT_p:
// value order = p + 1
// divergence order = p
// normal-trace order = p
//
// This distinction is what allows Disc<RT, p> and Disc<L2, p> to form a
// compatible pair while still giving different value-shape orders.
// -------------------------------------------------------------------------
template <typename OperandT> struct MfemOperandOrder;
template <RegisteredQuantity QuantityT, FieldOperationTag OperationT>
struct MfemOperandOrder<Operand<QuantityT, OperationT>> {
static constexpr int orderValue = []() consteval {
if constexpr (GlobalScalarQuantity<QuantityT>) {
static_assert(
std::same_as<OperationT, FieldOperation::Value>,
"Global scalars support only the value operation."
);
return 0;
} else {
using Space = typename QuantityT::Space;
constexpr int familyOrder = QuantityT::familyOrder;
if constexpr (std::same_as<OperationT, FieldOperation::Value>) {
if constexpr (std::same_as<Space, RT>) {
return familyOrder + 1;
} else {
return familyOrder;
}
} else if constexpr (
std::same_as<OperationT, FieldOperation::Divergence>
) {
static_assert(
std::same_as<Space, RT>,
"Only RT quantities currently support the divergence "
"polynomial-order rule."
);
return familyOrder;
} else if constexpr (
std::same_as<OperationT, FieldOperation::Gradient>
) {
static_assert(
std::same_as<Space, H1>,
"Only H1 quantities currently support the gradient "
"polynomial-order rule."
);
return familyOrder > 0 ? familyOrder - 1 : 0;
} else if constexpr (
std::same_as<OperationT, FieldOperation::Curl>
) {
static_assert(
std::same_as<Space, ND>,
"Only ND quantities currently support the curl "
"polynomial-order rule."
);
return familyOrder > 0 ? familyOrder - 1 : 0;
} else if constexpr (
std::same_as<OperationT, FieldOperation::NormalTrace>
) {
static_assert(
std::same_as<Space, RT>,
"Only RT quantities currently support the normal-trace "
"polynomial-order rule."
);
return familyOrder;
} else {
static_assert(
alwaysFalse<OperationT>,
"Unsupported MFEM field operation."
);
}
}
}();
};
// -------------------------------------------------------------------------
// Static polynomial-order contribution of an entire form
// -------------------------------------------------------------------------
template <typename FormT> struct MfemFormOrder;
template <
auto PolicyKeyV,
std::size_t DynamicOrderCountV,
FieldOperand... OperandTs>
struct MfemFormOrder<
FormSpec<PolicyKeyV, DynamicOrderCountV, OperandTs...>> {
static constexpr int staticOrder =
(MfemOperandOrder<OperandTs>::orderValue + ... + 0);
};
// -------------------------------------------------------------------------
// MFEM vector-dimension and ordering rules
//
// Vector H1/L2 fields are represented using multiple copies of a scalar
// finite-element space. RT and ND elements are intrinsically vector-valued
// and therefore use vdim = 1.
// -------------------------------------------------------------------------
template <FieldQuantity QuantityT> int get_vdim(int spaceDimension) {
if (spaceDimension <= 0) {
throw std::invalid_argument("Space dimension must be positive.");
}
if constexpr (QuantityT::rankValue == 0) {
return 1;
} else if constexpr (
std::same_as<typename QuantityT::Space, H1> ||
std::same_as<typename QuantityT::Space, L2>
) {
return spaceDimension;
} else {
return 1;
}
}
template <FieldQuantity QuantityT>
constexpr mfem::Ordering::Type get_ordering() {
if constexpr (
QuantityT::rankValue == 1 &&
(std::same_as<typename QuantityT::Space, H1> ||
std::same_as<typename QuantityT::Space, L2>)
) {
return mfem::Ordering::byVDIM;
} else {
return mfem::Ordering::byNODES;
}
}
// -------------------------------------------------------------------------
// Quantity-specific MFEM realization
//
// Backend choices that are part of a field definition live here rather
// than leaking into FEM setup or call sites.
// -------------------------------------------------------------------------
template <FieldQuantity QuantityT> struct MfemQuantityTraits {
static std::unique_ptr<mfem::FiniteElementCollection>
make_fec(int dimension) {
return FecFor<typename QuantityT::Space>::make(
QuantityT::familyOrder, dimension
);
}
static constexpr mfem::Ordering::Type ordering =
get_ordering<QuantityT>();
};
template <> struct MfemQuantityTraits<Gravity::Flux> {
static std::unique_ptr<mfem::FiniteElementCollection>
make_fec(int dimension) {
return std::make_unique<mfem::RT_FECollection>(
Gravity::Flux::familyOrder, dimension,
mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL
);
}
static constexpr mfem::Ordering::Type ordering =
mfem::Ordering::byNODES;
};
template <> struct MfemQuantityTraits<Displacement::Vector> {
static std::unique_ptr<mfem::FiniteElementCollection>
make_fec(int dimension) {
return FecFor<H1>::make(
Displacement::Vector::familyOrder, dimension
);
}
static constexpr mfem::Ordering::Type ordering =
mfem::Ordering::byNODES;
};
} // namespace mean_field::field::detail
export namespace mean_field::field {
// -------------------------------------------------------------------------
// User-facing field type
//
// The object itself is currently a zero-cost compile-time descriptor:
//
// Field<Gravity> gravityField;
//
// MFEM construction and typed quadrature-query generation are provided as
// static operations. Runtime ownership can later be added without changing
// Gravity, Displacement, or their form definitions.
// -------------------------------------------------------------------------
template <FieldTag TagT> class Field {
public:
using Tag = TagT;
// ---------------------------------------------------------------------
// MFEM finite-element collection construction
// ---------------------------------------------------------------------
template <FieldQuantity QuantityT>
requires typeListContains<
QuantityT,
typename TagT::Quantities>
static std::unique_ptr<mfem::FiniteElementCollection>
make_fec(int dimension) {
if (dimension <= 0) {
throw std::invalid_argument("Mesh dimension must be positive.");
}
return detail::MfemQuantityTraits<QuantityT>::make_fec(dimension);
}
// ---------------------------------------------------------------------
// MFEM parallel finite-element space construction
//
// The finite-element collection must outlive the returned space.
// ---------------------------------------------------------------------
template <FieldQuantity QuantityT>
requires typeListContains<
QuantityT,
typename TagT::Quantities>
static std::unique_ptr<mfem::ParFiniteElementSpace> make_fespace(
mfem::ParMesh &mesh,
mfem::FiniteElementCollection &finiteElementCollection
) {
return std::make_unique<mfem::ParFiniteElementSpace>(
&mesh, &finiteElementCollection,
detail::get_vdim<QuantityT>(mesh.SpaceDimension()),
detail::MfemQuantityTraits<QuantityT>::ordering
);
}
// ---------------------------------------------------------------------
// Typed quadrature-query construction
//
// geometryWeightOrder is supplied at runtime because it depends on the
// actual element transformation.
//
// dynamicOrders contains the form-specific polynomial orders that are
// not represented by registered compile-time quantities.
//
// Examples:
//
// Density::Form::CenterOfMass:
// { positionOrder }
//
// Gravity source forms need no dynamic orders because density and
// potential are both registered quantities.
//
// The completed base order is stored in Query::base_order, so Policy
// does not need to understand divergence, RT conventions, or individual
// field layouts.
// ---------------------------------------------------------------------
template <FieldForm FormT>
requires typeListContains<
FormT,
typename TagT::FormList>
static constexpr quadrature::Query make_query(
quadrature::QuadratureRole role,
int geometryWeightOrder,
std::array<
int,
FormT::dynamicOrderCount> dynamicOrders = {},
utils::DOMAINS domain = utils::DOMAINS::ALL,
quadrature::MappingKind mapping = quadrature::MappingKind::none
) {
if (geometryWeightOrder < 0) {
throw std::invalid_argument(
"Geometry weight order cannot be negative."
);
}
int baseOrder =
detail::MfemFormOrder<FormT>::staticOrder + geometryWeightOrder;
for (const int dynamicOrder : dynamicOrders) {
if (dynamicOrder < 0) {
throw std::invalid_argument(
"Dynamic polynomial orders cannot be negative."
);
}
baseOrder += dynamicOrder;
}
return {
.term = FormT::policyKey,
.role = role,
.domain = domain,
.mapping = mapping,
.trial_order = 0,
.test_order = 0,
.coefficient_order = 0,
.geometry_weight_order = geometryWeightOrder,
.base_order = baseOrder
};
}
};
static_assert(FieldTag<Gravity>);
static_assert(FieldTag<Displacement>);
static_assert(FieldTag<Density>);
static_assert(FieldTag<BarotropicConstant>);
} // namespace mean_field::field

View File

@@ -0,0 +1,379 @@
module;
#include <concepts>
#include <string_view>
export module mean_field:field.registry;
export import :field.base;
export import :quadrature.policy;
export namespace mean_field::field {
// =========================================================================
// Density
// =========================================================================
struct Density {
static constexpr std::string_view name = "density";
static constexpr int scalarOrder = 2;
struct Scalar final
: ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
static constexpr std::string_view symbol = "ρ";
};
using Quantities = TypeList<Scalar>;
using Constraints = TypeList<>;
static constexpr bool constraintsAreValid =
validate_constraints(Constraints{});
static_assert(constraintsAreValid);
struct Form {
// Density-space mass matrix: (rho, q).
using ProjectionMass = FormSpec<
quadrature::Term::density_projection,
0,
Operand<Scalar>,
Operand<Scalar>>;
// Projection RHS with one runtime coefficient order.
using ProjectionSource = FormSpec<
quadrature::Term::density_projection,
1,
Operand<Scalar>>;
// Density-space contribution to the barotropic EOS closure:
// (rho, q_rho).
using EosClosureMass = FormSpec<
quadrature::Term::eos_closure,
0,
Operand<Scalar>,
Operand<Scalar>>;
// Integral of density over the physical volume.
using MassConservation = FormSpec<
quadrature::Term::mass_conservation,
0,
Operand<Scalar>>;
// The same physical integral used as a nonlinear normalization
// constraint. It has a distinct policy key so solver assembly and
// diagnostics can be overintegrated independently.
using MassNormalization = FormSpec<
quadrature::Term::mass_normalization,
0,
Operand<Scalar>>;
// Integral of rho * x. The combined position-coefficient order is
// supplied as one dynamic order.
using CenterOfMass =
FormSpec<quadrature::Term::center_of_mass, 1, Operand<Scalar>>;
// Integral of rho times the quadratic position tensor. The
// combined tensor-coefficient order is supplied dynamically.
using Quadrupole =
FormSpec<quadrature::Term::quadrupole, 1, Operand<Scalar>>;
using ErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Scalar>,
Operand<Scalar>>;
};
using FormList = TypeList<
Form::ProjectionMass,
Form::ProjectionSource,
Form::EosClosureMass,
Form::MassConservation,
Form::MassNormalization,
Form::CenterOfMass,
Form::Quadrupole,
Form::ErrorNorm>;
};
// =========================================================================
// Gravity
// =========================================================================
struct Gravity {
static constexpr std::string_view name = "gravity";
static constexpr int potentialOrder = 2;
static constexpr int fluxOrder = 2;
struct Potential final
: ScalarQ<FieldRelation::Independent, Disc<L2, potentialOrder>> {
static constexpr std::string_view symbol = "φ";
};
struct Flux final
: VectorQ<FieldRelation::Gradient<Potential>, Disc<RT, fluxOrder>> {
static constexpr std::string_view symbol = "∇φ";
};
using Quantities = TypeList<Potential, Flux>;
using Constraints = TypeList<RtL2StablePair<Flux, Potential>>;
static constexpr bool constraintsAreValid =
validate_constraints(Constraints{});
static_assert(constraintsAreValid);
struct Form {
using HDivMass = FormSpec<
quadrature::Term::gravity_hdiv_mass,
0,
Operand<Flux>,
Operand<Flux>>;
using DivergenceCoupling = FormSpec<
quadrature::Term::gravity_divergence,
0,
Operand<Flux, FieldOperation::Divergence>,
Operand<Potential>>;
using Boundary = FormSpec<
quadrature::Term::gravity_boundary,
0,
Operand<Flux, FieldOperation::NormalTrace>,
Operand<Flux, FieldOperation::NormalTrace>>;
// Density is a registered coefficient field and potential is the
// test field, so the full polynomial order is compile-time data.
using SourceLinear = FormSpec<
quadrature::Term::gravity_source,
0,
Operand<Density::Scalar>,
Operand<Potential>>;
// Mixed density-to-potential projection. Both trial and test
// orders are registered quantities.
using SourceProjection = FormSpec<
quadrature::Term::gravity_source,
0,
Operand<Density::Scalar>,
Operand<Potential>>;
using PotentialErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Potential>,
Operand<Potential>>;
using FluxErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Flux>,
Operand<Flux>>;
};
using FormList = TypeList<
Form::HDivMass,
Form::DivergenceCoupling,
Form::Boundary,
Form::SourceLinear,
Form::SourceProjection,
Form::PotentialErrorNorm,
Form::FluxErrorNorm>;
};
// =========================================================================
// Displacement
// =========================================================================
struct Displacement {
static constexpr std::string_view name = "displacement";
static constexpr int vectorOrder = 3;
struct Vector final
: VectorQ<FieldRelation::Independent, Disc<H1, vectorOrder>> {
static constexpr std::string_view symbol = "d";
};
using Quantities = TypeList<Vector>;
using Constraints = TypeList<>;
static constexpr bool constraintsAreValid =
validate_constraints(Constraints{});
static_assert(constraintsAreValid);
struct Form {
// Harmonic or pseudoelastic interior mesh extension. For the
// initial Laplacian model this is (grad d, grad w).
using MeshExtension = FormSpec<
quadrature::Term::mesh_extension,
0,
Operand<Vector, FieldOperation::Gradient>,
Operand<Vector, FieldOperation::Gradient>>;
using ErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Vector>,
Operand<Vector>>;
};
using FormList = TypeList<Form::MeshExtension, Form::ErrorNorm>;
};
struct BarotropicConstant {
static constexpr std::string_view name = "barotropic_constant";
struct Scalar final : GlobalScalarQ {
static constexpr std::string_view symbol = "C";
};
using Quantities = TypeList<Scalar>;
using Constraints = TypeList<>;
using FormList = TypeList<>;
static constexpr bool constraintsAreValid =
validate_constraints(Constraints{});
static_assert(constraintsAreValid);
};
// =========================================================================
// Specific enthalpy
//
// Pressure is deliberately not registered as an independent field. For a
// barotrope it is derived from h through the EOS, while h supplies the
// continuous H1 trace used to define the isobaric stellar surface.
// =========================================================================
struct Enthalpy {
static constexpr std::string_view name = "specific_enthalpy";
static constexpr int scalarOrder = 3;
struct Scalar final
: ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {
static constexpr std::string_view symbol = "h";
};
using Quantities = TypeList<Scalar>;
using Constraints = TypeList<>;
static constexpr bool constraintsAreValid =
validate_constraints(Constraints{});
static_assert(constraintsAreValid);
struct Form {
// EOS source contribution (rho(h), q_rho). The dynamic order is
// the extra polynomial order introduced by the nonlinear EOS
// beyond the registered order of h. For an n=3 polytrope this is
// 2 * hOrder, making rho(h) cubic in h.
using EosClosureSource = FormSpec<
quadrature::Term::eos_closure,
1,
Operand<Scalar>,
Operand<Density::Scalar>>;
// (h, q_h) contribution to
// h + phi - Psi_rotation - C = 0.
using EquilibriumEnthalpy = FormSpec<
quadrature::Term::hydrostatic_equilibrium,
0,
Operand<Scalar>,
Operand<Scalar>>;
// (phi, q_h) contribution to hydrostatic equilibrium.
using EquilibriumGravity = FormSpec<
quadrature::Term::hydrostatic_equilibrium,
0,
Operand<Gravity::Potential>,
Operand<Scalar>>;
// (Psi_rotation, q_h). The rotation-potential order is supplied
// dynamically because it belongs to runtime rotation data.
using EquilibriumRotation = FormSpec<
quadrature::Term::hydrostatic_equilibrium,
1,
Operand<Scalar>>;
// (C, q_h), where C is spatially constant.
using EquilibriumConstant = FormSpec<
quadrature::Term::hydrostatic_equilibrium,
0,
Operand<BarotropicConstant::Scalar>,
Operand<Scalar>>;
// Boundary trace form available for weak enforcement, testing, or
// a future multiplier formulation of h|Gamma_star = 0.
using IsobaricSurface = FormSpec<
quadrature::Term::isobaric_surface,
0,
Operand<Scalar>,
Operand<Scalar>>;
// Integral of P(h). The dynamic order is the extra EOS order
// beyond the registered order of h.
using PressureIntegral = FormSpec<
quadrature::Term::pressure_integral,
1,
Operand<Scalar>>;
// Weak pressure force in the displacement test space:
//
// -int P(h) I : grad(w) dV
//
// which is equivalent to -int P(h) div(w) dV. The dynamic order
// is the extra EOS order beyond the registered order of h. For an
// n=3 polytrope this is 3 * hOrder, making P(h) quartic in h.
using PressureForce = FormSpec<
quadrature::Term::pressure_force,
1,
Operand<Scalar>,
Operand<Displacement::Vector, FieldOperation::Gradient>>;
using ErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Scalar>,
Operand<Scalar>>;
};
using FormList = TypeList<
Form::EosClosureSource,
Form::EquilibriumEnthalpy,
Form::EquilibriumGravity,
Form::EquilibriumRotation,
Form::EquilibriumConstant,
Form::IsobaricSurface,
Form::PressureIntegral,
Form::PressureForce,
Form::ErrorNorm>;
};
// =========================================================================
// Field definition concept
// =========================================================================
template <typename T>
concept FieldTag =
requires {
typename T::Quantities;
typename T::Constraints;
typename T::FormList;
{ T::name } -> std::convertible_to<std::string_view>;
} && isRegisteredQuantityList<typename T::Quantities> &&
isFieldFormList<typename T::FormList>;
static_assert(FieldTag<Gravity>);
static_assert(FieldTag<Displacement>);
static_assert(FieldTag<Density>);
static_assert(FieldTag<Enthalpy>);
static_assert(FieldTag<BarotropicConstant>);
static_assert(DerivedQuantity<Gravity::Flux>);
static_assert(std::same_as<
RelationTargetT<Gravity::Flux>,
Gravity::Potential>);
} // namespace mean_field::field

View File

@@ -25,4 +25,4 @@ export namespace mean_field::integrators {
private:
const mapping::DomainMapper &m_map;
};
}
} // namespace mean_field::integrators

View File

@@ -1,14 +1,19 @@
module;
#include <mfem.hpp>
export module mean_field:integrators.centrifugal;
import :mapping.domain_mapper;
export import :mapping.domain_mapper;
export namespace mean_field::integrators {
class CentrifugalForceIntegrator : public mfem::BlockNonlinearFormIntegrator {
class CentrifugalForceIntegrator
: public mfem::BlockNonlinearFormIntegrator {
public:
CentrifugalForceIntegrator(const mapping::DomainMapper& map, const mfem::Vector& omega);
CentrifugalForceIntegrator(
const mapping::DomainMapper &map,
const mfem::Vector &omega
);
void SetOmega(const mfem::Vector& omega);
void SetOmega(const mfem::Vector &omega);
void SetIntegrationRule(const mfem::IntegrationRule &ir);
void AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -25,8 +30,9 @@ export namespace mean_field::integrators {
) override;
private:
const mapping::DomainMapper& m_map;
const mapping::DomainMapper &m_map;
mfem::Vector m_omega;
const mfem::IntegrationRule *m_ir = nullptr;
};
}
} // namespace mean_field::integrators

View File

@@ -3,26 +3,32 @@ module;
export module mean_field:integrators.coriolis;
import :mapping.domain_mapper;
export namespace mean_field::integrators {
class CoriolisIntegrator : public mfem::BlockNonlinearFormIntegrator {
public:
CoriolisIntegrator(const mapping::DomainMapper& map, const mfem::Vector& omega);
CoriolisIntegrator(
const mapping::DomainMapper &map,
const mfem::Vector &omega
);
void AssembleElementVector(const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec) override;
void AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) override;
void AssembleElementGrad(const mfem::Array<const mfem::FiniteElement*> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats) override;
void AssembleElementGrad(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) override;
private:
const mapping::DomainMapper& m_map;
const mapping::DomainMapper &m_map;
mfem::Vector m_omega;
mfem::DenseMatrix m_omega_mat;
};
}
} // namespace mean_field::integrators

View File

@@ -1,14 +1,29 @@
module;
#include <cstdint>
#include <mfem.hpp>
export module mean_field:integrators.gravity;
import :mapping.domain_mapper;
export namespace mean_field::integrators {
class GravityForceIntegrator : public mfem::BlockNonlinearFormIntegrator {
public:
GravityForceIntegrator(const mapping::DomainMapper& map, const mfem::GridFunction& phi);
enum class GravityForceJacobianMode : std::uint8_t {
minimal,
field_coupled,
exact
};
void SetPotential(const mfem::GridFunction& phi);
class GravityMomentumIntegrator
: public mfem::BlockNonlinearFormIntegrator {
public:
explicit GravityMomentumIntegrator(
const mapping::DomainMapper &map,
GravityForceJacobianMode jacobian_mode =
GravityForceJacobianMode::field_coupled
);
void SetJacobianMode(GravityForceJacobianMode jacobian_mode);
void SetIntegrationRule(const mfem::IntegrationRule &integration_rule);
[[nodiscard]] GravityForceJacobianMode GetJacobianMode() const;
void AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -16,7 +31,6 @@ export namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) override;
void AssembleElementGrad(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
@@ -24,9 +38,9 @@ export namespace mean_field::integrators {
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) override;
private:
const mapping::DomainMapper& m_map;
const mfem::GridFunction* m_phi;
const mapping::DomainMapper &m_map;
GravityForceJacobianMode m_jacobian_mode;
const mfem::IntegrationRule *m_integration_rule{nullptr};
};
}
} // namespace mean_field::integrators

View File

@@ -4,9 +4,10 @@ export module mean_field:integrators.mass_continuity;
import :mapping.domain_mapper;
export namespace mean_field::integrators {
class ContinuityVolumeIntegrator : public mfem::BlockNonlinearFormIntegrator {
class ContinuityVolumeIntegrator
: public mfem::BlockNonlinearFormIntegrator {
public:
explicit ContinuityVolumeIntegrator(const mapping::DomainMapper& map);
explicit ContinuityVolumeIntegrator(const mapping::DomainMapper &map);
void AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -21,13 +22,14 @@ export namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) override;
private:
const mapping::DomainMapper& m_map;
const mapping::DomainMapper &m_map;
};
class ContinuityFaceIntegrator : public mfem::BlockNonlinearFormIntegrator {
public:
explicit ContinuityFaceIntegrator(const mapping::DomainMapper& map);
explicit ContinuityFaceIntegrator(const mapping::DomainMapper &map);
void AssembleFaceVector(
const mfem::Array<const mfem::FiniteElement *> &el1,
@@ -44,19 +46,20 @@ export namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) override;
private:
static bool skip_face(const mfem::FaceElementTransformations& Tr);
static bool skip_face(const mfem::FaceElementTransformations &Tr);
static double compute_u_n(
const mfem::Vector& v_dofs,
const mfem::Vector& shape_v_minus,
const mfem::Vector& n_unit,
const mfem::Vector &v_dofs,
const mfem::Vector &shape_v_minus,
const mfem::Vector &n_unit,
int dof_v_minus,
int dim
);
private:
const mapping::DomainMapper& m_map;
const mapping::DomainMapper &m_map;
};
}
} // namespace mean_field::integrators

View File

@@ -1,36 +1,47 @@
module;
#include <mfem.hpp>
#include "xad_promote_polyfill.h"
#include <XAD/XAD.hpp>
#include <mfem.hpp>
export module mean_field:integrators.pressure_gradient;
import :mapping.domain_mapper;
import :utils.misc;
export namespace mean_field::integrators {
template <utils::is_xad EOS_T>
class PressureGradientIntegrator : public mfem::BlockNonlinearFormIntegrator {
class PressureGradientIntegrator
: public mfem::BlockNonlinearFormIntegrator {
public:
PressureGradientIntegrator(const mapping::DomainMapper& map, utils::EOS_P<EOS_T> eos);
PressureGradientIntegrator(
const mapping::DomainMapper &map,
utils::EOS_P<EOS_T> eos
);
void AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) override;
void AssembleElementGrad(
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) override;
void AssembleElementVector(const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec) override;
void AssembleElementGrad(const mfem::Array<const mfem::FiniteElement*> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats) override;
private:
const mapping::DomainMapper& m_map;
const mapping::DomainMapper &m_map;
utils::EOS_P<EOS_T> m_eos;
};
template <utils::is_xad EOS_T>
PressureGradientIntegrator<EOS_T>::PressureGradientIntegrator(
const mapping::DomainMapper& map,
const mapping::DomainMapper &map,
utils::EOS_P<EOS_T> eos
) : m_map(map), m_eos(std::move(eos)) {}
)
: m_map(map),
m_eos(std::move(eos)) {
}
template <utils::is_xad EOS_T>
void PressureGradientIntegrator<EOS_T>::AssembleElementVector(
@@ -43,16 +54,16 @@ export namespace mean_field::integrators {
return;
}
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector& rho_dofs = *elfun[1];
const mfem::Vector &rho_dofs = *elfun[1];
mfem::Vector& r_v = *elvec[0];
mfem::Vector &r_v = *elvec[0];
r_v.SetSize(dof_v * dim);
r_v = 0.0;
if (elvec[1]) {
@@ -63,10 +74,11 @@ export namespace mean_field::integrators {
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
mfem::Vector shape_rho(dof_rho);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -76,13 +88,15 @@ export namespace mean_field::integrators {
fe_rho->CalcShape(ip, shape_rho);
double rho_val = 0.0;
for (int i = 0; i < dof_rho; ++i) rho_val += rho_dofs(i) * shape_rho(i);
for (int i = 0; i < dof_rho; ++i)
rho_val += rho_dofs(i) * shape_rho(i);
// Guard against negative density from Newton solver overshoots
if (rho_val < 1e-15) rho_val = 1e-15;
if (rho_val < 1e-15)
rho_val = 1e-15;
// Evaluate the exact Equation of State Pressure
EOS_T x_rho = rho_val;
EOS_T x_rho = rho_val;
double P_val = m_eos(x_rho, EOS_T(0.0)).value();
for (int i = 0; i < dof_v; ++i) {
@@ -95,36 +109,40 @@ export namespace mean_field::integrators {
template <utils::is_xad EOS_T>
void PressureGradientIntegrator<EOS_T>::AssembleElementGrad(
const mfem::Array<const mfem::FiniteElement*> &el,
const mfem::Array<const mfem::FiniteElement *> &el,
mfem::ElementTransformation &Tr,
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
const mfem::FiniteElement* fe_v = el[0];
const mfem::FiniteElement* fe_rho = el[1];
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const int dof_v = fe_v->GetDof();
const int dof_rho = fe_rho->GetDof();
const int dim = Tr.GetSpaceDim();
const mfem::Vector& rho_dofs = *elfun[1];
const mfem::Vector &rho_dofs = *elfun[1];
mfem::DenseMatrix* dv_dv = elmats(0, 0);
mfem::DenseMatrix* dv_drho = elmats(0, 1);
mfem::DenseMatrix *dv_dv = elmats(0, 0);
mfem::DenseMatrix *dv_drho = elmats(0, 1);
if (dv_dv) *dv_dv = 0.0;
if (dv_drho) *dv_drho = 0.0;
if (!dv_drho) return;
if (dv_dv)
*dv_dv = 0.0;
if (dv_drho)
*dv_drho = 0.0;
if (!dv_drho)
return;
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
mfem::Vector shape_rho(dof_rho);
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
const mfem::IntegrationRule *ir =
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
for (int q = 0; q < ir->GetNPoints(); ++q) {
using Scalar = EOS_T::value_type;
xad::Tape<Scalar> tape;
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
@@ -140,29 +158,32 @@ export namespace mean_field::integrators {
for (int i = 0; i < dof_rho; ++i) {
x_rho += rho_dofs(i) * shape_rho(i);
}
if (x_rho < 1e-15) x_rho = EOS_T(1e-15);
if (x_rho < 1e-15)
x_rho = EOS_T(1e-15);
EOS_T x_P = m_eos(x_rho, EOS_T(0.0));
tape.registerOutput(x_P);
x_P.setAdjoint(1.0);
tape.computeAdjoints();
double dP_drho = x_rho.getAdjoint();
double dP_drho = x_rho.getAdjoint();
double debug_K = 1.5;
double debug_n = 3.0;
double analytic_dp = debug_K * (1.0 + 1.0 / debug_n) * std::pow(xad::value(x_rho), 1.0 / debug_n);
double debug_K = 1.5;
double debug_n = 3.0;
double analytic_dp = debug_K * (1.0 + 1.0 / debug_n) *
std::pow(xad::value(x_rho), 1.0 / debug_n);
double ad_err = std::abs(dP_drho - analytic_dp);
double ad_err = std::abs(dP_drho - analytic_dp);
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
int row = i + c * dof_v;
for (int j = 0; j < dof_rho; ++j) {
int col = j;
double term = dshape_v_phys(i, c) * dP_drho * shape_rho(j);
double term =
dshape_v_phys(i, c) * dP_drho * shape_rho(j);
(*dv_drho)(row, col) -= term * weight;
}
}
}
}
}
}
} // namespace mean_field::integrators

View File

@@ -4,9 +4,13 @@ export module mean_field:integrators.viscosity;
import :mapping.domain_mapper;
export namespace mean_field::integrators {
class ViscosityIntegrator : public mfem::BlockNonlinearFormIntegrator {
class ViscosityIntegrator : public mfem::BlockNonlinearFormIntegrator {
public:
ViscosityIntegrator(const mapping::DomainMapper& map, double mu, int quad_boost);
ViscosityIntegrator(
const mapping::DomainMapper &map,
double mu,
int quad_boost
);
void SetMu(const double mu);
@@ -23,10 +27,11 @@ export namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) override;
private:
const mapping::DomainMapper& m_map;
const mapping::DomainMapper &m_map;
double m_mu;
int m_quad_boost;
};
}
} // namespace mean_field::integrators

View File

@@ -10,7 +10,7 @@ export namespace mean_field::mapping {
public:
MappedScalarCoefficient(
const DomainMapper &map,
mfem::Coefficient &coeff,
Coefficient &coeff,
COORDINATE_SPACE coord_space = COORDINATE_SPACE::PHYSICAL
);
@@ -21,14 +21,14 @@ export namespace mean_field::mapping {
private:
static double eval_at_point(
mfem::Coefficient &c,
Coefficient &c,
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
);
private:
const DomainMapper &m_map;
mfem::Coefficient &m_coeff;
Coefficient &m_coeff;
COORDINATE_SPACE m_coord_space;
};
@@ -42,29 +42,37 @@ export namespace mean_field::mapping {
MappedDiffusionCoefficient(
const DomainMapper &map,
mfem::MatrixCoefficient &sigma
MatrixCoefficient &sigma
);
void Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override;
void Eval(
mfem::DenseMatrix &K,
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) override;
private:
const DomainMapper &m_map;
mfem::Coefficient *m_scalar;
mfem::MatrixCoefficient *m_tensor;
MatrixCoefficient *m_tensor;
};
class MappedVectorCoefficient : public mfem::VectorCoefficient {
public:
MappedVectorCoefficient(
const DomainMapper &map,
mfem::VectorCoefficient &coeff
VectorCoefficient &coeff
);
void Eval(mfem::Vector &V, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override;
void Eval(
mfem::Vector &V,
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) override;
private:
const DomainMapper &m_map;
mfem::VectorCoefficient &m_coeff;
VectorCoefficient &m_coeff;
};
class PhysicalPositionFunctionCoefficient : public mfem::Coefficient {
@@ -76,7 +84,10 @@ export namespace mean_field::mapping {
Func f
);
double Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override;
double Eval(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) override;
private:
Func m_f;
@@ -85,11 +96,18 @@ export namespace mean_field::mapping {
class MappedHDivMassCoefficient final : public mfem::MatrixCoefficient {
public:
MappedHDivMassCoefficient(const DomainMapper& map, const int dim);
MappedHDivMassCoefficient(
const DomainMapper &map,
const int dim
);
void Eval(
mfem::DenseMatrix &matrix,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point
) override;
void Eval(mfem::DenseMatrix& matrix, mfem::ElementTransformation& transformation, const mfem::IntegrationPoint& integration_point) override;
private:
const DomainMapper& m_map;
const DomainMapper &m_map;
};
}
} // namespace mean_field::mapping

View File

@@ -0,0 +1,46 @@
module;
#include <mfem.hpp>
export module mean_field:mapping.compactification;
export import :mapping.types;
export namespace mean_field::mapping::compactification {
struct ExteriorMapInput {
const mfem::Vector &reference_position;
const mfem::Vector &displaced_position;
const mfem::DenseMatrix &displacement_jacobian;
double compactification_coordinate;
const mfem::Vector &compactification_coordinate_gradient;
};
struct ExteriorMapResult {
mfem::Vector physical_position;
mfem::DenseMatrix mapping_jacobian;
};
struct ExteriorMapDirection {
const mfem::Vector &displaced_position_variation;
const mfem::DenseMatrix &displacement_jacobian_variation;
};
struct ExteriorMapVariation {
mfem::Vector physical_position_variation;
mfem::DenseMatrix mapping_jacobian_variation;
};
class ExteriorDomainMap {
public:
virtual ~ExteriorDomainMap() = default;
[[nodiscard]] virtual MappingStatus Evaluate(
const ExteriorMapInput &input,
ExteriorMapResult &result
) const = 0;
[[nodiscard]] virtual MappingStatus EvaluateVariation(
const ExteriorMapInput &input,
const ExteriorMapResult &result,
const ExteriorMapDirection &direction,
ExteriorMapVariation &variation
) const = 0;
[[nodiscard]] virtual std::string_view GetName() const noexcept = 0;
};
} // namespace mean_field::mapping::compactification

View File

@@ -0,0 +1,50 @@
module;
#include <mfem.hpp>
export module mean_field:mapping.kelvin;
export import :mapping.compactification;
export import :mapping.types;
export import :mapping.compactification.options;
export namespace mean_field::mapping::compactification {
class KelvinCompactification final : public ExteriorDomainMap {
public:
explicit KelvinCompactification(
options::KelvinCompactificationOptions options
);
[[nodiscard]] MappingStatus Evaluate(
const ExteriorMapInput &input,
ExteriorMapResult &result
) const override;
[[nodiscard]] MappingStatus EvaluateVariation(
const ExteriorMapInput &input,
const ExteriorMapResult &result,
const ExteriorMapDirection &direction,
ExteriorMapVariation &variation
) const override;
[[nodiscard]] std::string_view GetName() const noexcept override;
[[nodiscard]] double GetReferenceStellarRadius() const noexcept;
[[nodiscard]] double GetReferenceInfinityRadius() const noexcept;
[[nodiscard]] double GetCoordinateTolerance() const noexcept;
private:
struct RadialFactors {
double coordinate;
double computational_radius;
double scale;
double scale_derivative;
};
[[nodiscard]] MappingStatus ComputeRadialFactors(
double compactification_coordinate,
RadialFactors &factors
) const;
options::KelvinCompactificationOptions m_options;
};
} // namespace mean_field::mapping::compactification

View File

@@ -0,0 +1,9 @@
export module mean_field:mapping.compactification.options;
export namespace mean_field::mapping::compactification::options {
struct KelvinCompactificationOptions {
double r_star_ref{1.0};
double r_inf_ref{2.0};
double coordinate_tolerance{1.0e-12};
};
} // namespace mean_field::mapping::compactification::options

View File

@@ -3,51 +3,302 @@ module;
#include "mean_field.h"
export module mean_field:mapping.domain_mapper;
export import :mapping.types;
import :mapping.compactification;
import :utils.user;
export namespace mean_field::mapping {
enum class FaceElementSide : uint8_t { element_1, element_2 };
class ElementDisplacementData {
public:
ElementDisplacementData(
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs,
mfem::Ordering::Type ordering = mfem::Ordering::byNODES
);
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
[[nodiscard]] const mfem::DenseMatrix &GetDofMatrix() const noexcept;
[[nodiscard]] int GetDimension() const noexcept;
[[nodiscard]] int GetDofCount() const noexcept;
[[nodiscard]] mfem::Ordering::Type GetOrdering() const noexcept;
private:
const mfem::FiniteElement *m_element;
mfem::DenseMatrix m_dof_matrix;
int m_dimension;
mfem::Ordering::Type m_ordering;
};
struct CompactificationPointData {
double coordinate{0.0};
mfem::Vector coordinate_gradient;
};
[[nodiscard]] ElementDisplacementData
ElementDisplacementDataFromElementVDofs(
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs
);
class ElementCompactificationData {
public:
ElementCompactificationData(
const mfem::FiniteElement &element,
const mfem::Vector &dofs
);
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
[[nodiscard]] const mfem::Vector &GetDofs() const noexcept;
[[nodiscard]] int GetDofCount() const noexcept;
private:
const mfem::FiniteElement *m_element;
mfem::Vector m_dofs;
};
struct ElementMappingData {
const ElementDisplacementData &displacement;
const ElementCompactificationData &compactification;
};
class DomainMapperStateless {
public:
class Workspace {
public:
explicit Workspace(int dimension = 3);
void SetDimension(int dimension);
[[nodiscard]] int GetDimension() const noexcept;
private:
friend class DomainMapperStateless;
int m_dimension;
mfem::Vector m_shape;
mfem::DenseMatrix m_mesh_dshape;
mfem::Vector m_field_value;
mfem::DenseMatrix m_field_jacobian;
mfem::Vector m_compactification_shape;
mfem::DenseMatrix m_compactification_dshape;
CompactificationPointData m_compactification_point;
mfem::Vector m_reference_normal;
mfem::Vector m_mapped_normal;
mfem::DenseMatrix m_full_element_jacobian;
mfem::Vector m_vector_temp;
mfem::DenseMatrix m_matrix_temp_1;
mfem::DenseMatrix m_matrix_temp_2;
compactification::ExteriorMapResult m_exterior_result;
compactification::ExteriorMapVariation m_exterior_variation;
};
public:
DomainMapperStateless(
utils::DomainMapperStatelessOptions options,
std::unique_ptr<const compactification::ExteriorDomainMap>
exterior_map
);
DomainMapperStateless(const DomainMapperStateless &) = delete;
DomainMapperStateless &
operator=(const DomainMapperStateless &) = delete;
DomainMapperStateless(DomainMapperStateless &&) = default;
DomainMapperStateless &operator=(DomainMapperStateless &&) = default;
[[nodiscard]] MappingStatus EvaluatePoint(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
MappingPointContext &context
) const;
[[nodiscard]] MappingStatus EvaluateVolume(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
VolumeMappingContext &context
) const;
[[nodiscard]] MappingStatus EvaluateFace(
const ElementMappingData &element_data,
mfem::FaceElementTransformations &transformation,
FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
FaceMappingContext &context
) const;
[[nodiscard]] MappingStatus 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;
[[nodiscard]] MappingStatus 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;
[[nodiscard]] MappingStatus EvaluateFaceVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::FaceElementTransformations &transformation,
FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
const FaceMappingContext &base_context,
Workspace &workspace,
FaceMappingVariation &variation
) const;
[[nodiscard]] bool IsCompactifiedElement(
const mfem::ElementTransformation &transformation
) const noexcept;
[[nodiscard]] int GetDimension() const noexcept;
[[nodiscard]] int GetVacuumElementAttribute() const noexcept;
[[nodiscard]] const compactification::ExteriorDomainMap &
GetExteriorMap() const noexcept;
private:
void ValidateElementData(const ElementMappingData &element_data) const;
void EvaluateField(
const ElementDisplacementData &field,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
mfem::Vector &value,
mfem::DenseMatrix &jacobian
) const;
[[nodiscard]] MappingStatus EvaluateCompactificationCoordinate(
const ElementCompactificationData &compactification,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
CompactificationPointData &point_data
) const;
[[nodiscard]] static mfem::ElementTransformation &
SelectFaceElementTransformation(
mfem::FaceElementTransformations &transformation,
FaceElementSide side
);
[[nodiscard]] static const mfem::IntegrationPoint &
SelectFaceElementIntegrationPoint(
mfem::FaceElementTransformations &transformation,
FaceElementSide side
);
utils::DomainMapperStatelessOptions m_options;
std::unique_ptr<const compactification::ExteriorDomainMap>
m_exterior_map;
};
class DomainMapper {
public:
struct VolumeQuadratureContext {
mfem::DenseMatrix J_inv;
double detJ;
double weight;
};
struct FaceQuadratureContext {
mfem::Vector normal;
double ds;
double v_dot_n_scale;
};
public:
explicit DomainMapper(const double r_star_ref, const double r_inf_ref);
explicit DomainMapper(
const double r_star_ref,
const double r_inf_ref
);
explicit DomainMapper(const mfem::GridFunction &d, const double r_star_ref, const double r_inf_ref);
explicit DomainMapper(
const mfem::GridFunction &d,
const double r_star_ref,
const double r_inf_ref
);
[[nodiscard]] bool is_vacuum(const mfem::ElementTransformation &T) const;
[[nodiscard]] bool
is_vacuum(const mfem::ElementTransformation &T) const;
void SetDisplacement(const mfem::GridFunction &d);
[[nodiscard]] bool IsIdentity() const;
[[nodiscard]] bool HasCompactification() const noexcept;
[[nodiscard]] bool HasDisplacementField() const noexcept;
[[nodiscard]] bool CalcIsIdentity() const;
void ResetDisplacement();
void ComputeJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &J) const;
void ComputeJacobian(
mfem::ElementTransformation &T,
mfem::DenseMatrix &J
) const;
double ComputeDetJ(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const;
double ComputeDetJ(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) const;
void ComputeMappedDiffusionTensor(mfem::ElementTransformation &T, mfem::DenseMatrix &D) const;
void ComputeMappedDiffusionTensor(
mfem::ElementTransformation &T,
mfem::DenseMatrix &D
) const;
void ComputeInverseJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &JInv) const;
void ComputeInverseJacobian(
mfem::ElementTransformation &T,
mfem::DenseMatrix &JInv
) const;
VolumeQuadratureContext GetQuadratureContext(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const;
VolumeQuadratureContext GetQuadratureContext(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) const;
FaceQuadratureContext GetFaceQuadratureContext(mfem::FaceElementTransformations &T, const mfem::IntegrationPoint &ip) const;
FaceQuadratureContext GetFaceQuadratureContext(
mfem::FaceElementTransformations &T,
const mfem::IntegrationPoint &ip
) const;
void GetPhysicalPoint(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip, mfem::Vector &x_phys) const;
void GetPhysicalPoint(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip,
mfem::Vector &x_phys
) const;
void GetVectorValue(const int i, const mfem::IntegrationPoint &ip, mfem::Vector &val) 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;
@@ -63,9 +314,17 @@ export namespace mean_field::mapping {
private:
void InitAllScratchSpaces() const;
void ApplyKelvinMapping(const mfem::Vector &x_ref, mfem::Vector &x_phys) 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 ComputeKelvinJacobian(
const mfem::Vector &x_ref,
const mfem::Vector &x_disp,
const mfem::DenseMatrix &J_D,
mfem::DenseMatrix &J
) const;
void InvalidateCache() const;
@@ -98,6 +357,8 @@ export namespace mean_field::mapping {
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

View File

@@ -0,0 +1,87 @@
module;
#include <mfem.hpp>
export module mean_field:mapping.transformations;
export import :mapping.types;
export namespace mean_field::mapping {
void MapHDivFluxToPhysical(
const MappingPointContext &context,
const mfem::Vector &reference_flux,
mfem::Vector &physical_flux
);
void MapPhysicalFluxToHDivReference(
const MappingPointContext &context,
const mfem::Vector &physical_flux,
mfem::Vector &reference_flux
);
void MapReferenceGradientToPhysical(
const MappingPointContext &context,
const mfem::Vector &reference_gradient,
mfem::Vector &physical_gradient
);
void MapPhysicalGradientToReference(
const MappingPointContext &context,
const mfem::Vector &physical_gradient,
mfem::Vector &reference_gradient
);
void MapReferenceVectorGradientToPhysical(
const MappingPointContext &context,
const mfem::DenseMatrix &reference_gradient,
mfem::DenseMatrix &physical_gradient
);
void MapPhysicalVectorGradientToReference(
const MappingPointContext &context,
const mfem::DenseMatrix &physical_gradient,
mfem::DenseMatrix &reference_gradient
);
[[nodiscard]] double MapHDivDivergenceToPhysical(
const MappingPointContext &context,
double reference_divergence
);
void ComputeHDivMassTensor(
const MappingPointContext &context,
mfem::DenseMatrix &mass_tensor
);
void ComputeScalarDiffusionTensor(
const MappingPointContext &context,
mfem::DenseMatrix &diffusion_tensor
);
void MapHCurlFieldToPhysical(
const MappingPointContext &context,
const mfem::Vector &reference_field,
mfem::Vector &physical_field
);
void MapPhysicalFieldToHCurlReference(
const MappingPointContext &context,
const mfem::Vector &physical_field,
mfem::Vector &reference_field
);
void MapHCurlCurlToPhysical(
const MappingPointContext &context,
const mfem::Vector &reference_curl,
mfem::Vector &physical_curl
);
void MapPhysicalCurlToHCurlReference(
const MappingPointContext &context,
const mfem::Vector &physical_curl,
mfem::Vector &reference_curl
);
void ComputeHCurlMassTensor(
const MappingPointContext &context,
mfem::DenseMatrix &mass_tensor
);
void ComputeHCurlCurlTensor(
const MappingPointContext &context,
mfem::DenseMatrix &curl_tensor
);
void ComputeHDivMassTensorVariation(
const MappingPointContext &context,
const MappingPointVariation &variation,
mfem::DenseMatrix &mass_tensor_variation
);
} // namespace mean_field::mapping

View File

@@ -1,10 +1,77 @@
module;
#include <cstdint>
#include <mfem.hpp>
export module mean_field:mapping.types;
namespace mean_field::mapping {
enum class COORDINATE_SPACE : uint8_t {
PHYSICAL,
REFERENCE
export namespace mean_field::mapping {
enum class COORDINATE_SPACE : uint8_t { PHYSICAL, REFERENCE };
enum class MappingStatus : uint8_t {
valid,
invalid_dimension,
non_finite_input,
invalid_reference_radius,
at_compactified_infinity,
outside_reference_domain,
non_finite_result,
non_positive_determinant
};
}
struct VolumeQuadratureContext {
mfem::DenseMatrix J_inv;
double detJ;
double weight;
};
struct FaceQuadratureContext {
mfem::Vector normal;
double ds;
double v_dot_n_scale;
};
struct MappingPointContext {
mfem::Vector reference_position;
mfem::Vector displaced_position;
mfem::Vector physical_position;
mfem::DenseMatrix displacement_jacobian;
mfem::DenseMatrix mapping_jacobian;
mfem::DenseMatrix inverse_mapping_jacobian;
double mapping_determinant{0.0};
bool compactified{false};
};
struct VolumeMappingContext {
MappingPointContext mapping;
VolumeQuadratureContext quadrature;
};
struct FaceMappingContext {
MappingPointContext mapping;
FaceQuadratureContext quadrature;
mfem::Vector reference_normal;
double reference_surface_weight{0.0};
double physical_surface_weight{0.0};
};
struct MappingPointVariation {
mfem::Vector displacement_variation;
mfem::Vector physical_position_variation;
mfem::DenseMatrix displacement_jacobian_variation;
mfem::DenseMatrix mapping_jacobian_variation;
mfem::DenseMatrix inverse_mapping_jacobian_variation;
double mapping_determinant_variation{0.0};
};
struct VolumeMappingVariation {
MappingPointVariation mapping;
mfem::DenseMatrix inverse_element_jacobian_variation;
double weight_variation{0.0};
};
struct FaceMappingVariation {
MappingPointVariation mapping;
mfem::Vector physical_normal_variation;
double physical_surface_weight_variation{0.0};
double normal_flux_scale_variation{0.0};
};
} // namespace mean_field::mapping

View File

@@ -5,11 +5,18 @@ export import :utils.misc;
export import :utils.user;
export import :utils.domain;
export import :physics.gravity;
export import :physics.solid_body;
export import :physics.barotrope;
export import :physics.contexts;
export import :boundary.contexts;
export import :analysis.integral;
export import :mapping.domain_mapper;
export import :mapping.coefficients;
export import :mapping.compactification;
export import :mapping.kelvin;
export import :mapping.transformations;
export import :mapping.types;
export import :mapping.compactification.options;
export import :integrators.advection;
export import :integrators.centrifugal;
export import :integrators.gravity;
@@ -19,3 +26,22 @@ export import :integrators.pressure_gradient;
export import :integrators.viscosity;
export import :quadrature.policy;
export import :quadrature.mfem;
export import :solver.fields;
export import :utils.blocks;
export import :operators.gravity_field;
export import :operators.gravity_field_jacobian;
export import :operators.kernels.gravity_field;
export import :operators.prepared_gravity_source;
export import :operators.prepared_hdiv_mass;
export import :operators.context.gravity_field;
export import :field.base;
export import :field.registry;
export import :field.mfem;
export import :operators.kernels.barotropic_closure;
export import :operators.prepared_barotropic_closure;
export import :operators.context.barotropic_closure_linearization;
export import :physics.rigid_rotation;
export import :operators.kernels.hydrostatic_equilibrium;
export import :operators.context.hydrostatic_equilibrium;
export import :operators.prepared_hydrostatic_equilibrium;
export import :operators.kernels.pressure_force;

View File

@@ -0,0 +1,76 @@
module;
#include <cstdint>
#include <mfem.hpp>
export module mean_field:operators.context.barotropic_closure_linearization;
export import :fem;
export import :mapping.domain_mapper;
export import :operators.prepared_barotropic_closure;
export import :physics.barotrope;
export namespace mean_field::operators::context::barotropic {
struct BarotropicClosureRevisions final {
std::uint64_t density = 0;
std::uint64_t enthalpy = 0;
std::uint64_t displacement = 0;
[[nodiscard]] bool
operator==(const BarotropicClosureRevisions &) const noexcept = default;
};
class BarotropicClosureLinearizationContext final {
public:
BarotropicClosureLinearizationContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope
);
void Prepare(
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementTrue,
const BarotropicClosureRevisions &revisions
);
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] bool MatchesRevisions(
const BarotropicClosureRevisions &revisions
) const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
[[nodiscard]] const BarotropicClosureRevisions &GetRevisions() const;
[[nodiscard]] const mfem::Vector &GetBaseDensityTrue() const;
[[nodiscard]] const mfem::Vector &GetBaseEnthalpyTrue() const;
[[nodiscard]] const mfem::Vector &GetDisplacementTrue() const;
[[nodiscard]]
const PreparedBarotropicClosureOperator &GetOperator() const noexcept;
void BuildResidual(mfem::Vector &residual) const;
private:
void VerifyPrepared() const;
const fem::FEM &m_f;
PreparedBarotropicClosureOperator m_operator;
mfem::Vector m_baseDensityTrue;
mfem::Vector m_baseEnthalpyTrue;
mfem::Vector m_displacementTrue;
BarotropicClosureRevisions m_revisions;
std::uint64_t m_preparationCount = 0;
bool m_isPrepared = false;
};
} // namespace mean_field::operators::context::barotropic

View File

@@ -0,0 +1,157 @@
module;
#include <compare>
#include <cstdint>
#include <memory>
#include <mfem.hpp>
export module mean_field:operators.context.gravity_field;
export import :fem;
export import :mapping.domain_mapper;
export import :operators.prepared_gravity_source;
export import :operators.prepared_hdiv_mass;
export namespace mean_field::operators::context::gravity_field {
template <typename Tag> struct Revision {
std::uint64_t value{0};
constexpr auto operator<=>(const Revision &) const = default;
};
struct DiscretizationRevisionTag { };
struct DisplacementRevisionTag { };
struct DensityRevisionTag { };
struct GravityGradientRevisionTag { };
struct GravityPotentialRevisionTag { };
using DiscretizationRevision = Revision<DiscretizationRevisionTag>;
using DisplacementRevision = Revision<DisplacementRevisionTag>;
using DensityRevision = Revision<DensityRevisionTag>;
using GravityGradientRevision = Revision<GravityGradientRevisionTag>;
using GravityPotentialRevision = Revision<GravityPotentialRevisionTag>;
struct GravityFieldRevisions {
DiscretizationRevision discretization;
DisplacementRevision displacement;
DensityRevision density;
GravityGradientRevision gravity_gradient;
GravityPotentialRevision gravity_potential;
};
struct GravityFieldStateView {
const mfem::Vector &density;
const mfem::Vector &displacement;
const mfem::Vector &gravity_gradient;
const mfem::Vector &gravity_potential;
};
struct GravityFieldGeometryPreparation {
bool reconstructed_operators{false};
bool rebuilt_mass_operator{false};
bool rebuilt_source_operator{false};
bool refreshed_variation_state{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return reconstructed_operators || rebuilt_mass_operator ||
rebuilt_source_operator || refreshed_variation_state;
}
};
class GravityFieldGeometryContext {
public:
GravityFieldGeometryContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
);
GravityFieldGeometryContext(const GravityFieldGeometryContext &) =
delete;
GravityFieldGeometryContext &
operator=(const GravityFieldGeometryContext &) = delete;
GravityFieldGeometryContext(GravityFieldGeometryContext &&) = delete;
GravityFieldGeometryContext &
operator=(GravityFieldGeometryContext &&) = delete;
GravityFieldGeometryPreparation Prepare(
const mfem::Vector &displacement_true,
DiscretizationRevision discretization_revision,
DisplacementRevision displacement_revision
);
[[nodiscard]] const PreparedMappedHDivMassOperator &
GetMassOperator() const;
[[nodiscard]] const PreparedMappedGravitySourceOperator &
GetSourceOperator() const;
[[nodiscard]] const mfem::Vector &GetDisplacement() const;
[[nodiscard]] DiscretizationRevision
GetDiscretizationRevision() const noexcept;
[[nodiscard]] DisplacementRevision
GetDisplacementRevision() const noexcept;
[[nodiscard]] bool IsPrepared() const noexcept;
private:
const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper;
std::unique_ptr<PreparedMappedHDivMassOperator> m_mass_operator;
std::unique_ptr<PreparedMappedGravitySourceOperator> m_source_operator;
mfem::Vector m_displacement_true;
DiscretizationRevision m_discretization_revision;
DisplacementRevision m_displacement_revision;
bool m_is_prepared{false};
};
struct GravityFieldPreparationReport {
GravityFieldGeometryPreparation geometry;
bool updated_density{false};
bool updated_gravity_gradient{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return geometry.DidAnyWork() || updated_density ||
updated_gravity_gradient;
}
};
class GravityFieldLinearizationContext {
public:
GravityFieldLinearizationContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
);
GravityFieldLinearizationContext(
const GravityFieldLinearizationContext &
) = delete;
GravityFieldLinearizationContext &
operator=(const GravityFieldLinearizationContext &) = delete;
GravityFieldLinearizationContext(GravityFieldLinearizationContext &&) =
delete;
GravityFieldLinearizationContext &
operator=(GravityFieldLinearizationContext &&) = delete;
GravityFieldPreparationReport Prepare(
const GravityFieldStateView &state,
const GravityFieldRevisions &revisions
);
[[nodiscard]] const GravityFieldGeometryContext &
GetGeometryContext() const;
[[nodiscard]] const mfem::Vector &GetDensity() const;
[[nodiscard]] const mfem::Vector &GetGravityGradient() const;
[[nodiscard]] const GravityFieldRevisions &GetRevisions() const;
[[nodiscard]] bool IsPrepared() const noexcept;
private:
const fem::FEM &m_fem;
GravityFieldGeometryContext m_geometry_context;
mfem::Vector m_density_true;
mfem::Vector m_gravity_gradient_true;
GravityFieldRevisions m_revisions;
bool m_is_prepared{false};
};
} // namespace mean_field::operators::context::gravity_field

View File

@@ -0,0 +1,154 @@
module;
#include <compare>
#include <cstdint>
#include <mfem.hpp>
export module mean_field:operators.context.hydrostatic_equilibrium;
export import :fem;
export import :mapping.domain_mapper;
export namespace mean_field::operators::context::hydrostatic {
template <typename Tag> struct DependencyStamp {
std::uint64_t identity{0};
std::uint64_t revision{0};
[[nodiscard]] constexpr bool
CanFollow(const DependencyStamp &prepared) const noexcept {
return identity != prepared.identity ||
revision >= prepared.revision;
}
constexpr auto operator<=>(const DependencyStamp &) const = default;
};
struct DiscretizationDependencyTag { };
struct EnthalpyDependencyTag { };
struct GravityPotentialDependencyTag { };
struct DisplacementDependencyTag { };
struct RotationDependencyTag { };
struct BernoulliConstantDependencyTag { };
using DiscretizationDependency =
DependencyStamp<DiscretizationDependencyTag>;
using EnthalpyDependency = DependencyStamp<EnthalpyDependencyTag>;
using GravityPotentialDependency =
DependencyStamp<GravityPotentialDependencyTag>;
using DisplacementDependency = DependencyStamp<DisplacementDependencyTag>;
using RotationDependency = DependencyStamp<RotationDependencyTag>;
using BernoulliConstantDependency =
DependencyStamp<BernoulliConstantDependencyTag>;
struct HydrostaticEquilibriumDependencies {
DiscretizationDependency discretization;
EnthalpyDependency enthalpy;
GravityPotentialDependency gravityPotential;
DisplacementDependency displacement;
RotationDependency rotation;
BernoulliConstantDependency bernoulliConstant;
constexpr auto
operator<=>(const HydrostaticEquilibriumDependencies &) const = default;
};
struct HydrostaticEquilibriumStateView {
const mfem::Vector &enthalpy;
const mfem::Vector &gravityPotential;
const mfem::Vector &displacement;
double bernoulliConstant{0.0};
};
struct HydrostaticPreparationReport {
bool preparedStaticDependencies{false};
bool preparedGeometryState{false};
bool preparedRotationDependencies{false};
bool preparedBaseState{false};
bool updatedEnthalpy{false};
bool updatedGravityPotential{false};
bool updatedDisplacement{false};
bool updatedBernoulliConstant{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return preparedStaticDependencies || preparedGeometryState ||
preparedRotationDependencies || preparedBaseState;
}
};
struct HydrostaticPreparationStatistics {
std::uint64_t staticPreparations{0};
std::uint64_t geometryPreparations{0};
std::uint64_t rotationPreparations{0};
std::uint64_t baseStatePreparations{0};
constexpr auto
operator<=>(const HydrostaticPreparationStatistics &) const = default;
};
class HydrostaticEquilibriumContext {
public:
HydrostaticEquilibriumContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper
);
HydrostaticEquilibriumContext(const HydrostaticEquilibriumContext &) =
delete;
HydrostaticEquilibriumContext &
operator=(const HydrostaticEquilibriumContext &) = delete;
HydrostaticEquilibriumContext(HydrostaticEquilibriumContext &&) =
delete;
HydrostaticEquilibriumContext &
operator=(HydrostaticEquilibriumContext &&) = delete;
HydrostaticPreparationReport Prepare(
const HydrostaticEquilibriumStateView &state,
const HydrostaticEquilibriumDependencies &dependencies
);
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] bool MatchesDependencies(
const HydrostaticEquilibriumDependencies &dependencies
) const noexcept;
[[nodiscard]] const HydrostaticEquilibriumDependencies &
GetDependencies() const;
[[nodiscard]] const HydrostaticPreparationStatistics &
GetPreparationStatistics() const noexcept;
[[nodiscard]] const mfem::Vector &GetBaseEnthalpyTrue() const;
[[nodiscard]] const mfem::Vector &GetBaseGravityPotentialTrue() const;
[[nodiscard]] const mfem::Vector &GetDisplacementTrue() const;
[[nodiscard]] double GetBernoulliConstant() const;
private:
void VerifyPrepared() const;
const fem::FEM &m_f;
const mapping::DomainMapperStateless &m_domainMapper;
mfem::Vector m_baseEnthalpyTrue;
mfem::Vector m_baseGravityPotentialTrue;
mfem::Vector m_displacementTrue;
double m_bernoulliConstant{0.0};
HydrostaticEquilibriumDependencies m_dependencies;
HydrostaticPreparationStatistics m_statistics;
bool m_isPrepared{false};
};
} // namespace mean_field::operators::context::hydrostatic

View File

@@ -0,0 +1,147 @@
module;
#include <cstdint>
#include <memory>
#include <mfem.hpp>
export module mean_field:operators.gravity_field;
export import :fem;
export import :mapping.domain_mapper;
export import :operators.gravity_field_jacobian;
export import :operators.context.gravity_field;
export namespace mean_field::operators {
enum class GravityResidualBlock : std::uint8_t {
gradient_equation = 0,
poisson_equation = 1,
count = 2
};
constexpr int
gravity_residual_block_index(const GravityResidualBlock block) noexcept {
return static_cast<int>(block);
}
inline constexpr int gravity_residual_block_count =
gravity_residual_block_index(GravityResidualBlock::count);
class GravityFieldOperator final : public mfem::Operator {
public:
GravityFieldOperator(
fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
context::gravity_field::GravityFieldLinearizationContext
&linearization_context,
const mfem::Array<int> &state_true_offsets,
GravityFieldJacobianOperator &jacobian
);
context::gravity_field::GravityFieldPreparationReport Prepare(
const mfem::Vector &state,
const context::gravity_field::GravityFieldRevisions &revisions
);
void Mult(
const mfem::Vector &state,
mfem::Vector &residual
) const override;
Operator &GetGradient(const mfem::Vector &state) const override;
[[nodiscard]] const mfem::Array<int> &
GetStateTrueOffsets() const noexcept;
[[nodiscard]] const mfem::Array<int> &
GetResidualTrueOffsets() const noexcept;
[[nodiscard]] context::gravity_field::GravityFieldLinearizationContext &
GetLinearizationContext() noexcept;
[[nodiscard]] const context::gravity_field::
GravityFieldLinearizationContext &
GetLinearizationContext() const noexcept;
void ApplyGravityUnknowns(
const mfem::Vector &gravity_gradient,
const mfem::Vector &gravity_potential,
const context::gravity_field::GravityFieldGeometryContext
&geometry_context,
mfem::Vector &action
) const;
void ApplyDensitySource(
const mfem::Vector &density,
const context::gravity_field::GravityFieldGeometryContext
&geometry_context,
mfem::Vector &action
) const;
private:
fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper;
context::gravity_field::GravityFieldLinearizationContext
&m_linearization_context;
mfem::Array<int> m_state_true_offsets;
mfem::Array<int> m_residual_true_offsets;
GravityFieldJacobianOperator &m_jacobian;
};
class ReducedGravityFieldOperator final : public mfem::Operator {
public:
ReducedGravityFieldOperator(
GravityFieldOperator &gravity_field_operator,
context::gravity_field::GravityFieldGeometryContext
&gravity_field_geometry_context,
const mfem::Vector &displacement
);
ReducedGravityFieldOperator(const ReducedGravityFieldOperator &) =
delete;
ReducedGravityFieldOperator &
operator=(const ReducedGravityFieldOperator &) = delete;
ReducedGravityFieldOperator(ReducedGravityFieldOperator &&) = delete;
ReducedGravityFieldOperator &
operator=(ReducedGravityFieldOperator &&) = delete;
void SetDisplacement(const mfem::Vector &displacement);
[[nodiscard]] const mfem::Vector &GetDisplacement() const;
void BuildRightHandSide(
const mfem::Vector &density,
mfem::Vector &right_hand_side
) const;
void Mult(
const mfem::Vector &gravity_state,
mfem::Vector &action
) const override;
[[nodiscard]] GravityFieldOperator &GetGravityFieldOperator() noexcept;
[[nodiscard]] const GravityFieldOperator &
GetGravityFieldOperator() const noexcept;
[[nodiscard]] context::gravity_field::GravityFieldGeometryContext &
GetGeometryContext() noexcept;
[[nodiscard]] const context::gravity_field::
GravityFieldGeometryContext &
GetGeometryContext() const noexcept;
[[nodiscard]] const mfem::Array<int> &
GetGravityTrueOffsets() const noexcept;
private:
void ValidateDisplacement(const mfem::Vector &displacement) const;
void ValidateDensity(const mfem::Vector &density) const;
void ValidateGravityState(const mfem::Vector &gravity_state) const;
private:
GravityFieldOperator &m_gravity_field_operator;
mfem::Array<int> m_gravity_true_offsets;
context::gravity_field::GravityFieldGeometryContext
&m_gravity_field_geometry_context;
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,38 @@
module;
#include <mfem.hpp>
export module mean_field:operators.gravity_field_jacobian;
export import :fem;
export import :mapping.domain_mapper;
export import :operators.context.gravity_field;
export namespace mean_field::operators {
class GravityFieldJacobianOperator final : public mfem::Operator {
public:
GravityFieldJacobianOperator(
fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
const context::gravity_field::GravityFieldLinearizationContext
&linearization_context,
const mfem::Array<int> &state_true_offsets,
const mfem::Array<int> &residual_true_offsets
);
void Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const override;
[[nodiscard]] const context::gravity_field::
GravityFieldLinearizationContext &
GetLinearizationContext() const noexcept;
private:
fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper;
const context::gravity_field::GravityFieldLinearizationContext
&m_linearization_context;
mfem::Array<int> m_state_true_offsets;
mfem::Array<int> m_residual_true_offsets;
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,51 @@
module;
#include <mfem.hpp>
export module mean_field:operators.kernels.barotropic_closure;
export import :fem;
export import :mapping.domain_mapper;
export import :physics.barotrope;
export namespace mean_field::operators::kernels {
void apply_barotropic_closure(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &densityTrue,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residual
);
void apply_barotropic_closure_density_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
);
void apply_barotropic_closure_enthalpy_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
);
void apply_barotropic_closure_displacement_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
);
} // namespace mean_field::operators::kernels

View File

@@ -0,0 +1,42 @@
module;
#include <mfem.hpp>
export module mean_field:operators.kernels.gravity_field;
export import :mapping.domain_mapper;
export import :fem;
export namespace mean_field::operators::kernels {
void apply_mapped_hdiv_mass(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
const mfem::Vector &gravity_gradient_true,
const mfem::Vector &displacement_true,
mfem::Vector &action
);
void apply_mapped_source(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
const mfem::Vector &density_true,
const mfem::Vector &displacement_true,
mfem::Vector &action
);
void apply_mapped_hdiv_mass_variation(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
const mfem::Vector &gravity_gradient_true,
const mfem::Vector &displacement_true,
const mfem::Vector &displacement_variation_true,
mfem::Vector &action
);
void apply_mapped_source_variation(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
const mfem::Vector &density_true,
const mfem::Vector &displacement_true,
const mfem::Vector &displacement_variation_true,
mfem::Vector &action_variation
);
} // namespace mean_field::operators::kernels

View File

@@ -0,0 +1,73 @@
module;
#include <mfem.hpp>
export module mean_field:operators.kernels.hydrostatic_equilibrium;
export import :fem;
export import :mapping.domain_mapper;
export import :physics.rigid_rotation;
export namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &potentialTrue,
const mfem::Vector &displacementTrue,
double bernoulliConstant,
mfem::Vector &residual
);
void apply_hydrostatic_equilibrium_enthalpy_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
);
void apply_hydrostatic_equilibrium_potential_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &potentialVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
);
void apply_hydrostatic_equilibrium_constant_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
double constantVariation,
const mfem::Vector &displacementTrue,
mfem::Vector &action
);
void apply_hydrostatic_equilibrium_displacement_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseDisplacementTrue,
double baseBernoulliConstant,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
);
void apply_hydrostatic_equilibrium_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseDisplacementTrue,
double baseBernoulliConstant,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &potentialVariationTrue,
double constantVariation,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
);
} // namespace mean_field::operators::kernels

View File

@@ -0,0 +1,20 @@
module;
#include <mfem.hpp>
export module mean_field:operators.kernels.pressure_force;
export import :fem;
export import :mapping.domain_mapper;
export import :physics.barotrope;
export namespace mean_field::operators::kernels {
void apply_pressure_force_residual(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residualTrue
);
} // namespace mean_field::operators::kernels

View File

@@ -0,0 +1,90 @@
module;
#include <cstdint>
#include <mfem.hpp>
#include <vector>
export module mean_field:operators.prepared_barotropic_closure;
export import :fem;
export import :mapping.domain_mapper;
export import :physics.barotrope;
export namespace mean_field::operators {
class PreparedBarotropicClosureOperator final : public mfem::Operator {
public:
PreparedBarotropicClosureOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope
);
void Prepare(
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementTrue
);
void Mult(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) const;
void Mult(
const mfem::Vector &combinedVariation,
mfem::Vector &action
) const override;
void BuildResidual(mfem::Vector &residual) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
[[nodiscard]] int GetDensitySize() const noexcept;
[[nodiscard]] int GetEnthalpySize() const noexcept;
private:
void VerifyPrepared() const;
void Mult(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &enthalpyVariationTrue,
mfem::Vector &action
) const;
struct ElementPAData {
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *enthalpyDofTransformation{nullptr};
mfem::DenseMatrix densityBasis;
mfem::DenseMatrix enthalpyBasis;
mfem::Vector weightedResidual;
mfem::Vector quadratureWeights;
mfem::Vector weightedEnthalpyDerivative;
};
const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper;
const physics::PolytropicBarotrope &m_barotrope;
std::vector<ElementPAData> m_elements;
mfem::Vector m_baseDensityTrue;
mfem::Vector m_baseEnthalpyTrue;
mfem::Vector m_baseDisplacementTrue;
int m_densitySize{0};
int m_enthalpySize{0};
std::uint64_t m_preparationCount{0};
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,61 @@
module;
#include <cstdint>
#include <memory>
#include <mfem.hpp>
#include <vector>
export module mean_field:operators.prepared_gravity_source;
export import :fem;
export import :mapping.domain_mapper;
export namespace mean_field::operators {
class PreparedMappedGravitySourceOperator final : public mfem::Operator {
public:
PreparedMappedGravitySourceOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
);
void Prepare(const mfem::Vector &displacement_true);
void Mult(
const mfem::Vector &density_true,
mfem::Vector &action
) const override;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
void MultTranspose(
const mfem::Vector &potential_true,
mfem::Vector &action
) const override;
private:
struct ElementPAData {
int element_id{-1};
mfem::Array<int> density_dofs;
mfem::Array<int> potential_dofs;
mfem::DofTransformation *density_dof_transformation{nullptr};
mfem::DofTransformation *potential_dof_transformation{nullptr};
// Rows are quadrature points; columns are element DOFs.
mfem::DenseMatrix density_basis;
mfem::DenseMatrix potential_basis;
// Contains quadrature weight, mesh Jacobian, mapped Jacobian,
// and 4*pi*G.
mfem::Vector quadrature_data;
};
const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper;
mfem::Array<int> m_stellar_marker;
std::vector<ElementPAData> m_elements;
std::uint64_t m_preparation_count{0};
bool m_is_prepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,40 @@
module;
#include <cstdint>
#include <memory>
#include <mfem.hpp>
export module mean_field:operators.prepared_hdiv_mass;
export import :fem;
export import :mapping.domain_mapper;
export namespace mean_field::operators {
class PreparedMappedHDivMassOperator final : public mfem::Operator {
public:
PreparedMappedHDivMassOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
);
void Prepare(const mfem::Vector &displacement_true);
void Mult(
const mfem::Vector &gravity_gradient_true,
mfem::Vector &action
) const override;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
private:
const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domain_mapper;
mfem::Array<int> m_stellar_marker;
mfem::Array<int> m_vacuum_marker;
std::unique_ptr<mfem::MatrixCoefficient> m_stellar_mass_coefficient;
std::unique_ptr<mfem::MatrixCoefficient> m_vacuum_mass_coefficient;
std::unique_ptr<mfem::ParBilinearForm> m_mass_form;
std::uint64_t m_preparation_count{0};
bool m_is_prepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,278 @@
module;
#include <compare>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <vector>
#include <mfem.hpp>
export module mean_field:operators.prepared_hydrostatic_equilibrium;
export import :fem;
export import :mapping.domain_mapper;
export import :operators.context.hydrostatic_equilibrium;
export import :physics.rigid_rotation;
export namespace mean_field::operators {
struct PreparedHydrostaticEquilibriumReport {
context::hydrostatic::HydrostaticPreparationReport contextReport;
bool updatedRotation{false};
bool preparedAlgebraicJacobianBlocks{false};
bool preparedDisplacementJacobianData{false};
bool preparedResidual{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return contextReport.DidAnyWork() || updatedRotation ||
preparedAlgebraicJacobianBlocks ||
preparedDisplacementJacobianData || preparedResidual;
}
};
struct PreparedHydrostaticAlgebraicJacobianStatistics {
std::uint64_t preparations{0};
std::uint64_t enthalpyApplications{0};
std::uint64_t gravityPotentialApplications{0};
std::uint64_t bernoulliConstantApplications{0};
std::uint64_t combinedApplications{0};
constexpr auto operator<=>(
const PreparedHydrostaticAlgebraicJacobianStatistics &
) const = default;
};
struct PreparedHydrostaticDisplacementJacobianStatistics {
std::uint64_t preparations{0};
std::uint64_t applications{0};
constexpr auto operator<=>(
const PreparedHydrostaticDisplacementJacobianStatistics &
) const = default;
};
struct PreparedHydrostaticCompleteJacobianStatistics {
std::uint64_t applications{0};
constexpr auto operator<=>(
const PreparedHydrostaticCompleteJacobianStatistics &
) const = default;
};
enum class HydrostaticJacobianInputBlock : int {
enthalpy = 0,
gravityPotential = 1,
bernoulliConstant = 2,
displacement = 3
};
class HydrostaticJacobianBlockLayout final {
public:
explicit HydrostaticJacobianBlockLayout(const fem::FEM &f);
[[nodiscard]] int Offset(HydrostaticJacobianInputBlock block) const;
[[nodiscard]] int Size(HydrostaticJacobianInputBlock block) const;
[[nodiscard]] int GetTotalSize() const noexcept;
[[nodiscard]] int GetResidualSize() const noexcept;
private:
int m_enthalpySize{0};
int m_gravityPotentialSize{0};
int m_displacementSize{0};
int m_totalSize{0};
int m_residualSize{0};
};
class PreparedHydrostaticEquilibriumOperator final {
public:
PreparedHydrostaticEquilibriumOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper
);
PreparedHydrostaticEquilibriumOperator(
const PreparedHydrostaticEquilibriumOperator &
) = delete;
PreparedHydrostaticEquilibriumOperator &
operator=(const PreparedHydrostaticEquilibriumOperator &) = delete;
PreparedHydrostaticEquilibriumOperator(
PreparedHydrostaticEquilibriumOperator &&
) = delete;
PreparedHydrostaticEquilibriumOperator &
operator=(PreparedHydrostaticEquilibriumOperator &&) = delete;
PreparedHydrostaticEquilibriumReport Prepare(
const context::hydrostatic::HydrostaticEquilibriumStateView &state,
const context::hydrostatic::HydrostaticEquilibriumDependencies
&dependencies,
const physics::RigidRotation &rotation
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyEnthalpyJacobianAction(
const mfem::Vector &enthalpyVariation,
mfem::Vector &action
) const;
void ApplyGravityPotentialJacobianAction(
const mfem::Vector &gravityPotentialVariation,
mfem::Vector &action
) const;
void ApplyBernoulliConstantJacobianAction(
double bernoulliConstantVariation,
mfem::Vector &action
) const;
void ApplyAlgebraicJacobianAction(
const mfem::Vector &enthalpyVariation,
const mfem::Vector &gravityPotentialVariation,
double bernoulliConstantVariation,
mfem::Vector &action
) const;
void ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const;
void ApplyCompleteJacobianAction(
const mfem::Vector &enthalpyVariation,
const mfem::Vector &gravityPotentialVariation,
double bernoulliConstantVariation,
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] const context::hydrostatic::
HydrostaticPreparationStatistics &
GetContextPreparationStatistics() const noexcept;
[[nodiscard]] std::uint64_t
GetResidualPreparationCount() const noexcept;
[[nodiscard]] std::uint64_t
GetResidualApplicationCount() const noexcept;
[[nodiscard]] const PreparedHydrostaticAlgebraicJacobianStatistics &
GetAlgebraicJacobianStatistics() const noexcept;
[[nodiscard]] const PreparedHydrostaticDisplacementJacobianStatistics &
GetDisplacementJacobianStatistics() const noexcept;
[[nodiscard]] const PreparedHydrostaticCompleteJacobianStatistics &
GetCompleteJacobianStatistics() const noexcept;
[[nodiscard]] std::size_t GetStellarElementCount() const noexcept;
[[nodiscard]] const fem::FEM &GetFEM() const noexcept;
private:
struct ElementPAData {
int elementId{-1};
mfem::Array<int> enthalpyDofs;
mfem::Array<int> gravityPotentialDofs;
mfem::Array<int> displacementDofs;
mfem::DofTransformation *enthalpyDofTransformation{nullptr};
mfem::DofTransformation *gravityPotentialDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
// Rows are quadrature points and columns are element DOFs.
mfem::DenseMatrix enthalpyBasis;
mfem::DenseMatrix gravityPotentialBasis;
// Rows are quadrature points and columns are physical components.
mfem::DenseMatrix physicalPositions;
mfem::Vector quadratureWeights;
std::vector<mapping::VolumeMappingContext> baseMappingContexts;
std::optional<mapping::ElementDisplacementData>
baseDisplacementData;
std::optional<mapping::ElementCompactificationData>
compactificationData;
mfem::Vector rotationPotential;
mfem::DenseMatrix rotationGradient;
mfem::Vector hydrostaticImbalance;
mfem::Vector weightedResidual;
// Geometry-dependent algebraic Jacobian blocks.
mfem::DenseMatrix enthalpyJacobian;
mfem::DenseMatrix gravityPotentialJacobian;
mfem::Vector bernoulliConstantJacobian;
};
void PrepareStaticPlan();
void PrepareGeometry();
void PrepareAlgebraicJacobianBlocks();
void PrepareRotation();
void PrepareBaseState();
void FinalizeDisplacementJacobianPreparation();
void AssembleCachedResidual();
void VerifyPrepared() const;
const fem::FEM &m_fem;
const mapping::DomainMapperStateless &m_domainMapper;
context::hydrostatic::HydrostaticEquilibriumContext m_context;
std::optional<physics::RigidRotation> m_rotation;
std::vector<ElementPAData> m_elements;
mfem::Vector m_cachedResidual;
std::uint64_t m_residualPreparationCount{0};
mutable std::uint64_t m_residualApplicationCount{0};
mutable PreparedHydrostaticAlgebraicJacobianStatistics
m_algebraicJacobianStatistics;
mutable PreparedHydrostaticDisplacementJacobianStatistics
m_displacementJacobianStatistics;
mutable PreparedHydrostaticCompleteJacobianStatistics
m_completeJacobianStatistics;
bool m_isPrepared{false};
};
class PreparedHydrostaticEquilibriumJacobianOperator final
: public mfem::Operator {
public:
PreparedHydrostaticEquilibriumJacobianOperator(
const fem::FEM &f,
const PreparedHydrostaticEquilibriumOperator &preparedOperator
);
void Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const override;
[[nodiscard]] const HydrostaticJacobianBlockLayout &
GetLayout() const noexcept;
private:
HydrostaticJacobianBlockLayout m_layout;
const PreparedHydrostaticEquilibriumOperator &m_preparedOperator;
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,173 @@
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,4 +1,5 @@
module;
#include <memory>
#include <mfem.hpp>
export module mean_field:physics.contexts;
@@ -23,6 +24,6 @@ export namespace mean_field::physics {
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

@@ -10,7 +10,10 @@ export namespace mean_field::physics {
mfem::ParGridFunction gradPhi;
mfem::ParGridFunction phi;
explicit GravitySolution(fem::FEM& fem): gradPhi(fem.RT_fes.get()), phi(fem.L2_fes.get()) {}
explicit GravitySolution(fem::FEM &fem)
: gradPhi(fem.gravityFluxFes.get()),
phi(fem.gravityPotentialFes.get()) {
}
};
GravitySolution grav_potential(
@@ -20,6 +23,13 @@ export namespace mean_field::physics {
bool phi_warm = false
);
GravitySolution grav_potential_new(
fem::FEM &f,
const utils::Args &args,
const mfem::GridFunction &rho,
const mfem::GridFunction &displacement
);
mfem::GridFunction get_potential(
fem::FEM &fem,
const utils::Args &args,
@@ -40,6 +50,4 @@ export namespace mean_field::physics {
);
void update_stiffness_matrix(fem::FEM &fem);
}
} // namespace mean_field::physics

View File

@@ -0,0 +1,127 @@
module;
#include <cmath>
#include <mfem.hpp>
export module mean_field:physics.rigid_rotation;
export namespace mean_field::physics {
class RigidRotation final {
public:
RigidRotation(
const mfem::Vector &angularVelocity,
const mfem::Vector &center
)
: m_angularVelocity(angularVelocity),
m_center(center) {
MFEM_VERIFY(
m_angularVelocity.Size() == 3,
"RigidRotation requires a three-dimensional "
"angular-velocity vector."
);
MFEM_VERIFY(
m_center.Size() == 3,
"RigidRotation requires a three-dimensional center."
);
for (int component = 0; component < 3; ++component) {
MFEM_VERIFY(
std::isfinite(m_angularVelocity(component)),
"RigidRotation received a non-finite "
"angular-velocity component."
);
MFEM_VERIFY(
std::isfinite(m_center(component)),
"RigidRotation received a non-finite center component."
);
}
}
[[nodiscard]] double
potential(const mfem::Vector &physicalPosition) const {
MFEM_VERIFY(
physicalPosition.Size() == 3,
"RigidRotation::potential requires a "
"three-dimensional position."
);
const double relativeX = physicalPosition(0) - m_center(0);
const double relativeY = physicalPosition(1) - m_center(1);
const double relativeZ = physicalPosition(2) - m_center(2);
const double crossX = m_angularVelocity(1) * relativeZ -
m_angularVelocity(2) * relativeY;
const double crossY = m_angularVelocity(2) * relativeX -
m_angularVelocity(0) * relativeZ;
const double crossZ = m_angularVelocity(0) * relativeY -
m_angularVelocity(1) * relativeX;
return 0.5 * (crossX * crossX + crossY * crossY + crossZ * crossZ);
}
[[nodiscard]] double potential_directional_derivative(
const mfem::Vector &physicalPosition,
const mfem::Vector &physicalPositionVariation
) const {
MFEM_VERIFY(
physicalPosition.Size() == 3,
"RigidRotation derivative requires a "
"three-dimensional position."
);
MFEM_VERIFY(
physicalPositionVariation.Size() == 3,
"RigidRotation derivative requires a "
"three-dimensional direction."
);
double angularVelocitySquared = 0.0;
double angularVelocityDotPosition = 0.0;
for (int component = 0; component < 3; ++component) {
const double relativePosition =
physicalPosition(component) - m_center(component);
angularVelocitySquared +=
m_angularVelocity(component) * m_angularVelocity(component);
angularVelocityDotPosition +=
m_angularVelocity(component) * relativePosition;
}
double derivative = 0.0;
for (int component = 0; component < 3; ++component) {
const double relativePosition =
physicalPosition(component) - m_center(component);
const double gradientComponent =
angularVelocitySquared * relativePosition -
angularVelocityDotPosition * m_angularVelocity(component);
derivative +=
gradientComponent * physicalPositionVariation(component);
}
return derivative;
}
[[nodiscard]] const mfem::Vector &angular_velocity() const noexcept {
return m_angularVelocity;
}
[[nodiscard]] const mfem::Vector &center() const noexcept {
return m_center;
}
private:
mfem::Vector m_angularVelocity;
mfem::Vector m_center;
};
} // namespace mean_field::physics

View File

@@ -5,5 +5,8 @@ export module mean_field:physics.solid_body;
export import :fem;
export namespace mean_field::physics {
double compute_moment_of_inertia(const fem::FEM &fem, const mfem::GridFunction &rho_ref);
double compute_moment_of_inertia(
const fem::FEM &fem,
const mfem::GridFunction &rho_ref
);
}

View File

@@ -4,93 +4,116 @@ module;
export module mean_field:quadrature.mfem;
export import :quadrature.policy;
export import :integrators.centrifugal;
import :field.mfem;
export namespace mean_field::quadrature {
struct MfemRule {
Resolution resolution;
const mfem::IntegrationRule* integration_rule;
const mfem::IntegrationRule *integration_rule;
};
class RuleFactory {
public:
explicit RuleFactory(
Policy policy
);
MfemRule get(
const Query& query,
mfem::Geometry::Type geometry
) const;
MfemRule get(
Term term,
explicit RuleFactory(Policy policy);
MfemRule
get(const Query &query,
mfem::Geometry::Type geometry) const;
MfemRule
get(Term term,
QuadratureRole role,
mfem::Geometry::Type geometry,
int base_order,
utils::DOMAINS domain = utils::DOMAINS::ALL,
MappingKind mapping = MappingKind::none
) const;
MappingKind mapping = MappingKind::none) const;
Resolution configure_gravity_hdiv_mass(
mfem::VectorFEMassIntegrator& integrator,
mfem::VectorFEMassIntegrator &integrator,
QuadratureRole role,
const mfem::FiniteElement& element,
const mfem::ElementTransformation& transformation,
const mfem::FiniteElement &element,
const mfem::ElementTransformation &transformation,
utils::DOMAINS domain = utils::DOMAINS::ALL,
MappingKind mapping = MappingKind::none
MappingKind mapping = MappingKind::none
) const;
Resolution configure_gravity_divergence(
mfem::VectorFEDivergenceIntegrator& integrator,
mfem::VectorFEDivergenceIntegrator &integrator,
QuadratureRole role,
const mfem::FiniteElement& trial_element,
const mfem::FiniteElement& test_element,
const mfem::ElementTransformation& transformation,
const mfem::FiniteElement &trial_element,
const mfem::FiniteElement &test_element,
const mfem::ElementTransformation &transformation,
utils::DOMAINS domain = utils::DOMAINS::ALL,
MappingKind mapping = MappingKind::none
MappingKind mapping = MappingKind::none
) const;
Resolution configure_gravity_boundary(
mfem::VectorFEBoundaryFluxLFIntegrator& integrator,
mfem::VectorFEBoundaryFluxLFIntegrator &integrator,
QuadratureRole role,
const mfem::FiniteElement& boundary_element,
const mfem::FiniteElement &boundary_element,
utils::DOMAINS domain = utils::DOMAINS::VACUUM,
MappingKind mapping = MappingKind::none
MappingKind mapping = MappingKind::none
) const;
Resolution configure_gravity_source(
mfem::DomainLFIntegrator& integrator,
mfem::DomainLFIntegrator &integrator,
QuadratureRole role,
const mfem::FiniteElement& test_element,
const mfem::ElementTransformation& transformation,
const mfem::FiniteElement &test_element,
const mfem::ElementTransformation &transformation,
int coefficient_order,
utils::DOMAINS domain = utils::DOMAINS::STELLAR,
MappingKind mapping = MappingKind::none
MappingKind mapping = MappingKind::none
) const;
template<typename IntegratorType>
Resolution configure_gravity_source(
mfem::MixedScalarMassIntegrator &integrator,
QuadratureRole role,
const mfem::FiniteElement &trial_element,
const mfem::FiniteElement &test_element,
const mfem::ElementTransformation &transformation,
int coefficient_order,
utils::DOMAINS domain = utils::DOMAINS::STELLAR,
MappingKind mapping = MappingKind::none
) const;
Resolution configure_centrifugal(
integrators::CentrifugalForceIntegrator &integrator,
QuadratureRole role,
const mfem::FiniteElement &density_element,
const mfem::FiniteElement &velocity_element,
const mfem::ElementTransformation &transformation,
int position_order,
utils::DOMAINS domain = utils::DOMAINS::STELLAR,
MappingKind mapping = MappingKind::none
) const;
template <typename IntegratorType>
Resolution configure(
IntegratorType& integrator,
IntegratorType &integrator,
Term term,
QuadratureRole role,
mfem::Geometry::Type geometry,
int base_order,
utils::DOMAINS domain = utils::DOMAINS::ALL,
MappingKind mapping = MappingKind::none
MappingKind mapping = MappingKind::none
) const;
private:
Policy policy;
};
RuleFactory::RuleFactory(Policy policy) : policy(std::move(policy)) {}
RuleFactory::RuleFactory(Policy policy) : policy(std::move(policy)) {
}
MfemRule RuleFactory::get(
const Query& query,
const Query &query,
const mfem::Geometry::Type geometry
) const {
const Resolution resolution = policy.resolve(query);
const mfem::IntegrationRule& integration_rule = mfem::IntRules.Get(geometry, resolution.order);
return {.resolution = resolution, .integration_rule = &integration_rule};
const mfem::IntegrationRule &integration_rule =
mfem::IntRules.Get(geometry, resolution.order);
return {
.resolution = resolution, .integration_rule = &integration_rule
};
}
MfemRule RuleFactory::get(
@@ -102,87 +125,197 @@ export namespace mean_field::quadrature {
const MappingKind mapping
) const {
Query query{.term = term};
query.domain = domain;
query.mapping = mapping;
query.role = role;
query.domain = domain;
query.mapping = mapping;
query.role = role;
query.base_order = base_order;
return get(query, geometry);
}
Resolution RuleFactory::configure_gravity_hdiv_mass(
mfem::VectorFEMassIntegrator& integrator,
mfem::VectorFEMassIntegrator &integrator,
const QuadratureRole role,
const mfem::FiniteElement& element,
const mfem::ElementTransformation& transformation,
const mfem::FiniteElement &element,
const mfem::ElementTransformation &transformation,
const utils::DOMAINS domain,
const MappingKind mapping
) const {
const int base_order = 2 * element.GetOrder() + transformation.OrderW();
return configure(integrator, Term::gravity_hdiv_mass, role, element.GetGeomType(), base_order, domain, mapping);
using GravityField = field::Field<field::Gravity>;
MFEM_VERIFY(
element.GetOrder() == field::Gravity::Flux::familyOrder + 1,
"The H(div) element order does not match the registered gravity "
"flux."
);
const Query query =
GravityField::make_query<field::Gravity::Form::HDivMass>(
role, transformation.OrderW(), {}, domain, mapping
);
const auto [resolution, integration_rule] =
get(query, element.GetGeomType());
integrator.SetIntegrationRule(*integration_rule);
return resolution;
}
Resolution RuleFactory::configure_gravity_divergence(
mfem::VectorFEDivergenceIntegrator& integrator,
mfem::VectorFEDivergenceIntegrator &integrator,
const QuadratureRole role,
const mfem::FiniteElement& trial_element,
const mfem::FiniteElement& test_element,
const mfem::ElementTransformation& transformation,
const mfem::FiniteElement &trial_element,
const mfem::FiniteElement &test_element,
const mfem::ElementTransformation &transformation,
const utils::DOMAINS domain,
const MappingKind mapping
) const {
const Query query = {
.term = Term::gravity_divergence,
.role = role,
.domain = domain,
.mapping = mapping,
.trial_order = trial_element.GetOrder(),
.test_order = test_element.GetOrder(),
.geometry_weight_order = transformation.OrderW()
};
using GravityField = field::Field<field::Gravity>;
MFEM_VERIFY(
trial_element.GetOrder() == field::Gravity::Flux::familyOrder + 1,
"The divergence trial element does not match the registered "
"gravity flux."
);
MFEM_VERIFY(
test_element.GetOrder() == field::Gravity::Potential::familyOrder,
"The divergence test element does not match the registered "
"gravity potential."
);
const Query query =
GravityField::make_query<field::Gravity::Form::DivergenceCoupling>(
role, transformation.OrderW(), {}, domain, mapping
);
const auto [resolution, integration_rule] = get(query, trial_element.GetGeomType());
const auto [resolution, integration_rule] =
get(query, trial_element.GetGeomType());
integrator.SetIntegrationRule(*integration_rule);
return resolution;
}
Resolution RuleFactory::configure_gravity_boundary(
mfem::VectorFEBoundaryFluxLFIntegrator& integrator,
mfem::VectorFEBoundaryFluxLFIntegrator &integrator,
const QuadratureRole role,
const mfem::FiniteElement& boundary_element,
const mfem::FiniteElement &boundary_element,
const utils::DOMAINS domain,
const MappingKind mapping
) const {
const int base_order = 2 * boundary_element.GetOrder();
return configure(integrator, Term::gravity_boundary, role, boundary_element.GetGeomType(), base_order, domain, mapping);
}
Resolution RuleFactory::configure_gravity_source(
mfem::DomainLFIntegrator& integrator,
const QuadratureRole role,
const mfem::FiniteElement& test_element,
const mfem::ElementTransformation& transformation,
const int coefficient_order,
const utils::DOMAINS domain,
const MappingKind mapping
) const {
const Query query = {
.term = Term::gravity_source,
.role = role,
.domain = domain,
.mapping = mapping,
.test_order = test_element.GetOrder(),
.coefficient_order = coefficient_order,
.geometry_weight_order = transformation.OrderW()
};
const auto [resolution, integration_rule] = get(query, test_element.GetGeomType());
using GravityField = field::Field<field::Gravity>;
MFEM_VERIFY(
boundary_element.GetOrder() == field::Gravity::Flux::familyOrder,
"The boundary element does not match the registered gravity-flux "
"normal trace."
);
const Query query =
GravityField::make_query<field::Gravity::Form::Boundary>(
role, 0, {}, domain, mapping
);
const auto [resolution, integration_rule] =
get(query, boundary_element.GetGeomType());
integrator.SetIntegrationRule(*integration_rule);
return resolution;
}
template<typename IntegratorType>
Resolution RuleFactory::configure_gravity_source(
mfem::DomainLFIntegrator &integrator,
const QuadratureRole role,
const mfem::FiniteElement &test_element,
const mfem::ElementTransformation &transformation,
const int coefficient_order,
const utils::DOMAINS domain,
const MappingKind mapping
) const {
using GravityField = field::Field<field::Gravity>;
MFEM_VERIFY(
test_element.GetOrder() == field::Gravity::Potential::familyOrder,
"The gravity-source test element does not match the registered "
"gravity potential."
);
MFEM_VERIFY(
coefficient_order == field::Density::Scalar::familyOrder,
"The gravity-source coefficient order does not match the "
"registered density field."
);
const Query query =
GravityField::make_query<field::Gravity::Form::SourceLinear>(
role, transformation.OrderW(), {}, domain, mapping
);
const auto [resolution, integration_rule] =
get(query, test_element.GetGeomType());
integrator.SetIntegrationRule(*integration_rule);
return resolution;
}
Resolution RuleFactory::configure_gravity_source(
mfem::MixedScalarMassIntegrator &integrator,
QuadratureRole role,
const mfem::FiniteElement &trial_element,
const mfem::FiniteElement &test_element,
const mfem::ElementTransformation &transformation,
int coefficient_order,
utils::DOMAINS domain,
MappingKind mapping
) const {
MFEM_VERIFY(
trial_element.GetGeomType() == test_element.GetGeomType(),
"Gravity source trial and test elements must use the same geometry."
);
MFEM_VERIFY(
trial_element.GetGeomType() == transformation.GetGeometryType(),
"Gravity source element and transformation geometries must agree."
);
using GravityField = field::Field<field::Gravity>;
MFEM_VERIFY(
trial_element.GetOrder() == field::Density::Scalar::familyOrder,
"The gravity-source trial element does not match the registered "
"density field."
);
MFEM_VERIFY(
test_element.GetOrder() == field::Gravity::Potential::familyOrder,
"The gravity-source test element does not match the registered "
"gravity potential."
);
MFEM_VERIFY(
coefficient_order == 0,
"The mapped gravity-source coefficient order must be zero; "
"density order is supplied by the registered trial field."
);
const Query query =
GravityField::make_query<field::Gravity::Form::SourceProjection>(
role, transformation.OrderW(), {}, domain, mapping
);
const auto [resolution, integration_rule] =
get(query, transformation.GetGeometryType());
integrator.SetIntRule(integration_rule);
return resolution;
}
Resolution RuleFactory::configure_centrifugal(
integrators::CentrifugalForceIntegrator &integrator,
const QuadratureRole role,
const mfem::FiniteElement &density_element,
const mfem::FiniteElement &velocity_element,
const mfem::ElementTransformation &transformation,
const int position_order,
const utils::DOMAINS domain,
const MappingKind mapping
) const {
const Query query = {
.term = Term::centrifugal,
.role = role,
.domain = domain,
.mapping = mapping,
.trial_order = density_element.GetOrder(),
.test_order = velocity_element.GetOrder(),
.coefficient_order = position_order,
.geometry_weight_order = transformation.OrderW()
};
const auto [resolution, integration_rule] =
get(query, velocity_element.GetGeomType());
integrator.SetIntegrationRule(*integration_rule);
return resolution;
}
template <typename IntegratorType>
Resolution RuleFactory::configure(
IntegratorType& integrator,
IntegratorType &integrator,
const Term term,
const QuadratureRole role,
const mfem::Geometry::Type geometry,
@@ -190,9 +323,10 @@ export namespace mean_field::quadrature {
const utils::DOMAINS domain,
const MappingKind mapping
) const {
const auto [resolution, integration_rule] = get(term, role, geometry, base_order, domain, mapping);
const auto [resolution, integration_rule] =
get(term, role, geometry, base_order, domain, mapping);
integrator.SetIntegrationRule(*integration_rule);
return resolution;
}
}
} // namespace mean_field::quadrature

View File

@@ -2,7 +2,9 @@ module;
#include <algorithm>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
export module mean_field:quadrature.policy;
export import :utils.misc;
@@ -13,11 +15,19 @@ export namespace mean_field::quadrature {
gravity_divergence,
gravity_source,
gravity_boundary,
centrifugal,
density_projection,
eos_closure,
hydrostatic_equilibrium,
isobaric_surface,
mesh_extension,
mass_conservation,
mass_normalization,
center_of_mass,
quadrupole,
gravitational_energy,
pressure_integral,
pressure_force,
virial,
error_norm
};
@@ -29,19 +39,9 @@ export namespace mean_field::quadrature {
projection
};
enum class MappingKind {
none,
affine,
general,
kelvin
};
enum class MappingKind { none, affine, general, kelvin };
enum class Mode {
fast,
production,
reference,
convergence
};
enum class Mode { fast, production, reference, convergence };
struct RuleControl {
std::optional<int> fixed_order;
@@ -55,17 +55,24 @@ export namespace mean_field::quadrature {
RuleControl projection;
};
struct RuleSet {
RuleControl gravity_hdiv_mass;
RuleControl gravity_divergence;
RuleControl gravity_source;
RuleControl gravity_boundary;
RuleControl centrifugal;
RuleControl density_projection;
RuleControl eos_closure;
RuleControl hydrostatic_equilibrium;
RuleControl isobaric_surface;
RuleControl mesh_extension;
RuleControl mass_conservation;
RuleControl mass_normalization;
RuleControl center_of_mass;
RuleControl quadrupole;
RuleControl gravitational_energy;
RuleControl pressure_integral;
RuleControl pressure_force;
RuleControl virial;
RuleControl error_norm;
RoleControls roles;
@@ -74,12 +81,12 @@ export namespace mean_field::quadrature {
struct Query {
Term term;
QuadratureRole role = QuadratureRole::discretization;
utils::DOMAINS domain = utils::DOMAINS::ALL;
MappingKind mapping = MappingKind::none;
int trial_order = 0;
int test_order = 0;
int coefficient_order = 0;
QuadratureRole role = QuadratureRole::discretization;
utils::DOMAINS domain = utils::DOMAINS::ALL;
MappingKind mapping = MappingKind::none;
int trial_order = 0;
int test_order = 0;
int coefficient_order = 0;
int geometry_weight_order = 0;
std::optional<int> base_order;
};
@@ -96,16 +103,16 @@ export namespace mean_field::quadrature {
};
struct QuadratureManifestOptions {
bool enabled = false;
bool enabled = false;
bool include_repeated_queries = false;
std::optional<std::string> output_file;
};
struct QuadratureValidationOptions {
bool require_explicit_base_order = false;
bool require_explicit_mfem_rule = false;
bool reject_negative_boosts = true;
bool report_unused_overrides = true;
bool require_explicit_mfem_rule = false;
bool reject_negative_boosts = true;
bool report_unused_overrides = true;
};
struct QuadratureRoleOptions {
@@ -116,7 +123,7 @@ export namespace mean_field::quadrature {
};
struct QuadratureOptions {
Mode mode = Mode::production;
Mode mode = Mode::production;
int global_boost = 0;
std::optional<int> fallback_fixed_order;
@@ -124,11 +131,19 @@ export namespace mean_field::quadrature {
QuadratureTermOptions gravity_divergence;
QuadratureTermOptions gravity_source;
QuadratureTermOptions gravity_boundary;
QuadratureTermOptions centrifugal;
QuadratureTermOptions density_projection;
QuadratureTermOptions eos_closure;
QuadratureTermOptions hydrostatic_equilibrium;
QuadratureTermOptions isobaric_surface;
QuadratureTermOptions mesh_extension;
QuadratureTermOptions mass_conservation;
QuadratureTermOptions mass_normalization;
QuadratureTermOptions center_of_mass;
QuadratureTermOptions quadrupole;
QuadratureTermOptions gravitational_energy;
QuadratureTermOptions pressure_integral;
QuadratureTermOptions pressure_force;
QuadratureTermOptions virial;
QuadratureTermOptions error_norm;
@@ -139,44 +154,51 @@ export namespace mean_field::quadrature {
QuadratureValidationOptions validation;
};
RuleSet make_rule_set(Mode mode, int global_boost = 0);
RuleSet make_rule_set(
Mode mode,
int global_boost = 0
);
class Policy {
public:
explicit Policy(RuleSet rule_set);
Resolution resolve(const Query& query) const;
Resolution resolve(const Query &query) const;
private:
const RuleControl& get_control(Term term) const;
static int compute_base_order(const Query& query) ;
const RuleControl& get_role_control(QuadratureRole role) const;
const RuleControl &get_control(Term term) const;
static int compute_base_order(const Query &query);
const RuleControl &get_role_control(QuadratureRole role) const;
RuleSet rule_set;
};
RuleSet make_rule_set(const Mode mode, const int global_boost) {
RuleSet make_rule_set(
const Mode mode,
const int global_boost
) {
RuleSet rule_set;
switch (mode) {
case Mode::fast:
case Mode::production:
case Mode::convergence:
rule_set.fallback.boost = global_boost;
break;
case Mode::reference:
rule_set.fallback.boost = global_boost + 8;
break;
case Mode::fast:
case Mode::production:
case Mode::convergence:
rule_set.fallback.boost = global_boost;
break;
case Mode::reference:
rule_set.fallback.boost = global_boost + 8;
break;
}
return rule_set;
}
Policy::Policy(RuleSet rule_set) : rule_set(std::move(rule_set)) {}
Policy::Policy(RuleSet rule_set) : rule_set(std::move(rule_set)) {
}
Resolution Policy::resolve(const Query& query) const {
const int base_order = compute_base_order(query);
const RuleControl& term_control = get_control(query.term);
const RuleControl& role_control = get_role_control(query.role);
Resolution Policy::resolve(const Query &query) const {
const int base_order = compute_base_order(query);
const RuleControl &term_control = get_control(query.term);
const RuleControl &role_control = get_role_control(query.role);
std::optional<int> fixed_order;
if (term_control.fixed_order.has_value()) {
@@ -189,49 +211,96 @@ export namespace mean_field::quadrature {
if (fixed_order.has_value()) {
if (*fixed_order < 0) {
throw std::invalid_argument("Quadrature fixed order cannot be negative.");
throw std::invalid_argument(
"Quadrature fixed order cannot be negative."
);
}
return {.base_order = base_order, .boost = 0, .order = *fixed_order, .used_fixed_order = true};
return {
.base_order = base_order,
.boost = 0,
.order = *fixed_order,
.used_fixed_order = true
};
}
const int boost = rule_set.fallback.boost + role_control.boost + term_control.boost;
const int boost =
rule_set.fallback.boost + role_control.boost + term_control.boost;
const int order = base_order + boost;
if (order < 0) {
throw std::invalid_argument("Resolved quadrature order cannot be negative.");
throw std::invalid_argument(
"Resolved quadrature order cannot be negative."
);
}
return {.base_order = base_order, .boost = boost, .order = order, .used_fixed_order = false};
return {
.base_order = base_order,
.boost = boost,
.order = order,
.used_fixed_order = false
};
}
const RuleControl& Policy::get_control(const Term term) const {
const RuleControl &Policy::get_control(const Term term) const {
switch (term) {
case Term::gravity_hdiv_mass: return rule_set.gravity_hdiv_mass;
case Term::gravity_divergence: return rule_set.gravity_divergence;
case Term::gravity_source: return rule_set.gravity_source;
case Term::gravity_boundary: return rule_set.gravity_boundary;
case Term::density_projection: return rule_set.density_projection;
case Term::mass_conservation: return rule_set.mass_conservation;
case Term::center_of_mass: return rule_set.center_of_mass;
case Term::quadrupole: return rule_set.quadrupole;
case Term::gravitational_energy: return rule_set.gravitational_energy;
case Term::virial: return rule_set.virial;
case Term::error_norm: return rule_set.error_norm;
case Term::gravity_hdiv_mass:
return rule_set.gravity_hdiv_mass;
case Term::gravity_divergence:
return rule_set.gravity_divergence;
case Term::gravity_source:
return rule_set.gravity_source;
case Term::gravity_boundary:
return rule_set.gravity_boundary;
case Term::centrifugal:
return rule_set.centrifugal;
case Term::density_projection:
return rule_set.density_projection;
case Term::eos_closure:
return rule_set.eos_closure;
case Term::hydrostatic_equilibrium:
return rule_set.hydrostatic_equilibrium;
case Term::isobaric_surface:
return rule_set.isobaric_surface;
case Term::mesh_extension:
return rule_set.mesh_extension;
case Term::mass_conservation:
return rule_set.mass_conservation;
case Term::mass_normalization:
return rule_set.mass_normalization;
case Term::center_of_mass:
return rule_set.center_of_mass;
case Term::quadrupole:
return rule_set.quadrupole;
case Term::gravitational_energy:
return rule_set.gravitational_energy;
case Term::pressure_integral:
return rule_set.pressure_integral;
case Term::pressure_force:
return rule_set.pressure_force;
case Term::virial:
return rule_set.virial;
case Term::error_norm:
return rule_set.error_norm;
}
throw std::logic_error("Unknown quadrature term.");
}
int Policy::compute_base_order(const Query& query) {
int Policy::compute_base_order(const Query &query) {
if (query.base_order.has_value()) {
if (*query.base_order < 0) {
throw std::invalid_argument("Quadrature base order cannot be negative.");
throw std::invalid_argument(
"Quadrature base order cannot be negative."
);
}
return *query.base_order;
}
if (query.trial_order < 0 || query.test_order < 0 || query.coefficient_order < 0 || query.geometry_weight_order < 0) {
throw std::invalid_argument("Quadrature query orders cannot be negative.");
if (query.trial_order < 0 || query.test_order < 0 ||
query.coefficient_order < 0 || query.geometry_weight_order < 0) {
throw std::invalid_argument(
"Quadrature query orders cannot be negative."
);
}
int trial_order = query.trial_order;
@@ -239,18 +308,24 @@ export namespace mean_field::quadrature {
trial_order = std::max(0, trial_order - 1);
}
return trial_order + query.test_order + query.coefficient_order + query.geometry_weight_order;
return trial_order + query.test_order + query.coefficient_order +
query.geometry_weight_order;
}
const RuleControl& Policy::get_role_control(const QuadratureRole role) const {
const RuleControl &
Policy::get_role_control(const QuadratureRole role) const {
switch (role) {
case QuadratureRole::discretization: return rule_set.roles.discretization;
case QuadratureRole::preconditioner: return rule_set.roles.preconditioner;
case QuadratureRole::diagnostic: return rule_set.roles.diagnostic;
case QuadratureRole::projection: return rule_set.roles.projection;
case QuadratureRole::discretization:
return rule_set.roles.discretization;
case QuadratureRole::preconditioner:
return rule_set.roles.preconditioner;
case QuadratureRole::diagnostic:
return rule_set.roles.diagnostic;
case QuadratureRole::projection:
return rule_set.roles.projection;
}
throw std::logic_error("Unknown quadrature role.");
}
}
} // namespace mean_field::quadrature

View File

@@ -0,0 +1,29 @@
module;
#include <cstdint>
export module mean_field:solver.fields;
export namespace mean_field::solver {
enum class FieldBlock : std::uint8_t {
velocity = 0,
density = 1,
gravity_gradient = 2,
gravity_potential = 3,
displacement = 4,
count = 5
};
[[nodiscard]] constexpr int block_index(const FieldBlock field) noexcept {
return static_cast<int>(field);
}
inline constexpr int field_block_count = block_index(FieldBlock::count);
static_assert(block_index(FieldBlock::velocity) == 0);
static_assert(block_index(FieldBlock::density) == 1);
static_assert(block_index(FieldBlock::gravity_gradient) == 2);
static_assert(block_index(FieldBlock::gravity_potential) == 3);
static_assert(block_index(FieldBlock::displacement) == 4);
static_assert(field_block_count == 5);
} // namespace mean_field::solver

View File

@@ -0,0 +1,493 @@
module;
#include <array>
#include <mfem.hpp>
#include <stdexcept>
#include <tuple>
#include <type_traits>
export module mean_field:utils.blocks;
export namespace mean_field::utils::blocks {
inline constexpr int dynamic_block_size = -1;
struct block { };
struct residual_block_base : block {
static constexpr int static_block_size = dynamic_block_size;
};
struct value_block_base : block {
static constexpr int static_block_size = dynamic_block_size;
};
struct term { };
struct field { };
template <typename Residual, typename... Values> struct block_row { };
template <int index_value>
struct residual_block final : residual_block_base {
static constexpr int index = index_value;
// ReSharper disable once CppNonExplicitConversionOperator
constexpr operator int() const noexcept {
return index;
}
};
template <int index_value> struct value_block final : value_block_base {
static constexpr int index = index_value;
// ReSharper disable once CppNonExplicitConversionOperator
constexpr operator int() const noexcept {
return index;
}
};
struct density final : field {
struct mass final : term {
struct value final : value_block_base { };
struct residual final : residual_block_base { };
};
static inline constexpr mass mass_term{};
};
struct displacement final : field {
struct geometry final : term {
struct value final : value_block_base { };
struct residual final : residual_block_base { };
};
static inline constexpr geometry geometry_term{};
};
struct gravity final : field {
struct gradient final : term {
struct value final : value_block_base { };
struct residual final : residual_block_base { };
};
struct poisson final : term {
struct value final : value_block_base { };
struct residual final : residual_block_base { };
};
static inline constexpr gradient gradient_term{};
static inline constexpr poisson poisson_term{};
};
struct enthalpy final : field {
struct specific final : term {
struct value final : value_block_base { };
struct residual final : residual_block_base { };
};
static inline constexpr specific specific_term{};
};
struct barotropic_constant final : field {
struct mass_normalization final : term {
struct value final : value_block_base {
static constexpr int static_block_size = 1;
};
struct residual final : residual_block_base {
static constexpr int static_block_size = 1;
};
};
static inline constexpr mass_normalization mass_normalization_term{};
};
inline constexpr density density_field{};
inline constexpr displacement displacement_field{};
inline constexpr gravity gravity_field{};
inline constexpr enthalpy enthalpy_field{};
inline constexpr barotropic_constant barotropic_constant_field{};
template <typename... Types> struct type_list {
static constexpr int size = sizeof...(Types);
};
template <typename Query, typename List> struct contains_type;
template <typename Query>
struct contains_type<Query, type_list<>> : std::false_type { };
template <typename Query, typename Head, typename... Tail>
struct contains_type<Query, type_list<Head, Tail...>>
: std::conditional_t<
std::is_same_v<Query, Head>,
std::true_type,
contains_type<Query, type_list<Tail...>>> { };
template <typename Query, typename List>
inline constexpr bool contains_type_v = contains_type<Query, List>::value;
template <typename Query, typename List> struct type_count;
template <typename Query>
struct type_count<Query, type_list<>> : std::integral_constant<int, 0> { };
template <typename Query, typename Head, typename... Tail>
struct type_count<Query, type_list<Head, Tail...>>
: std::integral_constant<
int,
(std::is_same_v<Query, Head> ? 1 : 0) +
type_count<Query, type_list<Tail...>>::value> { };
template <typename Query, typename List>
inline constexpr int type_count_v = type_count<Query, List>::value;
template <typename List> struct types_are_unique;
template <typename... Types>
struct types_are_unique<type_list<Types...>>
: std::bool_constant<
((type_count_v<Types, type_list<Types...>> == 1) && ...)> { };
template <typename List>
inline constexpr bool types_are_unique_v = types_are_unique<List>::value;
template <typename Row> struct block_row_traits {
using residual = void;
using values = type_list<>;
static constexpr int value_count = 0;
static constexpr bool is_block_row = false;
};
template <typename Residual, typename... Values>
struct block_row_traits<block_row<Residual, Values...>> {
using residual = Residual;
using values = type_list<Values...>;
static constexpr int value_count = sizeof...(Values);
static constexpr bool is_block_row = true;
};
template <typename Query, typename List> struct type_index;
template <typename Query, typename... Tail>
struct type_index<Query, type_list<Query, Tail...>> {
static constexpr int value = 0;
};
template <typename Query, typename Head, typename... Tail>
struct type_index<Query, type_list<Head, Tail...>> {
static constexpr int value =
1 + type_index<Query, type_list<Tail...>>::value;
};
template <typename Query, typename List>
inline constexpr int type_index_v = type_index<Query, List>::value;
template <typename ValueBlocks, typename ResidualBlocks> struct block_form {
using value_blocks = ValueBlocks;
using residual_blocks = ResidualBlocks;
static constexpr int value_block_count = ValueBlocks::size;
static constexpr int residual_block_count = ResidualBlocks::size;
};
template <typename Form> struct block_form_is_valid : std::false_type { };
template <typename... Values, typename... Residuals>
struct block_form_is_valid<
block_form<type_list<Values...>, type_list<Residuals...>>>
: std::bool_constant<
(std::is_base_of_v<value_block_base, Values> && ...) &&
(std::is_base_of_v<residual_block_base, Residuals> && ...) &&
types_are_unique_v<type_list<Values...>> &&
types_are_unique_v<type_list<Residuals...>>> { };
template <typename Form>
inline constexpr bool block_form_is_valid_v =
block_form_is_valid<Form>::value;
template <typename Row, typename ValueBlocks, typename ResidualBlocks>
struct block_row_is_valid : std::false_type { };
template <
typename Residual,
typename... Values,
typename ValueBlocks,
typename ResidualBlocks>
struct block_row_is_valid<
block_row<Residual, Values...>,
ValueBlocks,
ResidualBlocks>
: std::bool_constant<
std::is_base_of_v<residual_block_base, Residual> &&
contains_type_v<Residual, ResidualBlocks> &&
((std::is_base_of_v<value_block_base, Values> &&
contains_type_v<Values, ValueBlocks>) &&
...) &&
types_are_unique_v<type_list<Values...>>> { };
template <typename Rows> struct row_residual_list;
template <typename... Rows> struct row_residual_list<type_list<Rows...>> {
using type = type_list<typename block_row_traits<Rows>::residual...>;
};
template <typename Rows>
using row_residual_list_t = typename row_residual_list<Rows>::type;
template <typename Form, typename JacobianForm>
struct jacobian_form_is_valid : std::false_type { };
template <typename... Values, typename... Residuals, typename... Rows>
struct jacobian_form_is_valid<
block_form<type_list<Values...>, type_list<Residuals...>>,
type_list<Rows...>> {
using form_type =
block_form<type_list<Values...>, type_list<Residuals...>>;
using value_blocks = type_list<Values...>;
using residual_blocks = type_list<Residuals...>;
using rows = type_list<Rows...>;
static constexpr bool value =
block_form_is_valid_v<form_type> &&
(block_row_is_valid<Rows, value_blocks, residual_blocks>::value &&
...) &&
std::is_same_v<row_residual_list_t<rows>, residual_blocks>;
};
template <typename Form, typename JacobianForm>
inline constexpr bool jacobian_form_is_valid_v =
jacobian_form_is_valid<Form, JacobianForm>::value;
template <typename Form, typename JacobianForm>
concept valid_jacobian_form = jacobian_form_is_valid_v<Form, JacobianForm>;
template <typename Residual, typename Value, typename JacobianForm>
struct has_jacobian_coupling;
template <typename Residual, typename Value>
struct has_jacobian_coupling<Residual, Value, type_list<>>
: std::false_type { };
template <
typename Residual,
typename Value,
typename RowResidual,
typename... RowValues,
typename... RemainingRows>
struct has_jacobian_coupling<
Residual,
Value,
type_list<block_row<RowResidual, RowValues...>, RemainingRows...>>
: std::conditional_t<
std::is_same_v<Residual, RowResidual>,
std::bool_constant<(std::is_same_v<Value, RowValues> || ...)>,
has_jacobian_coupling<
Residual,
Value,
type_list<RemainingRows...>>> { };
template <typename Residual, typename Value, typename JacobianForm>
inline constexpr bool has_jacobian_coupling_v =
has_jacobian_coupling<Residual, Value, JacobianForm>::value;
template <
typename Form,
typename Term>
consteval auto get_value_block(const Term &) {
using value_type = typename Term::value;
constexpr int index =
type_index_v<value_type, typename Form::value_blocks>;
return value_block<index>{};
}
template <
typename Form,
typename Term>
consteval auto get_residual_block(const Term &) {
using residual_type = typename Term::residual;
constexpr int index =
type_index_v<residual_type, typename Form::residual_blocks>;
return residual_block<index>{};
}
template <typename Form> class form_layout {
public:
form_layout(
const std::array<
int,
Form::value_block_count> &value_sizes,
const std::array<
int,
Form::residual_block_count> &residual_sizes
) {
build_offsets(
m_value_offsets, value_sizes, typename Form::value_blocks{}
);
build_offsets(
m_residual_offsets, residual_sizes,
typename Form::residual_blocks{}
);
}
template <int index> [[nodiscard]] int size(value_block<index>) const {
return m_value_offsets[index + 1] - m_value_offsets[index];
}
template <int index>
[[nodiscard]] int size(residual_block<index>) const {
return m_residual_offsets[index + 1] - m_residual_offsets[index];
}
template <int index>
[[nodiscard]] int offset(value_block<index>) const {
return m_value_offsets[index];
}
template <int index>
[[nodiscard]] int offset(residual_block<index>) const {
return m_residual_offsets[index];
}
[[nodiscard]]
const mfem::Array<int> &value_offsets() const noexcept {
return m_value_offsets;
}
[[nodiscard]]
const mfem::Array<int> &residual_offsets() const noexcept {
return m_residual_offsets;
}
private:
template <typename BlockType>
[[nodiscard]]
static int resolve_block_size(const int requested_size) {
if constexpr (BlockType::static_block_size == dynamic_block_size) {
return requested_size;
} else {
if (requested_size != BlockType::static_block_size) {
throw std::invalid_argument(
"A statically sized block was given an "
"incompatible runtime size."
);
}
return BlockType::static_block_size;
}
}
template <typename... BlockTypes>
static void build_offsets(
mfem::Array<int> &offsets,
const std::array<
int,
sizeof...(BlockTypes)> &requested_sizes,
type_list<BlockTypes...>
) {
offsets.SetSize(sizeof...(BlockTypes) + 1);
offsets[0] = 0;
int block_index = 0;
((offsets[block_index + 1] =
offsets[block_index] +
resolve_block_size<BlockTypes>(requested_sizes[block_index]),
++block_index),
...);
}
mfem::Array<int> m_value_offsets;
mfem::Array<int> m_residual_offsets;
};
using gravity_field_form = block_form<
type_list<
density::mass::value,
displacement::geometry::value,
gravity::gradient::value,
gravity::poisson::value>,
type_list<gravity::gradient::residual, gravity::poisson::residual>>;
using gravity_jacobian_form = type_list<
block_row<
gravity::gradient::residual,
gravity::gradient::value,
gravity::poisson::value,
displacement::geometry::value>,
block_row<
gravity::poisson::residual,
gravity::gradient::value,
density::mass::value,
displacement::geometry::value>>;
// Columns:
// [rho, d, g, Phi, h, C]
//
// Rows:
// [R_g, R_Phi, R_rho, R_d, R_h, R_M]
using barotropic_equilibrium_form = block_form<
type_list<
density::mass::value,
displacement::geometry::value,
gravity::gradient::value,
gravity::poisson::value,
enthalpy::specific::value,
barotropic_constant::mass_normalization::value>,
type_list<
gravity::gradient::residual,
gravity::poisson::residual,
density::mass::residual,
displacement::geometry::residual,
enthalpy::specific::residual,
barotropic_constant::mass_normalization::residual>>;
using barotropic_equilibrium_jacobian_form = type_list<
// R_g(g, Phi, d)
block_row<
gravity::gradient::residual,
gravity::gradient::value,
gravity::poisson::value,
displacement::geometry::value>,
// R_Phi(g, rho, d)
block_row<
gravity::poisson::residual,
gravity::gradient::value,
density::mass::value,
displacement::geometry::value>,
// R_rho(rho, h, d)
block_row<
density::mass::residual,
density::mass::value,
enthalpy::specific::value,
displacement::geometry::value>,
// R_d(d, h)
block_row<
displacement::geometry::residual,
displacement::geometry::value,
enthalpy::specific::value>,
// R_h(h, Phi, d, C)
block_row<
enthalpy::specific::residual,
enthalpy::specific::value,
gravity::poisson::value,
displacement::geometry::value,
barotropic_constant::mass_normalization::value>,
// R_M(rho, d)
block_row<
barotropic_constant::mass_normalization::residual,
density::mass::value,
displacement::geometry::value>>;
static_assert(valid_jacobian_form<
gravity_field_form,
gravity_jacobian_form>);
static_assert(valid_jacobian_form<
barotropic_equilibrium_form,
barotropic_equilibrium_jacobian_form>);
} // namespace mean_field::utils::blocks

View File

@@ -21,4 +21,4 @@ export namespace mean_field::utils {
mapping::COORDINATE_SPACE vspace = mapping::COORDINATE_SPACE::REFERENCE,
mapping::COORDINATE_SPACE rspace = mapping::COORDINATE_SPACE::PHYSICAL
);
}
} // namespace mean_field::utils

View File

@@ -1,7 +1,7 @@
module;
#include <string_view>
#include <functional>
#include <expected>
#include <functional>
#include <string_view>
#include <mfem.hpp>
@@ -14,8 +14,10 @@ import :boundary.contexts;
export namespace mean_field::utils {
constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4;
bool is_vacuum(const mfem::ElementTransformation &Tr, mfem::Array<mfem::Vector*> elvec) {
bool is_vacuum(
const mfem::ElementTransformation &Tr,
mfem::Array<mfem::Vector *> elvec
) {
if (Tr.Attribute == 3) {
const int size_elvec = elvec.Size();
for (int i = 0; i < size_elvec; i++) {
@@ -28,45 +30,60 @@ export namespace mean_field::utils {
return false;
}
constexpr std::string_view ANSI_GREEN = "\033[32m";
constexpr std::string_view ANSI_RED = "\033[31m";
constexpr std::string_view ANSI_YELLOW = "\033[33m";
constexpr std::string_view ANSI_BLUE = "\033[34m";
constexpr std::string_view ANSI_MAGENTA = "\033[35m";
constexpr std::string_view ANSI_CYAN = "\033[36m";
constexpr std::string_view ANSI_RESET = "\033[0m";
constexpr std::string_view ANSI_BCYAN = "\033[1;36m";
bool is_vacuum(
const mfem::ElementTransformation &Tr,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
if (Tr.Attribute == 3) {
const int cols = elmats.NumCols();
const int rows = elmats.NumRows();
for (int rowID = 0; rowID < rows; rowID++) {
for (int colID = 0; colID < cols; colID++) {
if (elmats(rowID, colID)) {
*elmats(rowID, colID) = 0.0;
}
}
}
return true;
}
return false;
}
constexpr std::string_view ANSI_GREEN = "\033[32m";
constexpr std::string_view ANSI_RED = "\033[31m";
constexpr std::string_view ANSI_YELLOW = "\033[33m";
constexpr std::string_view ANSI_BLUE = "\033[34m";
constexpr std::string_view ANSI_MAGENTA = "\033[35m";
constexpr std::string_view ANSI_CYAN = "\033[36m";
constexpr std::string_view ANSI_RESET = "\033[0m";
constexpr std::string_view ANSI_BCYAN = "\033[1;36m";
constexpr double G = 1.0;
constexpr double MASS = 1.0;
constexpr double RADIUS = 1.0;
constexpr double G = 1.0;
constexpr double MASS = 1.0;
constexpr double RADIUS = 1.0;
[[maybe_unused]] constexpr char HOST[10] = "localhost";
[[maybe_unused]] constexpr int PORT = 19916;
[[maybe_unused]] constexpr int PORT = 19916;
template <typename T>
concept is_xad = std::is_same_v<T, xad::AReal<long double>> ||
std::is_same_v<T, xad::AReal<double>> ||
std::is_same_v<T, xad::AReal<float>>;
template<typename T>
concept is_xad =
std::is_same_v<T, xad::AReal<long double> >
|| 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>;
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 {
CORE = 1 << 0,
CORE = 1 << 0,
ENVELOPE = 1 << 1,
VACUUM = 1 << 2,
STELLAR = CORE | ENVELOPE,
ALL = CORE | ENVELOPE | VACUUM
VACUUM = 1 << 2,
STELLAR = CORE | ENVELOPE,
ALL = CORE | ENVELOPE | VACUUM
};
DOMAINS operator|(
DOMAINS lhs,
DOMAINS rhs
@@ -78,7 +95,7 @@ export namespace mean_field::utils {
);
void populate_element_mask(
const mfem::Mesh* mesh,
const mfem::Mesh *mesh,
DOMAINS domain,
mfem::Array<int> &mask
);
@@ -89,13 +106,14 @@ export namespace mean_field::utils {
mfem::Array<int> &ess_tdof
);
std::expected<boundary::Bounds, boundary::BoundsError> discover_bounds(
std::expected<
boundary::Bounds,
boundary::BoundsError>
discover_bounds(
const mfem::Mesh *mesh,
int vacuum_attr
);
int get_mesh_order(
const mfem::Mesh &mesh
);
int get_mesh_order(const mfem::Mesh &mesh);
}
} // namespace mean_field::utils

View File

@@ -3,6 +3,8 @@ module;
export module mean_field:utils.user;
export import :quadrature.policy;
export import :mapping.compactification.options;
export namespace mean_field::utils {
struct potential {
double rtol;
@@ -16,6 +18,11 @@ export namespace mean_field::utils {
double L;
};
struct DomainMapperStatelessOptions {
int dimension{3};
int vacuum_element_attribute{3};
};
struct Args {
std::string mesh_file;
potential p{};
@@ -24,13 +31,13 @@ export namespace mean_field::utils {
double index{};
double mass{};
double c{};
int quad_boost{0};
DomainMapperStatelessOptions domain_mapper_options{};
mapping::compactification::options::KelvinCompactificationOptions
kelvin_options{};
int max_iters{};
double tol{};
quadrature::QuadratureOptions quadrature{};
};
}
} // namespace mean_field::utils