feat(mean_field): added initial implementation

note this implementation lacks many tests
This commit is contained in:
2026-07-15 09:44:43 -04:00
commit 9bc4f2758a
49 changed files with 171811 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
module;
#include <mfem.hpp>
module mean_field;
import :mapping.coefficients;
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());
mfem::GridFunctionCoefficient gf_c(&gf);
double local_integral;
mfem::Array<int> elem_markers;
populate_element_mask(fem.mesh.get(), domain, elem_markers);
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
auto *lf_integrator = new mfem::DomainLFIntegrator(mapped_gf_c);
lf_integrator->SetIntRule(fem.int_rule.get());
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.");
}
lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(gf_c), 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());
return global_integral;
}
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;
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 j = 0; j < ir.GetNPoints(); ++j) {
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
trans->SetIntPoint(&ip);
double weight = trans->Weight() * ip.weight;
if (fem.has_mapping()) {
weight *= fem.mapping->ComputeDetJ(*trans, ip);
}
double rho_val = rho.GetValue(i, ip);
mfem::Vector phys_point(dim);
if (fem.has_mapping()) {
fem.mapping->GetPhysicalPoint(*trans, ip, phys_point);
} else {
trans->Transform(ip, phys_point);
}
const double mass_term = rho_val * weight;
local_mass += mass_term;
for (int d = 0; d < dim; ++d) {
local_com(d) += phys_point(d) * mass_term;
}
}
}
double global_mass = 0.0;
mfem::Vector global_com(dim);
MPI_Comm comm = fem.H1_fes->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);
if (global_mass > 1e-18) {
global_com /= global_mass;
} else {
global_com = 0.0;
}
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)
rho *= (target_mass / current_mass);
}
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);
} else {
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
}
mfem::GridFunctionCoefficient rho_coeff(&rho);
mfem::ProductCoefficient I_integrand(rho_coeff, *s2_coeff);
mfem::LinearForm I_lf(fem.H1_fes.get());
double I = 0.0;
// TODO: Need to filter here to just the stellar domain and also update the IntRule
if (fem.has_mapping()) {
mapping::MappedScalarCoefficient mapped_integrand(*fem.mapping, I_integrand);
I_lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(mapped_integrand));
I_lf.Assemble();
I = I_lf.Sum();
} else {
I_lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(I_integrand));
I_lf.Assemble();
I = I_lf.Sum();
}
return I;
}
double get_mesh_volume(
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);
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.");
}
mfem::ElementTransformation *T = mesh.GetElementTransformation(e);
for (int q = 0; q < ir.GetNPoints(); ++q) {
const mfem::IntegrationPoint &ip = ir.IntPoint(q);
T->SetIntPoint(&ip);
double dV = ip.weight * T->Weight();
if (physical) {
dV *= std::fabs(map.ComputeDetJ(*T, ip));
}
local_volume += dV;
}
}
double global_volume = 0.0;
MPI_Allreduce(&local_volume, &global_volume, 1, MPI_DOUBLE, MPI_SUM,
mesh.GetComm());
return global_volume;
}
}

190
libmeanfield/impl/fem.cpp Normal file
View File

@@ -0,0 +1,190 @@
module;
#include <string>
#include <memory>
#include <mfem.hpp>
#include <stroid/stroid.h>
module mean_field;
import :boundary.contexts;
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 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);
}
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh);
fem.mesh->EnsureNodes();
const int geom_order = utils::get_mesh_order(*fem.mesh);
const int dim = fem.mesh->Dimension();
const int v_order = 2;
const int rho_order = 2;
const int p = rho_order ;
const int cb_type = mfem::BasisType::GaussLobatto;
const int ob_type = mfem::BasisType::IntegratedGLL;
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());
fem.H1_fec = std::make_unique<mfem::H1_FECollection>(v_order, dim);
fem.L2_fec = std::make_unique<mfem::L2_FECollection>(rho_order, dim);
// 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);
// Density & Pressure (Scalar Discontinuous L2)
fem.L2_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.L2_fec.get());
//==================================================================
// 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();
fem.mapping = std::make_unique<mapping::DomainMapper>(r_star_ref, r_inf_ref);
//==================================================================
// 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();
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();
//==================================================================
// Section 4: Multipole BC setup.
//==================================================================
fem.com.SetSize(dim);
fem.com = 0.0;
fem.Q.SetSize(dim, dim);
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;
fem.int_rule = std::make_unique<mfem::IntegrationRule>(mfem::IntRules.Get(mfem::Geometry::CUBE, fem.int_order));
//==================================================================
// Section 6: Essential Boundaries & Domain Masks
//==================================================================
fem.ess_v_tdofs.SetSize(0);
populate_element_mask(fem.mesh.get(), utils::DOMAINS::STELLAR, fem.gravity_context.stellar_mask);
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.boundary_context.inf_bounds = 0;
fem.boundary_context.stellar_bounds = 0;
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;
//==================================================================
// 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.gravity_context.prec_Phi = std::make_unique<mfem::HypreBoomerAMG>();
fem.gravity_context.prec_Phi->SetPrintLevel(0);
fem.gravity_context.block_prec = std::make_unique<mfem::BlockDiagonalPreconditioner>(fem.gravity_block_true_offsets);
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);
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);
}
const quadrature::QuadratureOptions& quadrature_options = args.quadrature;
if (quadrature_options.validation.reject_negative_boosts && quadrature_options.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);
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.");
}
quadrature_rule_set.fallback.fixed_order = quadrature_options.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.");
}
if (quadrature_options.validation.reject_negative_boosts && term_options.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
rule_control.boost += term_options.additional_boost;
if (term_options.fixed_order.has_value()) {
rule_control.fixed_order = term_options.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(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);
fem.quadrature_factory = std::make_unique<quadrature::RuleFactory>(quadrature::Policy(std::move(quadrature_rule_set)));
return fem;
}
}

