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