View File

@@ -0,0 +1,206 @@
module;
#include <mfem.hpp>
module mean_field;
namespace mean_field::integrators {
AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map) : m_map(map) {}
void AdvectionIntegrator::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 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];
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;
}
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);
for (int q = 0; q < ir->GetNPoints(); q++) {
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
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);
}
mfem::Vector v_val(dim);
v_val = 0.0;
mfem::DenseMatrix grad_v(dim, dim);
grad_v = 0.0;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
const double v_ic = v_dofs(i + c * dof_v);
v_val(c) += v_ic * shape_v(i);
for (int d = 0; d < dim; ++d) {
grad_v(c, d) += v_ic * dshape_v_phys(i, d);
}
}
}
mfem::Vector adv_val(dim);
adv_val = 0.0;
for (int c = 0; c < dim; ++c) {
for (int d = 0; d < dim; ++d) {
adv_val(c) += v_val(d) * grad_v(c, d);
}
}
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;
}
}
}
}
void AdvectionIntegrator::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 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];
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;
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);
for (int q = 0; q < ir->GetNPoints(); q++) {
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
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);
}
mfem::Vector v_val(dim);
v_val = 0.0;
mfem::DenseMatrix grad_v(dim, dim);
grad_v = 0.0;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
double v_ic = v_dofs(i + c * dof_v);
v_val(c) += v_ic * shape_v(i);
for (int d = 0; d < dim; ++d) {
grad_v(c, d) += v_ic * dshape_v_phys(i, d);
}
}
}
mfem::Vector adv_val(dim);
adv_val = 0.0;
for (int c = 0; c < dim; ++c) {
for (int d = 0; d < dim; ++d) {
adv_val(c) += v_val(d) * grad_v(c, d);
}
}
// Jacobian wrt. Velocity: dR_v/dv
if (dv_dv) {
for (int i = 0; i < dof_v; ++i) {
// Test function index
for (int c = 0; c < dim; ++c) {
// Test function component
int row = i + c * dof_v;
for (int j = 0; j < dof_v; ++j) {
// Trial function index
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);
}
for (int d = 0; d < dim; ++d) {
// Trial function component
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
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;
(*dv_dv)(row, col) += shape_v(i) * rho_val * (termA + termB) * weight;
}
}
}
}
}
// Jacobian wrt. Density: dR_v / drho
if (dv_drho) {
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;
// \delta \rho * (\vec{v} \cdot \nabla \vec{v})
double term = shape_rho(j) * adv_val(c);
(*dv_drho)(row, col) += shape_v(i) * term * weight;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,146 @@
module;
#include <mfem.hpp>
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) {
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
m_omega = omega;
}
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;
}
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 mfem::Vector& rho_dofs = *elfun[1];
mfem::Vector& r_v = *elvec[0];
r_v = 0.0;
r_v.SetSize(dof_v * dim);
if (elvec[1]) {
elvec[1]->SetSize(dof_rho);
*elvec[1] = 0.0;
}
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());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
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);
// ω 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);
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_v; ++i) {
for (int c = 0; c < dim; ++c) {
r_v(i + c * dof_v) += shape_v(i) * rho_val * b(c) * weight;
}
}
}
}
void CentrifugalForceIntegrator::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 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);
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());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
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);
// ω 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);
// dR_dv_i_c / drho_j = φ_i * φ_j * b_c
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) * b(c) * weight;
}
}
}
}
}
}

View File

@@ -0,0 +1,152 @@
module;
#include <mfem.hpp>
module mean_field;
namespace mean_field::integrators {
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, 1) = -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);
}
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 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];
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;
}
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
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);
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);
}
mfem::Vector F_coriolis(dim);
m_omega_mat.Mult(v_val, F_coriolis);
F_coriolis *= 2.0;
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;
}
}
}
}
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 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];
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;
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
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);
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);
}
mfem::Vector F_coriolis(dim);
m_omega_mat.Mult(v_val, F_coriolis);
F_coriolis *= 2.0;
if (dv_dv) {
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_v; ++j) {
for (int d = 0; d < dim; ++d) {
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;
}
}
}
}
}
if (dv_drho) {
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;
(*dv_drho)(row, col) += shape_v(i) * shape_rho(j) * F_coriolis(c) * weight;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,124 @@
module;
#include <mfem.hpp>
module mean_field;
namespace mean_field::integrators {
GravityForceIntegrator::GravityForceIntegrator(
const mapping::DomainMapper& map,
const mfem::GridFunction& phi
): m_map(map), m_phi(&phi) {}
void GravityForceIntegrator::SetPotential(const mfem::GridFunction& phi) { m_phi = &phi; };
void GravityForceIntegrator::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 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];
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;
}
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 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);
Tr.SetIntPoint(&ip);
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);
fe_v->CalcShape(ip, shape_v);
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_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;
}
}
}
}
void GravityForceIntegrator::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 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);
if (dv_dv) *dv_dv = 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 grad_phi_ref(dim), grad_phi_phys(dim), grad_phi_elem(dim);
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);
Tr.SetIntPoint(&ip);
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);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
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;
}
}
}
}
}
}

View File

@@ -0,0 +1,417 @@
module;
#include <mfem.hpp>
module mean_field;
namespace mean_field::integrators {
ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(const mapping::DomainMapper& map) : m_map(map) {};
void ContinuityVolumeIntegrator::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 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];
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];
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);
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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
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;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
const int row = i + c * dof_v;
v_val(c) += v_dofs(row) * shape_v(i);
}
}
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) {
double grad_dot_rhov = 0.0;
for (int c = 0; c < dim; ++c) {
grad_dot_rhov += dshape_rho_phys(i, c) * rho_val * v_val(c);
}
r_rho(i) -= grad_dot_rhov * weight;
}
}
}
void ContinuityVolumeIntegrator::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 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];
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 (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);
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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
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;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
const int row = i + c * dof_v;
v_val(c) += v_dofs(row) * shape_v(i);
}
}
double rho_val = 0.0;
for (int i = 0; i < dof_rho; ++i) {
rho_val += rho_dofs(i) * shape_rho(i);
}
if (drho_dv) {
for (int i = 0; i < dof_rho; ++i) {
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;
}
}
}
}
if (drho_drho) {
for (int i = 0; i < dof_rho; ++i) {
double grad_psi_dot_v = 0.0;
for (int c = 0; c < dim; ++c) {
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;
}
}
}
}
}
ContinuityFaceIntegrator::ContinuityFaceIntegrator(const mapping::DomainMapper& map): m_map(map) {}
void ContinuityFaceIntegrator::AssembleFaceVector(
const mfem::Array<const mfem::FiniteElement *> &el1,
const mfem::Array<const mfem::FiniteElement *> &el2,
mfem::FaceElementTransformations &Tr,
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_rho_minus = el1[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();
if (elvect[0]) {
elvect[0]->SetSize(dim * dof_v_minus + dim * dof_v_plus);
*elvect[0] = 0.0;
}
mfem::Vector &r_rho = *elvect[1];
r_rho.SetSize(dof_rho_minus + dof_rho_plus);
r_rho = 0.0;
const int attr_minus = Tr.Elem1->Attribute;
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) {
return; // No flux contribution for vacuum faces
}
if (Tr.Elem2 == nullptr) {
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
// 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);};
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);
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);
Tr.SetAllIntPoints(&face_ip);
const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint();
const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint();
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);
// v dot n
// u_n = ∑ n_c * ∑ v_kc * φ_k
double u_n = 0.0;
for (int c = 0; c < dim; ++c) {
double v_c = 0.0;
for (int k = 0; k < dof_v_minus; ++k) {
v_c += v_minus_dof(k, c) * shape_v_minus(k);
}
u_n += v_c * n_unit(c);
}
double rho_minus_val = 0.0;
for (int i = 0; i < dof_rho_minus; ++i) {
rho_minus_val += shape_rho_minus(i) * rho_minus_dof(i);
}
double rho_plus_val = 0.0;
for (int i = 0; i < dof_rho_plus; ++i) {
rho_plus_val += shape_rho_plus(i) * rho_plus_dof(i);
}
// Upwind density
// 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
for (int i = 0; i < dof_rho_minus; ++i) {
r_rho(i) += shape_rho_minus(i) * flux_weighted;
}
for (int i = 0; i < dof_rho_plus; ++i) {
r_rho(dof_rho_minus + i) -= shape_rho_plus(i) * flux_weighted;
}
}
}
void ContinuityFaceIntegrator::AssembleFaceGrad(
const mfem::Array<const mfem::FiniteElement *> &el1,
const mfem::Array<const mfem::FiniteElement *> &el2,
mfem::FaceElementTransformations &Tr,
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_rho_minus = el1[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 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;
}
};
size_and_zero_mat(elmats(0, 0), N_v_total, N_v_total);
size_and_zero_mat(elmats(0, 1), N_v_total, N_rho_total);
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;
mfem::DenseMatrix *drho_dv = elmats(1, 0);
mfem::DenseMatrix *drho_drho = elmats(1, 1);
if (!drho_dv && !drho_drho) return;
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);
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);
Tr.SetAllIntPoints(&face_ip);
const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint();
const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint();
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);
double rho_minus_val = 0.0;
for (int i = 0; i < dof_rho_minus; ++i) {
rho_minus_val += shape_rho_minus(i) * rho_dofs(i);
}
double rho_plus_val = 0.0;
for (int i = 0; i < dof_rho_plus; ++i) {
rho_plus_val += shape_rho_plus(i) * rho_dofs(dof_rho_minus + i);
}
const bool upwind_minus = (u_n >= 0.0);
const double rho_up = upwind_minus ? rho_minus_val : rho_plus_val;
// (1, 1)
if (drho_drho) {
const double u_w = u_n * ds;
if (upwind_minus) {
for (int ip = 0; ip < dof_rho_minus; ++ip) {
const double col_w = u_w * shape_rho_minus(ip);
for (int i = 0; i < dof_rho_minus; ++i) {
(*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;
}
}
} 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;
for (int i = 0; i < dof_rho_minus; ++i) {
(*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;
}
}
}
}
// (1, 0)
if (drho_dv) {
const double rho_w = rho_up * ds;
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 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;
}
for (int j = 0; j < dof_rho_plus; ++j) {
(*drho_dv)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w;
}
}
}
}
}
}
bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations& Tr) {
constexpr int VACUUM_ATTR = 3;
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
}
if (Tr.Elem2 == nullptr) {
return true; // Boundary face,
}
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 u_n = 0.0;
for (int c = 0; c < dim; ++c) {
double v_c = 0.0;
for (int k = 0; k < dof_v_minus; ++k) {
v_c += v_dofs(k + c * dof_v_minus) * shape_v_minus(k);
}
u_n += v_c * n_unit(c);
}
return u_n;
}
}

View File

@@ -0,0 +1,156 @@
module;
#include <mfem.hpp>
module mean_field;
namespace mean_field::integrators {
ViscosityIntegrator::ViscosityIntegrator(
const mapping::DomainMapper& map,
const double mu,
const int quad_boost
) : m_map(map), m_mu(mu), m_quad_boost(quad_boost) {}
void ViscosityIntegrator::SetMu(const double mu) { m_mu = mu; }
void ViscosityIntegrator::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;
}
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 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];
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;
}
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);
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcDShape(ip, dshape_v_ref);
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;
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);
}
}
}
double div_v = 0.0;
for (int c = 0; c < dim; ++c) {
div_v += grad_v(c, c);
}
// D_cj = dj v_c + dc v_j - (2/3) δcj ∇v
// R_v_i^c += μ * weight * ∑_j (δj φi) D_cj
const double mu_w = m_mu * weight;
for (int i = 0; i < dof_v; ++i) {
for (int c = 0; c < dim; ++c) {
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;
acc += dshape_v_phys(i, j) * D_cj;
}
r_v(i + c * dof_v) += mu_w * acc;
}
}
}
}
void ViscosityIntegrator::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 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);
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());
for (int q = 0; q < ir->GetNPoints(); ++q) {
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
const double mu_w = m_mu * weight;
for (int i = 0; i < dof_v; ++i) {
for (int n = 0; n < dof_v; ++n) {
double dot_grad = 0.0;
for (int j = 0; j < dim; ++j) {
dot_grad += dshape_v_phys(i, j) * dshape_v_phys(n, j);
}
for (int c = 0; c < dim; ++c) {
const int row = i + c * dof_v;
for (int d = 0; d < dim; ++d) {
const int col = n + d * dof_v;
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);
(*dv_dv)(row, col) += mu_w * val;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,152 @@
module;
#include <mfem.hpp>
module mean_field;
import :mapping.types;
namespace mean_field::mapping {
///////////////////////////////
/// MappedScalarCoefficient ///
//////////////////////////////
MappedScalarCoefficient::MappedScalarCoefficient(
const DomainMapper &map,
mfem::Coefficient &coeff,
const COORDINATE_SPACE coord_space
) : m_map(map),
m_coeff(coeff),
m_coord_space(coord_space) {};
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;
}
}
}
double MappedScalarCoefficient::eval_at_point(mfem::Coefficient &c, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
return c.Eval(T, ip);
}
//////////////////////////////////
/// MappedDiffusionCoefficient ///
//////////////////////////////////
MappedDiffusionCoefficient::MappedDiffusionCoefficient(
const DomainMapper &map,
mfem::Coefficient &sigma,
const int dim
) : mfem::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) {
};
void MappedDiffusionCoefficient::Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
const int dim = height;
T.SetIntPoint(&ip);
mfem::DenseMatrix J(dim, dim), JInv(dim, dim);
m_map.ComputeJacobian(T, J);
const double detJ = J.Det();
mfem::CalcInverse(J, JInv);
if (m_scalar) {
const double sig_val = m_scalar->Eval(T, ip);
mfem::MultABt(JInv, JInv, K);
K *= sig_val * fabs(detJ);
} else {
mfem::DenseMatrix sig_mat(dim, dim);
m_tensor->Eval(sig_mat, T, ip);
mfem::DenseMatrix temp(dim, dim);
Mult(JInv, sig_mat, temp);
MultABt(temp, JInv, K);
K *= fabs(detJ);
}
}
///////////////////////////////
/// MappedVectorCoefficient ///
///////////////////////////////
MappedVectorCoefficient::MappedVectorCoefficient(
const DomainMapper &map,
mfem::VectorCoefficient &coeff
) : mfem::VectorCoefficient(coeff.GetVDim()),
m_map(map),
m_coeff(coeff) {
};
void MappedVectorCoefficient::Eval(mfem::Vector &V, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
const int dim = vdim;
T.SetIntPoint(&ip);
mfem::DenseMatrix JInv(dim, dim);
m_map.ComputeInverseJacobian(T, JInv);
double detJ = m_map.ComputeDetJ(T, ip);
mfem::Vector C_phys(dim);
m_coeff.Eval(C_phys, T, ip);
V.SetSize(dim);
JInv.MultTranspose(C_phys, V);
V *= fabs(detJ);
}
///////////////////////////////////////////
/// PhysicalPositionFunctionCoefficient ///
///////////////////////////////////////////
PhysicalPositionFunctionCoefficient::PhysicalPositionFunctionCoefficient(
const DomainMapper &map,
Func f // std::function<double(const mfem::Vector&)>
) : m_f(std::move(f)),
m_map(map) {};
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) {}
void MappedHDivMassCoefficient::Eval(
mfem::DenseMatrix& matrix,
mfem::ElementTransformation& transformation,
const mfem::IntegrationPoint& integration_point
) {
transformation.SetIntPoint(&integration_point);
mfem::DenseMatrix map_jacobian(height, height);
m_map.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.");
mfem::MultAtB(map_jacobian, map_jacobian, matrix);
matrix *= 1.0 / std::abs(map_determinant);
}
}

View File

@@ -0,0 +1,294 @@
module;
#include <mfem.hpp>
module mean_field;
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) {
InitAllScratchSpaces();
}
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) {
InitAllScratchSpaces();
}
bool DomainMapper::is_vacuum(const mfem::ElementTransformation &T) const {
if (T.ElementType == mfem::ElementTransformation::ELEMENT) {
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
}
return false;
}
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());
throw std::invalid_argument(err_msg);
}
m_d = &d;
InvalidateCache();
}
bool DomainMapper::IsIdentity() const {
return (m_d == nullptr);
}
void DomainMapper::ResetDisplacement() {
m_d = nullptr;
InvalidateCache();
}
void DomainMapper::ComputeJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &J) const {
J.SetSize(m_dim, m_dim);
J = 0.0;
m_J_D = 0.0;
if (IsIdentity()) {
for (int i = 0; i < m_dim; ++i) {
m_J_D(i, i) = 1.0; // Identity mapping
}
} else {
UpdateElementCache(T);
m_dshape.SetSize(m_fe->GetDof(), m_dim);
m_fe->CalcPhysDShape(T, m_dshape);
mfem::MultAtB(m_dof_mat, m_dshape, m_J_D);
for (int i = 0; i < m_dim; ++i) {
m_J_D(i, i) += 1.0;
}
}
if (is_vacuum(T)) {
T.Transform(T.GetIntPoint(), m_x_ref);
if (IsIdentity()) {
m_x_disp = m_x_ref;
} else {
m_shape.SetSize(m_fe->GetDof());
m_fe->CalcShape(T.GetIntPoint(), m_shape);
m_dof_mat.MultTranspose(m_shape, m_d_val);
add(m_x_ref, m_d_val, m_x_disp);
}
ComputeKelvinJacobian(m_x_ref, m_x_disp, m_J_D, J);
} else {
J = m_J_D;
}
}
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
T.SetIntPoint(&ip);
mfem::DenseMatrix J;
ComputeJacobian(T, J);
return J.Det();
}
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);
D.SetSize(m_dim, m_dim);
mfem::MultABt(m_JInv_temp, m_JInv_temp, D);
D *= fabs(detJ);
}
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 {
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 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 {
const int dim = T.GetSpaceDim();
T.SetAllIntPoints(&ip);
mfem::Vector n_raw(dim);
mfem::CalcOrtho(T.Jacobian(), n_raw);
if (IsIdentity()) {
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};
}
// 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();
mfem::DenseMatrix J_map_inv(dim, dim);
mfem::CalcInverse(J_map, J_map_inv);
mfem::Vector n_phys(dim);
J_map_inv.MultTranspose(n_raw, n_phys);
n_phys *= detJ_map;
const double n_phys_mag = n_phys.Norml2();
mfem::Vector n_unit(dim);
n_unit = n_phys;
n_unit /= n_phys_mag;
const double n_raw_mag = n_raw.Norml2();
return FaceQuadratureContext{
.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 {
x_phys.SetSize(m_dim);
T.Transform(ip, m_x_ref);
if (IsIdentity()) {
x_phys = m_x_ref;
} else {
UpdateElementCache(T);
m_shape.SetSize(m_fe->GetDof());
m_fe->CalcShape(ip, m_shape);
m_dof_mat.MultTranspose(m_shape, m_d_val);
add(m_x_ref, m_d_val, x_phys);
}
if (is_vacuum(T)) {
ApplyKelvinMapping(m_x_ref, x_phys);
}
}
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; }
double DomainMapper::GetPhysInfRadius() const {
return 1.0 - m_xi_clamp;
}
size_t DomainMapper::GetCacheHits() const {
return m_cache_hits;
}
size_t DomainMapper::GetCacheMisses() const {
return m_cache_misses;
}
double DomainMapper::GetCacheHitRate() const {
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_misses = 0;
}
void DomainMapper::InitAllScratchSpaces() const {
m_J_D.SetSize(m_dim, m_dim);
m_J_temp.SetSize(m_dim, m_dim);
m_JInv_temp.SetSize(m_dim, m_dim);
m_x_ref.SetSize(m_dim);
m_x_disp.SetSize(m_dim);
m_d_val.SetSize(m_dim);
}
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);
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();
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);
const double denom = 1.0 - xi;
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)));
J.SetSize(m_dim, m_dim);
const double outer_factor = dk_dr / r_ref;
for (int i = 0; i < m_dim; ++i) {
for (int j = 0; j < m_dim; ++j) {
J(i, j) = outer_factor * x_disp(i) * x_ref(j) + k * J_D(i, j);
}
}
}
void DomainMapper::InvalidateCache() const {
m_cached_elem_id = -1;
}
void DomainMapper::UpdateElementCache(const mfem::ElementTransformation &T) const {
if (IsIdentity()) return;
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;
const mfem::FiniteElementSpace *fes = m_d->FESpace();
mfem::Array<int> vdofs;
if (T.ElementType == mfem::ElementTransformation::ELEMENT) {
m_fe = fes->GetFE(m_cached_elem_id);
fes->GetElementVDofs(m_cached_elem_id, vdofs);
} else {
m_fe = fes->GetBE(m_cached_elem_id);
fes->GetBdrElementVDofs(m_cached_elem_id, vdofs);
}
m_d->GetSubVector(vdofs, m_elem_dofs);
const int nd = m_fe->GetDof();
const int vd = fes->GetVDim();
m_dof_mat.UseExternalData(m_elem_dofs.GetData(), nd, vd);
} else {
m_cache_hits++;
}
}
}

View File

@@ -0,0 +1,297 @@
module;
#include "mfem.hpp"
#include <source_location>
#include <cmath>
#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) {
const double s2 = std::pow(phys_x(0), 2) + std::pow(phys_x(1), 2);
return -0.5 * s2 * std::pow(omega, 2);
}
}
namespace mean_field::physics {
GravitySolution grav_potential(
fem::FEM &f,
const utils::Args &args,
const mfem::GridFunction &rho,
const bool phi_warm
) {
mfem::Array<int> outer_bdr_marker(f.mesh->bdr_attributes.Max());
outer_bdr_marker = 0;
outer_bdr_marker[1] = 1;
mfem::ParLinearForm g_rhs(f.RT_fes.get());
// ReSharper disable once CppTooWideScope
std::unique_ptr<mfem::Coefficient> boundary_potential_coeff;
if (!f.has_mapping()) { // We only need to explicitly add a boundary integrator if a mapping is not being used. In the case where the outer domain has been compactified the φ=0 boundary condition is the natural condition and MFEM automatically handles this
auto boundary_potential = [&f](const mfem::Vector& x_physical) {
return l2_multipole_potential(f, utils::MASS, x_physical);
};
boundary_potential_coeff = std::make_unique<mfem::FunctionCoefficient>(boundary_potential);
auto boundary_integrator = std::make_unique<mfem::VectorFEBoundaryFluxLFIntegrator>(*boundary_potential_coeff);
const mfem::FiniteElement& boundary_element = *f.RT_fes->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);
}
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());
std::unique_ptr<mfem::Coefficient> mapped_source_coeff;
mfem::Coefficient* active_source_coeff = &source_coeff;
quadrature::MappingKind source_mapping_kind = quadrature::MappingKind::none;
if (f.has_mapping()) {
mapped_source_coeff = std::make_unique<mapping::MappedScalarCoefficient>(*f.mapping, source_coeff);
active_source_coeff = mapped_source_coeff.get();
source_mapping_kind = quadrature::MappingKind::general;
}
auto source_integrator = std::make_unique<mfem::DomainLFIntegrator>(*active_source_coeff);
const mfem::FiniteElement& source_test_element = *f.L2_fes->GetTypicalFE();
const mfem::ElementTransformation& source_transformation = *f.mesh->GetElementTransformation(0);
const int source_coefficient_order = f.L2_fes->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_rhs.Assemble();
mfem::BlockVector RHS(f.gravity_block_true_offsets);
RHS.GetBlock(0) = *g_rhs.ParallelAssemble();
RHS.GetBlock(1) = *f_rhs.ParallelAssemble();
mfem::BlockVector X(f.gravity_block_true_offsets);
X = 0.0;
f.gravity_context.minres->SetOperator(*f.gravity_context.block_A);
f.gravity_context.minres->Mult(RHS, X);
GravitySolution solution(f);
solution.gradPhi.SetFromTrueDofs(X.GetBlock(0));
solution.phi.SetFromTrueDofs(X.GetBlock(1));
return solution;
}
mfem::GridFunction get_potential(
fem::FEM &fem,
const utils::Args &args,
const mfem::GridFunction &rho,
const bool warm
) {
auto phi = grav_potential(fem, args, rho, warm);
if (args.r.enabled) {
auto rot = [&fem, &args](const mfem::Vector &x) {
mfem::Vector rel_x = x;
rel_x -= fem.com;
return centrifugal_potential(rel_x, args.r.omega);
};
std::unique_ptr<mfem::Coefficient> centrifugal_coeff;
if (fem.has_mapping()) {
centrifugal_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, rot);
} else {
centrifugal_coeff = std::make_unique<mfem::FunctionCoefficient>(rot);
}
mfem::GridFunction centrifugal_gf(fem.H1_fes.get());
centrifugal_gf.ProjectCoefficient(*centrifugal_coeff);
phi.phi += centrifugal_gf;
}
return phi.phi;
}
mfem::DenseMatrix compute_quadrupole_moment_tensor(
const fem::FEM &fem,
const mfem::GridFunction &rho,
const mfem::Vector &com
) {
const int dim = fem.mesh->Dimension();
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;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = *fem.int_rule;
for (int j = 0; j < ir.GetNPoints(); ++j) {
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
trans->SetIntPoint(&ip);
double weight = trans->Weight() * ip.weight;
if (fem.has_mapping()) {
weight *= fem.mapping->ComputeDetJ(*trans, ip);
}
const double rho_val = rho.GetValue(i, ip);
mfem::Vector phys_point(dim);
if (fem.has_mapping()) {
fem.mapping->GetPhysicalPoint(*trans, ip, phys_point);
} else {
trans->Transform(ip, phys_point);
}
mfem::Vector x_prime(dim);
double r_sq = 0.0;
for (int d = 0; d < dim; ++d) {
x_prime(d) = phys_point(d) - com(d);
r_sq += x_prime(d) * x_prime(d);
}
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;
local_Q(m, n) += rho_val * contrib * weight;
}
}
}
}
mfem::DenseMatrix global_Q(dim, dim);
MPI_Allreduce(local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
return global_Q;
}
double l2_multipole_potential(
const fem::FEM &fem,
const double total_mass,
const mfem::Vector &phys_x
) {
const double r = phys_x.Norml2();
if (r < 1e-12) return 0.0;
const int dim = fem.mesh->Dimension();
mfem::Vector n(phys_x);
n /= r;
double l2_mult_factor = 0.0;
for (int i = 0; i < dim; ++i) {
for (int j = 0; j < dim; ++j) {
l2_mult_factor += fem.Q(i, j) * n(i) * n(j);
}
}
const double l2_contrib = -(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor;
const double l0_contrib = -utils::G * total_mass / r;
// l1 contribution is zero for a system centered on its COM
return l0_contrib + l2_contrib;
}
void update_stiffness_matrix(fem::FEM &f) {
mfem::Array<int> empty_tdofs;
// ==========================================
// 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);
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);
} else {
f.gravity_context.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;
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();
// ==========================================
// 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);
auto divergence_discretization_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement& divergence_discretization_test_element = *f.L2_fes->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();
// ==========================================
// 3. Assemble Global Block Operator
// ==========================================
f.gravity_context.BT = std::make_unique<mfem::TransposeOperator>(f.gravity_context.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());
// ==========================================
// 4. Construct a mapped Schur preconditioner
// ==========================================
mfem::Vector mass_diagonal(f.RT_fes->GetTrueVSize());
f.gravity_context.m_form->AssembleDiagonal(mass_diagonal);
mfem::Vector inverse_mass_diagonal(mass_diagonal);
for (int i = 0; i < inverse_mass_diagonal.Size(); ++i) {
MFEM_VERIFY(std::isfinite(inverse_mass_diagonal(i)) && inverse_mass_diagonal(i) > 0.0, "Mapped RT mass matrix has a non-positive or non-finite diagonal entry.");
inverse_mass_diagonal(i) = 1.0 / inverse_mass_diagonal(i);
}
mfem::ParMixedBilinearForm b_preconditioner(f.RT_fes.get(), f.L2_fes.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);
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());
b_preconditioner.Assemble();
b_preconditioner.Finalize();
std::unique_ptr<mfem::HypreParMatrix> b_matrix(b_preconditioner.ParallelAssemble());
std::unique_ptr<mfem::HypreParMatrix> inverse_mass_b_transpose(b_matrix->Transpose());
inverse_mass_b_transpose->ScaleRows(inverse_mass_diagonal);
f.gravity_context.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());
}
}

View File

@@ -0,0 +1,38 @@
module;
#include "mean_field.h"
module mean_field;
namespace mean_field::physics {
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;
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = *fem.int_rule;
for (int j = 0; j < ir.GetNPoints(); j++) {
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
T->SetIntPoint(&ip);
const double rho_hat = rho_ref.GetValue(i, ip);
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 detJ = std::fabs(fem.mapping->ComputeDetJ(*T, ip));
const double weight = T->Weight() * ip.weight * detJ;
local_I += rho_hat * r_cyl_sq * weight;
}
}
double global_I = 0.0;
MPI_Allreduce(&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
return global_I;
}
}

View File

@@ -0,0 +1,188 @@
module;
#include <mfem.hpp>
module mean_field;
namespace mean_field::utils {
bool GetReferencePoint(
const fem::FEM &fem,
const mfem::Vector &x_phys_target,
mfem::Vector &x_ref
) {
const int dim = fem.mesh->Dimension();
x_ref = x_phys_target;
mfem::Array<int> init_elem;
mfem::Array<mfem::IntegrationPoint> init_ip;
mfem::DenseMatrix init_P(dim, 1);
init_P.SetCol(0, x_ref);
fem.mesh->FindPoints(init_P, init_elem, init_ip, false);
if (init_elem.Size() == 0 || init_elem[0] < 0) {
mfem::Vector origin(dim);
origin = 0.0;
mfem::DenseMatrix P_origin(dim, 1);
P_origin.SetCol(0, origin);
mfem::Array<int> origin_elem;
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]);
T0->SetIntPoint(&origin_ip[0]);
mfem::DenseMatrix J0(dim, dim), J0_inv(dim, dim);
fem.mapping->ComputeJacobian(*T0, J0);
mfem::CalcInverse(J0, J0_inv);
J0_inv.Mult(x_phys_target, x_ref);
}
init_P.SetCol(0, x_ref);
fem.mesh->FindPoints(init_P, init_elem, init_ip, false);
if (init_elem.Size() == 0 || init_elem[0] < 0) {
double norm = x_ref.Norml2();
if (norm > 1e-15) {
double scale = 0.9 * RADIUS / norm;
if (scale < 1.0) {
x_ref *= scale;
}
}
init_P.SetCol(0, x_ref);
fem.mesh->FindPoints(init_P, init_elem, init_ip, false);
if (init_elem.Size() == 0 || init_elem[0] < 0) {
x_ref = 0.0;
}
}
}
constexpr int max_iter = 50;
mfem::Array<int> elem_ids;
mfem::Array<mfem::IntegrationPoint> ips;
mfem::DenseMatrix P(dim, 1);
mfem::Vector d(dim);
mfem::Vector residual(dim);
mfem::Vector step(dim);
mfem::DenseMatrix J_map(dim, dim);
mfem::DenseMatrix J_map_inv(dim, dim);
int find_failures = 0;
for (int iter = 0; iter < max_iter; ++iter) {
P.SetCol(0, x_ref);
fem.mesh->FindPoints(P, elem_ids, ips, false);
if (elem_ids.Size() == 0 || elem_ids[0] < 0) {
find_failures++;
if (find_failures > 10) return false;
double norm = x_ref.Norml2();
if (norm > 1e-15) {
x_ref *= 0.5 * RADIUS / norm;
} else {
x_ref = 0.0;
}
continue;
}
int elemID = elem_ids[0];
const mfem::IntegrationPoint &ip = ips[0];
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(elemID);
T->SetIntPoint(&ip);
mfem::Vector current_x_phys(dim);
fem.mapping->GetPhysicalPoint(*T, ip, current_x_phys);
for (int i = 0; i < dim; ++i) {
residual(i) = current_x_phys(i) - x_phys_target(i);
}
if (constexpr double tol = 1e-12; residual.Norml2() < tol) {
return true;
}
fem.mapping->ComputeJacobian(*T, J_map);
mfem::CalcInverse(J_map, J_map_inv);
J_map_inv.Mult(residual, step);
double alpha = 1.0;
mfem::Vector x_ref_candidate(dim);
bool found_valid = false;
for (int ls = 0; ls < 8; ++ls) {
x_ref_candidate = x_ref;
x_ref_candidate.Add(-alpha, step);
P.SetCol(0, x_ref_candidate);
fem.mesh->FindPoints(P, elem_ids, ips, false);
if (elem_ids.Size() > 0 && elem_ids[0] >= 0) {
found_valid = true;
break;
}
alpha *= 0.5;
}
if (found_valid) {
x_ref = x_ref_candidate;
} else {
find_failures++;
if (find_failures > 10) return false;
if (double norm = x_ref.Norml2(); norm > 1e-15) {
x_ref *= 0.5 * RADIUS / norm;
} else {
x_ref = 0.0;
}
}
}
return false;
}
double EvalGridFunctionAtPoint(
const fem::FEM &fem,
const mfem::ParGridFunction &u,
const mfem::Vector &x,
const mapping::COORDINATE_SPACE vspace,
const mapping::COORDINATE_SPACE rspace
) {
mfem::Vector x_search;
if (vspace == mapping::COORDINATE_SPACE::PHYSICAL && fem.has_mapping()) {
GetReferencePoint(fem, x, x_search);
} else {
x_search = x;
}
mfem::Array<int> elem_ids;
mfem::Array<mfem::IntegrationPoint> ips;
mfem::DenseMatrix P(x_search.Size(), 1);
P.SetCol(0, x_search);
fem.mesh->FindPoints(P, elem_ids, ips, false);
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.");
}
local_val = val;
}
double global_val = 0.0;
MPI_Allreduce(&local_val, &global_val, 1, MPI_DOUBLE, MPI_MAX, fem.H1_fes->GetComm());
return global_val;
}
}

View File

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