feat(surface): major work on implementing surface constraints in a presciption agnostic manner

This commit is contained in:
2026-08-30 16:41:14 -04:00
parent 36adfa1174
commit 0a7f18c5c7
95 changed files with 30144 additions and 25766 deletions

View File

@@ -14,20 +14,23 @@ namespace {
) {
switch (domain) {
case mean_field::utils::DOMAINS::CORE:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Core, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Core, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::ENVELOPE:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Envelope, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Envelope, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::ALL:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::All, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::All, DomainSchema>(mesh);
case mean_field::utils::DOMAINS::STELLAR:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Stellar, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Stellar, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::VACUUM:
return mean_field::utils::domain::make_attribute_marker<
mean_field::utils::domain::Vacuum, DomainSchema>(mesh);
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Vacuum, DomainSchema>(
mesh
);
}
MFEM_ABORT("Unsupported integration domain.");
}
@@ -62,15 +65,14 @@ namespace mean_field::analysis {
mfem::LinearForm lf(fem.densityFes.get());
mfem::GridFunctionCoefficient gf_c(&gf);
double local_integral;
mfem::Array<int> elem_markers = make_domain_marker(*fem.mesh, domain);
mfem::Array<int> elem_markers = make_domain_marker(*fem.mesh, domain);
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) {
mapping::MappedScalarCoefficient mapped_gf_c(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, gf_c
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, gf_c
);
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM
@@ -107,16 +109,14 @@ namespace mean_field::analysis {
) {
const int dim = fem.mesh->Dimension();
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
mfem::Vector local_com(dim);
local_com = 0.0;
double local_mass = 0.0;
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(
fem.mesh->GetAttribute(i)))
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = get_density_rule<field::Density::Form::CenterOfMass>(
@@ -129,16 +129,15 @@ namespace mean_field::analysis {
mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) ==
mapping::MappingStatus::valid,
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
"Center-of-mass integration encountered an invalid mapping."
);
const double weight = mapping_context.quadrature.weight;
double rho_val = rho.GetValue(i, ip);
const double weight = mapping_context.quadrature.weight;
double rho_val = rho.GetValue(i, ip);
const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
const double mass_term = rho_val * weight;
const double mass_term = rho_val * weight;
local_mass += mass_term;
for (int d = 0; d < dim; ++d) {
@@ -183,8 +182,7 @@ namespace mean_field::analysis {
std::unique_ptr<mfem::Coefficient> s2_coeff;
if (fem.has_mapping()) {
s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, s2_func
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, s2_func
);
} else {
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
@@ -204,8 +202,7 @@ namespace mean_field::analysis {
double local_I = 0.0;
if (fem.has_mapping()) {
mapping::MappedScalarCoefficient mapped_integrand(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate, I_integrand
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, I_integrand
);
auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand);
integrator->SetIntRule(&integration_rule);
@@ -239,18 +236,16 @@ namespace mean_field::analysis {
double local_volume = 0.0;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
for (int e = 0; e < mesh.GetNE(); ++e) {
const int attr = mesh.GetAttribute(e);
const bool selected =
domain == utils::DOMAINS::ALL ||
(domain == utils::DOMAINS::STELLAR &&
DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(attr)) ||
(domain == utils::DOMAINS::VACUUM &&
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr));
const int attr = mesh.GetAttribute(e);
const bool selected = domain == utils::DOMAINS::ALL ||
(domain == utils::DOMAINS::STELLAR &&
DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(attr)) ||
(domain == utils::DOMAINS::VACUUM &&
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr));
if (!selected)
continue;
mfem::ElementTransformation *T = mesh.GetElementTransformation(e);
@@ -266,8 +261,7 @@ namespace mean_field::analysis {
if (physical) {
mapping::VolumeMappingContext context;
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*T, ip, context) ==
mapping::MappingStatus::valid,
mapping_evaluator.EvaluateVolume(*T, ip, context) == mapping::MappingStatus::valid,
"Mesh-volume integration encountered an invalid mapping."
);
dV = context.quadrature.weight;

View File

@@ -21,361 +21,322 @@ import :utils.misc;
import :utils.user;
namespace mean_field::fem {
FEM setup_fem(const std::string &filename, const utils::Args &args,
const int extraRefine) {
FEM fem;
FEM setup_fem(
const std::string &filename,
const utils::Args &args,
const int extraRefine
) {
FEM fem;
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;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
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;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
// =====================================================================
// Section 1: Mesh construction
// =====================================================================
// =====================================================================
// Section 1: Mesh construction
// =====================================================================
fem.smesh = stroid::IO::LoadStroidMesh(filename).value();
fem.smesh = stroid::IO::LoadStroidMesh(filename).value();
if (extraRefine > 0) {
stroid::refinement::UniformRefinement(fem.smesh, extraRefine);
}
if (extraRefine > 0) {
stroid::refinement::UniformRefinement(fem.smesh, extraRefine);
}
int mpiSize = 1;
MPI_Comm_size(MPI_COMM_WORLD, &mpiSize);
int mpiSize = 1;
MPI_Comm_size(MPI_COMM_WORLD, &mpiSize);
const std::unique_ptr<int[]> meshPartitioning(
fem.smesh.mesh->GeneratePartitioning(mpiSize, 1));
const std::unique_ptr<int[]> meshPartitioning(fem.smesh.mesh->GeneratePartitioning(mpiSize, 1));
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh,
meshPartitioning.get(), 1);
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1);
fem.mesh->EnsureNodes();
fem.mesh->EnsureNodes();
// =====================================================================
// Section 2: Exterior compactification coordinate
// =====================================================================
// =====================================================================
// Section 2: Exterior compactification coordinate
// =====================================================================
if (fem.smesh.exterior_coordinate == nullptr) {
throw std::runtime_error("Exterior coordinate not set.");
}
if (fem.smesh.exterior_coordinate == nullptr) {
throw std::runtime_error("Exterior coordinate not set.");
}
if (fem.smesh.exterior_coordinate->space == nullptr) {
throw std::runtime_error("Space for exterior coordinate not set.");
}
if (fem.smesh.exterior_coordinate->space == nullptr) {
throw std::runtime_error("Space for exterior coordinate not set.");
}
if (fem.smesh.exterior_coordinate->values == nullptr) {
throw std::runtime_error("Values for exterior coordinate not set.");
}
if (fem.smesh.exterior_coordinate->values == nullptr) {
throw std::runtime_error("Values for exterior coordinate not set.");
}
const mfem::FiniteElementSpace &serialCoordinateSpace =
*fem.smesh.exterior_coordinate->space;
const mfem::FiniteElementSpace &serialCoordinateSpace = *fem.smesh.exterior_coordinate->space;
const mfem::GridFunction &serialCoordinate =
*fem.smesh.exterior_coordinate->values;
const mfem::GridFunction &serialCoordinate = *fem.smesh.exterior_coordinate->values;
if (serialCoordinate.FESpace() != &serialCoordinateSpace) {
throw std::runtime_error(
"Exterior coordinate values are not associated with the "
"supplied finite-element space.");
}
if (serialCoordinate.FESpace() != &serialCoordinateSpace) {
throw std::runtime_error(
"Exterior coordinate values are not associated with the "
"supplied finite-element space."
);
}
if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) {
throw std::runtime_error(
"Exterior coordinate space is not associated with the "
"loaded STROID mesh.");
}
if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) {
throw std::runtime_error(
"Exterior coordinate space is not associated with the "
"loaded STROID mesh."
);
}
if (serialCoordinateSpace.GetVDim() != 1) {
throw std::runtime_error("Exterior coordinate must be a scalar field.");
}
if (serialCoordinateSpace.GetVDim() != 1) {
throw std::runtime_error("Exterior coordinate must be a scalar field.");
}
if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) {
throw std::runtime_error(
"Exterior coordinate value count does not match its "
"finite-element space.");
}
if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) {
throw std::runtime_error(
"Exterior coordinate value count does not match its "
"finite-element space."
);
}
const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder();
const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder();
const int dimension = fem.mesh->Dimension();
const int dimension = fem.mesh->Dimension();
fem.compactificationFec =
std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension);
fem.compactificationFec = std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension);
fem.compactificationFes = std::make_unique<mfem::ParFiniteElementSpace>(
fem.mesh.get(), fem.compactificationFec.get());
fem.compactificationFes =
std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.compactificationFec.get());
mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate,
meshPartitioning.get());
mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate, meshPartitioning.get());
if (distributedCoordinate.Size() != fem.compactificationFes->GetVSize()) {
throw std::runtime_error(
"Distributed exterior coordinate does not match the "
"constructed parallel finite-element space.");
}
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 = std::make_unique<mfem::ParGridFunction>(fem.compactificationFes.get());
*fem.compactificationCoordinate = distributedCoordinate;
*fem.compactificationCoordinate = distributedCoordinate;
double localMinimum = std::numeric_limits<double>::infinity();
double localMinimum = std::numeric_limits<double>::infinity();
double localMaximum = -std::numeric_limits<double>::infinity();
double localMaximum = -std::numeric_limits<double>::infinity();
for (int index = 0; index < fem.compactificationCoordinate->Size(); ++index) {
const double value = (*fem.compactificationCoordinate)(index);
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.");
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: Multipole data
// =====================================================================
fem.com.SetSize(dimension);
fem.com = 0.0;
fem.Q.SetSize(dimension, dimension);
fem.Q = 0.0;
// =====================================================================
// Section 5: Boundary markers
// =====================================================================
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.inf_bounds = 0;
fem.boundaryContext.stellar_bounds = 0;
fem.boundaryContext.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
// =====================================================================
// Section 7: Quadrature policy
// =====================================================================
const quadrature::QuadratureOptions &quadratureOptions = args.quadrature;
if (quadratureOptions.validation.reject_negative_boosts && quadratureOptions.global_boost < 0) {
throw std::invalid_argument("Global quadrature boost cannot be negative.");
}
quadrature::RuleSet quadratureRuleSet =
quadrature::make_rule_set(quadratureOptions.mode, quadratureOptions.global_boost);
if (quadratureOptions.fallback_fixed_order.has_value()) {
if (*quadratureOptions.fallback_fixed_order < 0) {
throw std::invalid_argument("Fallback quadrature order cannot be negative.");
}
quadratureRuleSet.fallback.fixed_order = quadratureOptions.fallback_fixed_order;
}
auto apply_quadrature_options = [&quadratureOptions](
quadrature::RuleControl &ruleControl,
const quadrature::QuadratureTermOptions &termOptions
) {
if (termOptions.fixed_order.has_value() && *termOptions.fixed_order < 0) {
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
}
if (quadratureOptions.validation.reject_negative_boosts && termOptions.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
ruleControl.boost += termOptions.additional_boost;
if (termOptions.fixed_order.has_value()) {
ruleControl.fixed_order = termOptions.fixed_order;
}
};
apply_quadrature_options(quadratureRuleSet.gravity_hdiv_mass, quadratureOptions.gravity_hdiv_mass);
apply_quadrature_options(quadratureRuleSet.gravity_divergence, quadratureOptions.gravity_divergence);
apply_quadrature_options(quadratureRuleSet.gravity_source, quadratureOptions.gravity_source);
apply_quadrature_options(quadratureRuleSet.gravity_force, quadratureOptions.gravity_force);
apply_quadrature_options(quadratureRuleSet.gravity_boundary, quadratureOptions.gravity_boundary);
apply_quadrature_options(quadratureRuleSet.centrifugal, quadratureOptions.centrifugal);
apply_quadrature_options(quadratureRuleSet.density_projection, quadratureOptions.density_projection);
apply_quadrature_options(quadratureRuleSet.eos_closure, quadratureOptions.eos_closure);
apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium, quadratureOptions.hydrostatic_equilibrium);
apply_quadrature_options(quadratureRuleSet.isobaric_surface, quadratureOptions.isobaric_surface);
apply_quadrature_options(quadratureRuleSet.mesh_extension, quadratureOptions.mesh_extension);
apply_quadrature_options(quadratureRuleSet.mass_conservation, quadratureOptions.mass_conservation);
apply_quadrature_options(quadratureRuleSet.mass_normalization, quadratureOptions.mass_normalization);
apply_quadrature_options(quadratureRuleSet.center_of_mass, quadratureOptions.center_of_mass);
apply_quadrature_options(quadratureRuleSet.quadrupole, quadratureOptions.quadrupole);
apply_quadrature_options(quadratureRuleSet.gravitational_energy, quadratureOptions.gravitational_energy);
apply_quadrature_options(quadratureRuleSet.pressure_integral, quadratureOptions.pressure_integral);
apply_quadrature_options(quadratureRuleSet.pressure_force, quadratureOptions.pressure_force);
apply_quadrature_options(quadratureRuleSet.virial, quadratureOptions.virial);
apply_quadrature_options(quadratureRuleSet.error_norm, quadratureOptions.error_norm);
apply_quadrature_options(quadratureRuleSet.roles.discretization, quadratureOptions.roles.discretization);
apply_quadrature_options(quadratureRuleSet.roles.preconditioner, quadratureOptions.roles.preconditioner);
apply_quadrature_options(quadratureRuleSet.roles.diagnostic, quadratureOptions.roles.diagnostic);
apply_quadrature_options(quadratureRuleSet.roles.projection, quadratureOptions.roles.projection);
fem.quadratureFactory =
std::make_unique<quadrature::RuleFactory>(quadrature::Policy(std::move(quadratureRuleSet)));
// =====================================================================
// Section 11: Stateless domain mapper
// =====================================================================
auto exteriorDomain =
std::make_unique<const mapping::compactification::KelvinCompactification>(args.kelvin_options);
MFEM_VERIFY(
args.domain_mapper_options.vacuum_element_attribute ==
DomainSchema::template material_attribute<utils::domain::Vacuum>(),
"The domain-mapper compactification attribute must match the vacuum "
"material registered by the "
"production domain schema."
);
fem.domainMapperStateless =
std::make_unique<mapping::DomainMapper>(args.domain_mapper_options, std::move(exteriorDomain));
return fem;
}
localMinimum = std::min(localMinimum, value);
localMaximum = std::max(localMaximum, value);
}
double globalMinimum = 0.0;
double globalMaximum = 0.0;
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN,
MPI_COMM_WORLD);
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX,
MPI_COMM_WORLD);
constexpr double coordinateTolerance = 1.0e-12;
if (globalMinimum < -coordinateTolerance ||
globalMaximum > 1.0 + coordinateTolerance) {
throw std::runtime_error("Exterior coordinate lies outside the expected "
"interval [0, 1].");
}
// =====================================================================
// Section 3: Compile-time field realization
// =====================================================================
// ---------------------------------------------------------------------
// Gravity potential: scalar L2
// ---------------------------------------------------------------------
fem.gravityPotentialFec = GravityField::make_fec<GravityPotential>(dimension);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(
*fem.mesh, *fem.gravityPotentialFec);
// ---------------------------------------------------------------------
// Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem.
// ---------------------------------------------------------------------
fem.gravityFluxFec = GravityField::make_fec<GravityFlux>(dimension);
fem.gravityFluxFes =
GravityField::make_fespace<GravityFlux>(*fem.mesh, *fem.gravityFluxFec);
// ---------------------------------------------------------------------
// Displacement: vector H1. Ordering is encoded by field.mfem.
// ---------------------------------------------------------------------
fem.displacementFec =
DisplacementField::make_fec<DisplacementVector>(dimension);
fem.displacementFes = DisplacementField::make_fespace<DisplacementVector>(
*fem.mesh, *fem.displacementFec);
fem.displacement =
std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
*fem.displacement = 0.0;
// ---------------------------------------------------------------------
// Density: scalar discontinuous L2
// ---------------------------------------------------------------------
fem.densityFec = DensityField::make_fec<DensityScalar>(dimension);
fem.densityFes =
DensityField::make_fespace<DensityScalar>(*fem.mesh, *fem.densityFec);
// ---------------------------------------------------------------------
// Specific enthalpy: scalar continuous H1
// ---------------------------------------------------------------------
fem.enthalpyFec = EnthalpyField::make_fec<EnthalpyScalar>(dimension);
fem.enthalpyFes =
EnthalpyField::make_fespace<EnthalpyScalar>(*fem.mesh, *fem.enthalpyFec);
// =====================================================================
// Section 4: Multipole data
// =====================================================================
fem.com.SetSize(dimension);
fem.com = 0.0;
fem.Q.SetSize(dimension, dimension);
fem.Q = 0.0;
// =====================================================================
// Section 5: Boundary markers
// =====================================================================
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount);
fem.boundaryContext.inf_bounds = 0;
fem.boundaryContext.stellar_bounds = 0;
fem.boundaryContext
.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext
.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) -
1] = 1;
// =====================================================================
// Section 7: Quadrature policy
// =====================================================================
const quadrature::QuadratureOptions &quadratureOptions = args.quadrature;
if (quadratureOptions.validation.reject_negative_boosts &&
quadratureOptions.global_boost < 0) {
throw std::invalid_argument("Global quadrature boost cannot be negative.");
}
quadrature::RuleSet quadratureRuleSet = quadrature::make_rule_set(
quadratureOptions.mode, quadratureOptions.global_boost);
if (quadratureOptions.fallback_fixed_order.has_value()) {
if (*quadratureOptions.fallback_fixed_order < 0) {
throw std::invalid_argument(
"Fallback quadrature order cannot be negative.");
}
quadratureRuleSet.fallback.fixed_order =
quadratureOptions.fallback_fixed_order;
}
auto apply_quadrature_options = [&quadratureOptions](
quadrature::RuleControl &ruleControl,
const quadrature::QuadratureTermOptions
&termOptions) {
if (termOptions.fixed_order.has_value() && *termOptions.fixed_order < 0) {
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
}
if (quadratureOptions.validation.reject_negative_boosts &&
termOptions.additional_boost < 0) {
throw std::invalid_argument("Term quadrature boost cannot be negative.");
}
ruleControl.boost += termOptions.additional_boost;
if (termOptions.fixed_order.has_value()) {
ruleControl.fixed_order = termOptions.fixed_order;
}
};
apply_quadrature_options(quadratureRuleSet.gravity_hdiv_mass,
quadratureOptions.gravity_hdiv_mass);
apply_quadrature_options(quadratureRuleSet.gravity_divergence,
quadratureOptions.gravity_divergence);
apply_quadrature_options(quadratureRuleSet.gravity_source,
quadratureOptions.gravity_source);
apply_quadrature_options(quadratureRuleSet.gravity_force,
quadratureOptions.gravity_force);
apply_quadrature_options(quadratureRuleSet.gravity_boundary,
quadratureOptions.gravity_boundary);
apply_quadrature_options(quadratureRuleSet.centrifugal,
quadratureOptions.centrifugal);
apply_quadrature_options(quadratureRuleSet.density_projection,
quadratureOptions.density_projection);
apply_quadrature_options(quadratureRuleSet.eos_closure,
quadratureOptions.eos_closure);
apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium,
quadratureOptions.hydrostatic_equilibrium);
apply_quadrature_options(quadratureRuleSet.isobaric_surface,
quadratureOptions.isobaric_surface);
apply_quadrature_options(quadratureRuleSet.mesh_extension,
quadratureOptions.mesh_extension);
apply_quadrature_options(quadratureRuleSet.mass_conservation,
quadratureOptions.mass_conservation);
apply_quadrature_options(quadratureRuleSet.mass_normalization,
quadratureOptions.mass_normalization);
apply_quadrature_options(quadratureRuleSet.center_of_mass,
quadratureOptions.center_of_mass);
apply_quadrature_options(quadratureRuleSet.quadrupole,
quadratureOptions.quadrupole);
apply_quadrature_options(quadratureRuleSet.gravitational_energy,
quadratureOptions.gravitational_energy);
apply_quadrature_options(quadratureRuleSet.pressure_integral,
quadratureOptions.pressure_integral);
apply_quadrature_options(quadratureRuleSet.pressure_force,
quadratureOptions.pressure_force);
apply_quadrature_options(quadratureRuleSet.virial, quadratureOptions.virial);
apply_quadrature_options(quadratureRuleSet.error_norm,
quadratureOptions.error_norm);
apply_quadrature_options(quadratureRuleSet.roles.discretization,
quadratureOptions.roles.discretization);
apply_quadrature_options(quadratureRuleSet.roles.preconditioner,
quadratureOptions.roles.preconditioner);
apply_quadrature_options(quadratureRuleSet.roles.diagnostic,
quadratureOptions.roles.diagnostic);
apply_quadrature_options(quadratureRuleSet.roles.projection,
quadratureOptions.roles.projection);
fem.quadratureFactory = std::make_unique<quadrature::RuleFactory>(
quadrature::Policy(std::move(quadratureRuleSet)));
// =====================================================================
// Section 11: Stateless domain mapper
// =====================================================================
auto exteriorDomain =
std::make_unique<const mapping::compactification::KelvinCompactification>(
args.kelvin_options);
MFEM_VERIFY(
args.domain_mapper_options.vacuum_element_attribute ==
DomainSchema::template material_attribute<utils::domain::Vacuum>(),
"The domain-mapper compactification attribute must match the vacuum "
"material registered by the "
"production domain schema.");
fem.domainMapperStateless = std::make_unique<mapping::DomainMapper>(
args.domain_mapper_options, std::move(exteriorDomain));
return fem;
}
} // namespace mean_field::fem

View File

@@ -9,7 +9,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(mapper, displacement, compactification_coordinate) {
: m_mapping(
mapper,
displacement,
compactification_coordinate
) {
}
void AdvectionIntegrator::AssembleElementVector(

View File

@@ -9,7 +9,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega
)
: m_mapping(mapper, displacement, compactification_coordinate),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_omega(3) {
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
m_omega = omega;

View File

@@ -10,7 +10,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega
)
: m_mapping(mapper, displacement, compactification_coordinate),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_omega(omega) {
m_omega_mat.SetSize(3, 3);
m_omega_mat = 0.0;

View File

@@ -19,7 +19,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &compactification_coordinate,
const GravityForceJacobianMode jacobian_mode
)
: m_mapping(mapper, displacement, compactification_coordinate),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_jacobian_mode(jacobian_mode) {
}

View File

@@ -9,7 +9,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(mapper, displacement, compactification_coordinate) { };
: m_mapping(
mapper,
displacement,
compactification_coordinate
) { };
void ContinuityVolumeIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -174,7 +178,11 @@ namespace mean_field::integrators {
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(mapper, displacement, compactification_coordinate) {
: m_mapping(
mapper,
displacement,
compactification_coordinate
) {
}
void ContinuityFaceIntegrator::AssembleFaceVector(
@@ -206,11 +214,11 @@ 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;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const int attr_minus = Tr.Elem1->Attribute;
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) ||
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) {
return; // No flux contribution for vacuum faces
@@ -416,9 +424,9 @@ namespace mean_field::integrators {
}
bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations &Tr) {
const int attr_minus = Tr.Elem1->Attribute;
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const int attr_minus = Tr.Elem1->Attribute;
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) ||
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) {
return true; // No flux contribution for vacuum faces

View File

@@ -10,7 +10,11 @@ namespace mean_field::integrators {
const double mu,
const int quad_boost
)
: m_mapping(mapper, displacement, compactification_coordinate),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_mu(mu),
m_quad_boost(quad_boost) {
}

View File

@@ -15,7 +15,11 @@ namespace mean_field::mapping {
Coefficient &coeff,
const COORDINATE_SPACE coord_space
)
: m_mapping(mapper, displacement, compactification_coordinate),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_coeff(coeff),
m_coord_space(coord_space) { };
@@ -28,7 +32,7 @@ namespace mean_field::mapping {
switch (m_coord_space) {
case COORDINATE_SPACE::PHYSICAL: {
f_val = eval_at_point(m_coeff, T, ip);
f_val = eval_at_point(m_coeff, T, ip);
VolumeMappingContext context;
MFEM_VERIFY(
m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid,
@@ -63,7 +67,11 @@ namespace mean_field::mapping {
const int dim
)
: MatrixCoefficient(dim),
m_mapping(mapper, displacement, compactification_coordinate),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_scalar(&sigma),
m_tensor(nullptr) { };
@@ -74,7 +82,11 @@ namespace mean_field::mapping {
MatrixCoefficient &sigma
)
: MatrixCoefficient(sigma.GetHeight()),
m_mapping(mapper, displacement, compactification_coordinate),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_scalar(nullptr),
m_tensor(&sigma) { };
@@ -92,7 +104,7 @@ namespace mean_field::mapping {
"Mapped diffusion coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian;
const double detJ = context.mapping.mapping_determinant;
const double detJ = context.mapping.mapping_determinant;
if (m_scalar) {
const double sig_val = m_scalar->Eval(T, ip);
@@ -120,7 +132,11 @@ namespace mean_field::mapping {
VectorCoefficient &coeff
)
: VectorCoefficient(coeff.GetVDim()),
m_mapping(mapper, displacement, compactification_coordinate),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_coeff(coeff) { };
void MappedVectorCoefficient::Eval(
@@ -137,7 +153,7 @@ namespace mean_field::mapping {
"Mapped vector coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian;
const double detJ = context.mapping.mapping_determinant;
const double detJ = context.mapping.mapping_determinant;
mfem::Vector C_phys(dim);
m_coeff.Eval(C_phys, T, ip);
@@ -157,7 +173,11 @@ namespace mean_field::mapping {
Func f // std::function<double(const mfem::Vector&)>
)
: m_f(std::move(f)),
m_mapping(mapper, displacement, compactification_coordinate) { };
m_mapping(
mapper,
displacement,
compactification_coordinate
) { };
double PhysicalPositionFunctionCoefficient::Eval(
mfem::ElementTransformation &T,
@@ -179,7 +199,11 @@ namespace mean_field::mapping {
const int dim
)
: MatrixCoefficient(dim),
m_mapping(mapper, displacement, compactification_coordinate) {
m_mapping(
mapper,
displacement,
compactification_coordinate
) {
}
void MappedHDivMassCoefficient::Eval(
@@ -195,7 +219,7 @@ namespace mean_field::mapping {
"Mapped H(div) coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &map_jacobian = context.mapping.mapping_jacobian;
const double map_determinant = context.mapping.mapping_determinant;
const double map_determinant = context.mapping.mapping_determinant;
MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant.");

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ namespace mean_field::models::structure {
validate();
}
const eos::EquationOfState &PolytropicStructure::equationOfState() const noexcept {
const eos::Polytrope &PolytropicStructure::equationOfState() const noexcept {
return m_equationOfState;
}
@@ -29,7 +29,9 @@ namespace mean_field::models::structure {
const double polytropicIndex = m_equationOfState.polytropic_index();
const std::vector<LaneEmdenPoint> laneEmdenSolution = solveLaneEmden(polytropicIndex);
const double surfaceCoordinate = laneEmdenSolution.back().coordinate;
const double centralEnthalpy = m_equationOfState.enthalpy_from_density(request.centralDensity);
const double centralEnthalpy =
eos::evaluate<eos::quantity::SpecificEnthalpy>(m_equationOfState, eos::DensityValue{request.centralDensity})
.value();
const double radialScaleSquared =
centralEnthalpy / (4.0 * std::numbers::pi_v<double> * mean_field::utils::G * request.centralDensity);
@@ -61,11 +63,12 @@ namespace mean_field::models::structure {
const double dimensionlessRadius = sampleFraction * surfaceCoordinate;
const double laneEmdenValue =
interpolateLaneEmdenValue(laneEmdenSolution, dimensionlessRadius, interpolationIndex);
const double density = request.centralDensity * std::pow(laneEmdenValue, polytropicIndex);
const double density = request.centralDensity * std::pow(laneEmdenValue, polytropicIndex);
seed.radius(sampleIndex) = radialScale * dimensionlessRadius;
seed.density(sampleIndex) = density;
seed.enthalpy(sampleIndex) = m_equationOfState.enthalpy_from_density(density);
seed.radius(sampleIndex) = radialScale * dimensionlessRadius;
seed.density(sampleIndex) = density;
seed.enthalpy(sampleIndex) =
eos::evaluate<eos::quantity::SpecificEnthalpy>(m_equationOfState, eos::DensityValue{density}).value();
}
seed.radius(0) = 0.0;

View File

@@ -80,13 +80,19 @@ namespace mean_field::operators::context::hydrostatic {
: m_f(f),
m_domainMapper(domainMapper),
m_enthalpyMap(
field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes)
field::make_field_dof_map<
field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
),
m_gravityPotentialMap(
field::make_field_dof_map<field::Gravity, DomainSchema>(*f.gravityPotentialFes)
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
),
m_displacementMap(
field::make_field_dof_map<field::Displacement, DomainSchema>(*f.displacementFes)
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
MFEM_VERIFY(m_f.mesh != nullptr, "HydrostaticEquilibriumContext requires a mesh.");

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,8 @@ import :field.registry;
import :utils.domain;
namespace {
namespace eos = mean_field::eos;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using ClosureDomain = mean_field::field::FieldDomainT<mean_field::field::Density>;
@@ -336,11 +338,21 @@ namespace {
if (closureAction == ClosureAction::residual) {
const double density = elementDensityInput * densityShape;
integrand = density - barotrope.density_from_enthalpy(baseEnthalpy);
const double equationOfStateDensity =
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{baseEnthalpy})
.value();
integrand = density - equationOfStateDensity;
} else {
const double enthalpyVariation = elementEnthalpyVariation * enthalpyShape;
integrand = -barotrope.density_derivative_from_enthalpy(baseEnthalpy) * enthalpyVariation;
const double densityDerivative =
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{baseEnthalpy}
)
.value();
integrand = -densityDerivative * enthalpyVariation;
}
}
@@ -628,11 +640,14 @@ namespace mean_field::operators::kernels {
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double densityValue = elementBaseDensity * densityShape;
const double densityValue = elementBaseDensity * densityShape;
const double enthalpyValue = elementBaseEnthalpy * enthalpyShape;
const double enthalpyValue = elementBaseEnthalpy * enthalpyShape;
const double closureValue = densityValue - barotrope.density_from_enthalpy(enthalpyValue);
const double equationOfStateDensity =
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{enthalpyValue}).value();
const double closureValue = densityValue - equationOfStateDensity;
const double geometryActionValue = closureValue * mappingVariation.weight_variation;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -398,11 +398,17 @@ namespace mean_field::operators {
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_equationOfState.density_from_enthalpy(enthalpy);
const double enthalpyDerivative = m_equationOfState.density_derivative_from_enthalpy(enthalpy);
const double density = elementBaseDensity * densityShape;
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
const double quadratureWeight = mappingContext.quadrature.weight;
const eos::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double eosDensity =
eos::evaluate<eos::quantity::Density>(m_equationOfState, specificEnthalpy).value();
const double enthalpyDerivative =
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
m_equationOfState, specificEnthalpy
)
.value();
MFEM_VERIFY(
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) &&

View File

@@ -9,531 +9,535 @@ module mean_field;
import :operators.prepared_gravity_source;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_height(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.gravityPotentialFes != nullptr,
"PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space.");
return mean_field::field::make_field_dof_map<mean_field::field::Gravity,
DomainSchema>(
*f.gravityPotentialFes)
.reduced_size();
}
int get_operator_height(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space."
);
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes)
.reduced_size();
}
int get_operator_width(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.densityFes != nullptr,
"PreparedMappedGravitySourceOperator requires the density "
"finite-element space.");
return mean_field::field::make_field_dof_map<mean_field::field::Density,
DomainSchema>(*f.densityFes)
.reduced_size();
}
int get_operator_width(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density "
"finite-element space."
);
return mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*f.densityFes)
.reduced_size();
}
void true_to_local(const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &true_vector,
mfem::Vector &local_vector) {
local_vector.SetSize(finite_element_space.GetVSize());
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();
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
} else {
local_vector = true_vector;
}
}
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.");
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;
true_vector.SetSize(finite_element_space.GetTrueVSize());
true_vector = 0.0;
const mfem::Operator *prolongation =
finite_element_space.GetProlongationMatrix();
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector);
} else {
true_vector = local_vector;
}
}
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);
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;
}
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::DomainMapper &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);
}
class FrozenMappedGravitySourceCoefficient final : public mfem::Coefficient {
public:
FrozenMappedGravitySourceCoefficient(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &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);
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(),
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 (DomainSchema::template attribute_belongs_to<
mean_field::utils::domain::Vacuum>(transformation.Attribute)) {
return 0.0;
}
"ID."
);
if (DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(
transformation.Attribute
)) {
return 0.0;
}
LoadElement(element_id);
const mean_field::mapping::ElementMappingData mapping_data{
.displacement = *m_displacement_data,
.compactification = *m_compactification_data};
LoadElement(element_id);
const mean_field::mapping::ElementMappingData mapping_data{
.displacement = *m_displacement_data, .compactification = *m_compactification_data
};
mean_field::mapping::VolumeMappingContext mapping_context;
mean_field::mapping::VolumeMappingContext mapping_context;
const mean_field::mapping::MappingStatus status =
m_domain_mapper.EvaluateVolume(mapping_data, transformation,
integration_point, m_workspace,
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);
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());
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);
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;
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,
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.");
"non-finite mapping determinant."
);
return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
}
return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
}
private:
void LoadElement(const int element_id) {
if (element_id == m_cached_element_id) {
return;
}
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);
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);
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);
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 (displacement_dof_transformation != nullptr) {
displacement_dof_transformation->InvTransformPrimal(m_element_displacement);
}
if (compactification_dof_transformation != nullptr) {
compactification_dof_transformation->InvTransformPrimal(
m_element_compactification);
}
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_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_compactification_data = std::make_unique<mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification
);
m_cached_element_id = element_id;
}
m_cached_element_id = element_id;
}
const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapper &m_domain_mapper;
const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapper &m_domain_mapper;
mfem::Vector m_displacement_local;
mfem::Vector m_displacement_local;
mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs;
mfem::Array<int> m_displacement_dofs;
mfem::Array<int> m_compactification_dofs;
mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification;
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;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data;
mean_field::mapping::DomainMapper::Workspace m_workspace;
int m_cached_element_id{-1};
};
mean_field::mapping::DomainMapper::Workspace m_workspace;
int m_cached_element_id{-1};
};
} // namespace
namespace mean_field::operators {
PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator(
const fem::FEM &f, const mapping::DomainMapper &domain_mapper)
: Operator(get_operator_height(f), get_operator_width(f)), m_fem(f),
m_domain_mapper(domain_mapper),
m_density_map(field::make_field_dof_map<field::Density, DomainSchema>(
*f.densityFes)),
m_potential_map(field::make_field_dof_map<field::Gravity, DomainSchema>(
*f.gravityPotentialFes)),
m_displacement_map(
field::make_field_dof_map<field::Displacement, DomainSchema>(
*f.displacementFes)) {
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.");
PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator(
const fem::FEM &f,
const mapping::DomainMapper &domain_mapper
)
: Operator(
get_operator_height(f),
get_operator_width(f)
),
m_fem(f),
m_domain_mapper(domain_mapper),
m_density_map(
field::make_field_dof_map<
field::Density,
DomainSchema>(*f.densityFes)
),
m_potential_map(
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
),
m_displacement_map(
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
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."
);
m_stellar_marker =
utils::domain::make_attribute_marker<utils::domain::Stellar,
DomainSchema>(*f.mesh);
}
void PreparedMappedGravitySourceOperator::Prepare(
const mfem::Vector &displacement) {
MFEM_VERIFY(displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedGravitySourceOperator received a displacement "
"vector "
"with the wrong size.");
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(std::isfinite(displacement(i)),
"PreparedMappedGravitySourceOperator received a non-finite "
"displacement value.");
}
m_is_prepared = false;
m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
FrozenMappedGravitySourceCoefficient source_coefficient(
m_fem, m_domain_mapper, m_displacement_true);
for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) {
const int attribute = m_fem.mesh->GetAttribute(element_id);
if (attribute <= 0 || attribute > m_stellar_marker.Size() ||
m_stellar_marker[attribute - 1] == 0) {
continue;
m_stellar_marker = utils::domain::make_attribute_marker<utils::domain::Stellar, DomainSchema>(*f.mesh);
}
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement) {
MFEM_VERIFY(
displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedGravitySourceOperator received a displacement "
"vector "
"with the wrong size."
);
data.element_id = element_id;
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement(i)), "PreparedMappedGravitySourceOperator received a non-finite "
"displacement value."
);
}
data.density_dof_transformation =
m_fem.densityFes->GetElementDofs(element_id, data.density_dofs);
m_is_prepared = false;
m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
data.potential_dof_transformation =
m_fem.gravityPotentialFes->GetElementDofs(element_id,
data.potential_dofs);
FrozenMappedGravitySourceCoefficient source_coefficient(m_fem, m_domain_mapper, m_displacement_true);
const mfem::FiniteElement &density_element =
*m_fem.densityFes->GetFE(element_id);
for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) {
const int attribute = m_fem.mesh->GetAttribute(element_id);
const mfem::FiniteElement &potential_element =
*m_fem.gravityPotentialFes->GetFE(element_id);
if (attribute <= 0 || attribute > m_stellar_marker.Size() || m_stellar_marker[attribute - 1] == 0) {
continue;
}
mfem::ElementTransformation &transformation =
*m_fem.mesh->GetElementTransformation(element_id);
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
const mfem::IntegrationRule &integration_rule = get_source_rule(
m_fem, density_element, potential_element, transformation);
data.element_id = element_id;
const int quadrature_point_count = integration_rule.GetNPoints();
data.density_dof_transformation = m_fem.densityFes->GetElementDofs(element_id, data.density_dofs);
const int density_dof_count = density_element.GetDof();
data.potential_dof_transformation =
m_fem.gravityPotentialFes->GetElementDofs(element_id, data.potential_dofs);
const int potential_dof_count = potential_element.GetDof();
const mfem::FiniteElement &density_element = *m_fem.densityFes->GetFE(element_id);
data.density_basis.SetSize(quadrature_point_count, density_dof_count);
const mfem::FiniteElement &potential_element = *m_fem.gravityPotentialFes->GetFE(element_id);
data.potential_basis.SetSize(quadrature_point_count, potential_dof_count);
mfem::ElementTransformation &transformation = *m_fem.mesh->GetElementTransformation(element_id);
data.quadrature_data.SetSize(quadrature_point_count);
const mfem::IntegrationRule &integration_rule =
get_source_rule(m_fem, density_element, potential_element, transformation);
mfem::Vector density_shape(density_dof_count);
mfem::Vector potential_shape(potential_dof_count);
const int quadrature_point_count = integration_rule.GetNPoints();
for (int quadrature_point = 0; quadrature_point < quadrature_point_count;
++quadrature_point) {
const mfem::IntegrationPoint &integration_point =
integration_rule.IntPoint(quadrature_point);
const int density_dof_count = density_element.GetDof();
transformation.SetIntPoint(&integration_point);
const int potential_dof_count = potential_element.GetDof();
// CalcPhysShape matches the scalar mixed-mass discretization,
// including the finite-element map type.
density_element.CalcPhysShape(transformation, density_shape);
data.density_basis.SetSize(quadrature_point_count, density_dof_count);
potential_element.CalcPhysShape(transformation, potential_shape);
data.potential_basis.SetSize(quadrature_point_count, potential_dof_count);
for (int i = 0; i < density_dof_count; ++i) {
data.density_basis(quadrature_point, i) = density_shape(i);
}
data.quadrature_data.SetSize(quadrature_point_count);
for (int i = 0; i < potential_dof_count; ++i) {
data.potential_basis(quadrature_point, i) = potential_shape(i);
}
mfem::Vector density_shape(density_dof_count);
mfem::Vector potential_shape(potential_dof_count);
const double coefficient_value =
source_coefficient.Eval(transformation, integration_point);
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);
transformation.SetIntPoint(&integration_point);
const double quadrature_value = integration_point.weight *
transformation.Weight() *
coefficient_value;
// CalcPhysShape matches the scalar mixed-mass discretization,
// including the finite-element map type.
density_element.CalcPhysShape(transformation, density_shape);
MFEM_VERIFY(std::isfinite(quadrature_value) && quadrature_value > 0.0,
"Prepared gravity source operator encountered invalid "
"quadrature data on element "
<< element_id << ", quadrature point " << quadrature_point
<< ".");
potential_element.CalcPhysShape(transformation, potential_shape);
data.quadrature_data(quadrature_point) = quadrature_value;
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,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
"Mult is called."
);
MFEM_VERIFY(!m_elements.empty(),
"PreparedMappedGravitySourceOperator found no stellar elements.");
MFEM_VERIFY(
density.Size() == Width(), "PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size."
);
m_is_prepared = true;
++m_preparation_count;
}
void PreparedMappedGravitySourceOperator::Mult(const mfem::Vector &density,
mfem::Vector &action) const {
MFEM_VERIFY(m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"Mult is called.");
m_density_true.SetSize(m_density_map.full_size());
m_density_map.scatter(density, m_density_true);
MFEM_VERIFY(density.Size() == Width(),
"PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size.");
mfem::Vector density_local;
m_density_true.SetSize(m_density_map.full_size());
m_density_map.scatter(density, m_density_true);
true_to_local(*m_fem.densityFes, m_density_true, density_local);
mfem::Vector density_local;
mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize());
local_action = 0.0;
true_to_local(*m_fem.densityFes, m_density_true, density_local);
mfem::Vector element_density;
mfem::Vector quadrature_density;
mfem::Vector element_action;
mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize());
local_action = 0.0;
for (const ElementPAData &data : m_elements) {
density_local.GetSubVector(data.density_dofs, element_density);
mfem::Vector element_density;
mfem::Vector quadrature_density;
mfem::Vector element_action;
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->InvTransformPrimal(element_density);
}
for (const ElementPAData &data : m_elements) {
density_local.GetSubVector(data.density_dofs, element_density);
quadrature_density.SetSize(data.quadrature_data.Size());
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->InvTransformPrimal(element_density);
// 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, m_action_true);
action.SetSize(Height());
m_potential_map.gather(m_action_true, action);
}
quadrature_density.SetSize(data.quadrature_data.Size());
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called."
);
// B_density * x_e
data.density_basis.Mult(element_density, quadrature_density);
MFEM_VERIFY(
potential.Size() == Height(), "PreparedMappedGravitySourceOperator received a potential vector "
"with the wrong size."
);
// D * B_density * x_e
for (int q = 0; q < quadrature_density.Size(); ++q) {
quadrature_density(q) *= data.quadrature_data(q);
m_potential_true.SetSize(m_potential_map.full_size());
m_potential_map.scatter(potential, m_potential_true);
mfem::Vector potential_local;
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, potential_local);
mfem::Vector local_action(m_fem.densityFes->GetVSize());
local_action = 0.0;
mfem::Vector element_potential;
mfem::Vector quadrature_potential;
mfem::Vector element_action;
for (const ElementPAData &data : m_elements) {
potential_local.GetSubVector(data.potential_dofs, element_potential);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->InvTransformPrimal(element_potential);
}
quadrature_potential.SetSize(data.quadrature_data.Size());
data.potential_basis.Mult(element_potential, quadrature_potential);
for (int q = 0; q < quadrature_potential.Size(); ++q) {
quadrature_potential(q) *= data.quadrature_data(q);
}
element_action.SetSize(data.density_dofs.Size());
data.density_basis.MultTranspose(quadrature_potential, element_action);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->TransformDual(element_action);
}
local_action.AddElementVector(data.density_dofs, element_action);
}
local_to_true(*m_fem.densityFes, local_action, m_action_true);
action.SetSize(Width());
m_density_map.gather(m_action_true, action);
}
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
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);
std::uint64_t PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
local_action.AddElementVector(data.potential_dofs, element_action);
}
local_to_true(*m_fem.gravityPotentialFes, local_action, m_action_true);
action.SetSize(Height());
m_potential_map.gather(m_action_true, action);
}
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential, mfem::Vector &action) const {
MFEM_VERIFY(m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called.");
MFEM_VERIFY(potential.Size() == Height(),
"PreparedMappedGravitySourceOperator received a potential vector "
"with the wrong size.");
m_potential_true.SetSize(m_potential_map.full_size());
m_potential_map.scatter(potential, m_potential_true);
mfem::Vector potential_local;
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, potential_local);
mfem::Vector local_action(m_fem.densityFes->GetVSize());
local_action = 0.0;
mfem::Vector element_potential;
mfem::Vector quadrature_potential;
mfem::Vector element_action;
for (const ElementPAData &data : m_elements) {
potential_local.GetSubVector(data.potential_dofs, element_potential);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->InvTransformPrimal(element_potential);
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept {
return m_density_map;
}
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);
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept {
return m_potential_map;
}
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);
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
local_action.AddElementVector(data.density_dofs, element_action);
}
local_to_true(*m_fem.densityFes, local_action, m_action_true);
action.SetSize(Width());
m_density_map.gather(m_action_true, action);
}
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept {
return m_density_map;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept {
return m_potential_map;
}
const field::FieldDofMap &
PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
} // namespace mean_field::operators

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -617,13 +617,19 @@ namespace mean_field::operators {
data.enthalpyJacobian = 0.0;
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const double enthalpy = quadratureEnthalpy(quadraturePoint);
const double enthalpy = quadratureEnthalpy(quadraturePoint);
const double pressure = m_equationOfState.pressure_from_enthalpy(enthalpy);
const eos::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double pressure =
eos::evaluate<eos::quantity::Pressure>(m_equationOfState, specificEnthalpy).value();
const double pressureDerivative = m_equationOfState.pressure_derivative_from_enthalpy(enthalpy);
const double pressureDerivative =
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
m_equationOfState, specificEnthalpy
)
.value();
const double quadratureWeight = data.quadratureWeights(quadraturePoint);
const double quadratureWeight = data.quadratureWeights(quadraturePoint);
MFEM_VERIFY(
std::isfinite(pressure) && std::isfinite(pressureDerivative),
@@ -1134,4 +1140,4 @@ namespace mean_field::operators {
const BarotropicEquilibriumLayout &PreparedPressureForceJacobianOperator::GetLayout() const noexcept {
return m_layout;
}
} // namespace mean_field::operators
} // namespace mean_field::operators

View File

@@ -21,6 +21,12 @@ namespace {
);
}
[[nodiscard]] mfem::Vector make_computational_origin(const mfem::ParMesh &mesh) {
mfem::Vector origin(mesh.SpaceDimension());
origin = 0.0;
return origin;
}
[[nodiscard]] mean_field::operators::StellarEquilibriumLayout make_layout(
const mean_field::field::FieldDofMap &densityMap,
const mean_field::field::FieldDofMap &displacementMap,
@@ -253,6 +259,8 @@ namespace mean_field::operators {
field::FieldDofMap gravityFluxMap;
field::FieldDofMap gravityPotentialMap;
field::FieldDofMap enthalpyMap;
field::FieldBoundaryDofMap pressureSurfaceRows;
field::FieldPointDofMap centerDisplacementRows;
StellarEquilibriumLayout layout;
mfem::Array<int> gravityStateOffsets;
@@ -284,6 +292,23 @@ namespace mean_field::operators {
field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
),
pressureSurfaceRows(
field::make_field_boundary_dof_map<
field::Enthalpy,
utils::domain::StellarSurface,
DomainSchema>(
*f.enthalpyFes,
enthalpyMap
)
),
centerDisplacementRows(
field::make_field_point_dof_map<field::Displacement>(
*f.displacementFes,
displacementMap,
make_computational_origin(*f.mesh),
1.0e-12
)
),
layout(make_layout(
densityMap,
displacementMap,
@@ -314,27 +339,15 @@ namespace mean_field::operators {
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const models::StellarModel &stellarModel
)
: PreparedStellarEquilibriumOperator(
f,
domainMapper,
equationOfState,
stellarModel.targetMass()
) {
}
PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const double targetMass
const double targetMass,
const PressureSurfaceConstraintView surfaceConstraint
)
: PreparedStellarEquilibriumOperator(
f,
domainMapper,
equationOfState,
targetMass,
surfaceConstraint,
MakeConstructionData(f)
) {
}
@@ -344,6 +357,7 @@ namespace mean_field::operators {
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const double targetMass,
const PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData
)
: mfem::Operator(
@@ -390,6 +404,11 @@ namespace mean_field::operators {
domainMapper,
m_gravityContext
),
m_surfaceConstraintOperator(
constructionData.pressureSurfaceRows,
surfaceConstraint
),
m_centeringConstraintOperator(constructionData.centerDisplacementRows),
m_targetMass(targetMass) {
MFEM_VERIFY(
std::isfinite(m_targetMass) && m_targetMass > 0.0,
@@ -506,6 +525,14 @@ namespace mean_field::operators {
report.massNormalization =
m_massNormalizationOperator.Prepare({.targetMass = m_targetMass}, make_mass_dependencies(dependencies));
report.surfaceConstraint = m_surfaceConstraintOperator.Prepare(
reducedEnthalpy, !wasPrepared || dependencies.enthalpy != m_preparedDependencies.enthalpy
);
report.centeringConstraint = m_centeringConstraintOperator.Prepare(
displacement, !wasPrepared || dependencies.displacement != m_preparedDependencies.displacement
);
const bool dependenciesChanged = !wasPrepared || dependencies != m_preparedDependencies;
if (dependenciesChanged || report.DidAnyChildWork()) {
AssembleResidual();
@@ -542,7 +569,9 @@ namespace mean_field::operators {
m_gravityOperator.Mult(m_gravityState, gravity);
m_barotropicClosureOperator.BuildResidual(closure);
m_displacementOperator.BuildResidual(displacement);
m_centeringConstraintOperator.ApplyResidualRows(displacement);
m_hydrostaticOperator.BuildResidual(hydrostatic);
m_surfaceConstraintOperator.ApplyResidualRows(hydrostatic);
m_massNormalizationOperator.BuildResidual(mass);
m_cachedResidual.SetSize(Height());
@@ -659,11 +688,13 @@ namespace mean_field::operators {
reducedDensityDirection, displacementDirection, gravityGradientDirection, reducedEnthalpyDirection,
displacementAction
);
m_centeringConstraintOperator.ApplyJacobianRows(displacementDirection, displacementAction);
m_hydrostaticOperator.ApplyCompleteJacobianAction(
reducedEnthalpyDirection, gravityPotentialDirection, bernoulliDirection(0), displacementDirection,
hydrostaticAction
);
m_surfaceConstraintOperator.ApplyJacobianRows(reducedEnthalpyDirection, hydrostaticAction);
m_massNormalizationOperator.ApplyCompleteJacobianAction(
reducedDensityDirection, displacementDirection, massAction
@@ -712,7 +743,8 @@ namespace mean_field::operators {
bool PreparedStellarEquilibriumOperator::IsPrepared() const noexcept {
return m_isPrepared && m_gravityContext.IsPrepared() && m_barotropicClosureOperator.IsPrepared() &&
m_hydrostaticOperator.IsPrepared() && m_displacementOperator.IsPrepared() &&
m_massNormalizationOperator.IsPrepared();
m_massNormalizationOperator.IsPrepared() && m_surfaceConstraintOperator.IsPrepared() &&
m_centeringConstraintOperator.IsPrepared();
}
double PreparedStellarEquilibriumOperator::GetTargetMass() const noexcept {
@@ -771,6 +803,16 @@ namespace mean_field::operators {
return m_massNormalizationOperator;
}
const PreparedPressureSurfaceConstraint &
PreparedStellarEquilibriumOperator::GetSurfaceConstraintOperator() const noexcept {
return m_surfaceConstraintOperator;
}
const PreparedCenteringConstraint &
PreparedStellarEquilibriumOperator::GetCenteringConstraintOperator() const noexcept {
return m_centeringConstraintOperator;
}
void PreparedStellarEquilibriumOperator::VerifyPrepared() const {
MFEM_VERIFY(
IsPrepared(), "PreparedStellarEquilibriumOperator must be prepared before residual or Jacobian application."

View File

@@ -13,16 +13,14 @@ namespace mean_field::physics {
) {
const int dim = fem.mesh->Dimension();
mfem::DenseMatrix local_Q(dim, dim);
local_Q = 0.0;
local_Q = 0.0;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(
fem.mesh->GetAttribute(i)))
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
@@ -40,13 +38,12 @@ namespace mean_field::physics {
mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) ==
mapping::MappingStatus::valid,
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
"Quadrupole integration encountered an invalid mapping."
);
const double weight = mapping_context.quadrature.weight;
const double weight = mapping_context.quadrature.weight;
const double rho_val = rho.GetValue(i, ip);
const double rho_val = rho.GetValue(i, ip);
const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
@@ -145,7 +142,7 @@ namespace mean_field::physics {
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.poisson_term);
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const field::FieldDofGridFunctionAdapter density_adapter =
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*f.densityFes);
const field::FieldDofGridFunctionAdapter displacement_adapter =
@@ -220,12 +217,8 @@ namespace mean_field::physics {
GravitySolution solution(f);
gravity_flux_adapter.scatter(
gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi
);
gravity_potential_adapter.scatter(
gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi
);
gravity_flux_adapter.scatter(gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi);
gravity_potential_adapter.scatter(gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi);
return solution;
}

View File

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

View File

@@ -13,8 +13,7 @@ namespace mean_field::utils {
const int dim = fem.mesh->Dimension();
x_ref = x_phys_target;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement,
*fem.compactificationCoordinate
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
mfem::Array<int> init_elem;
@@ -39,8 +38,7 @@ namespace mean_field::utils {
mapping::MappingPointContext context;
MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*T0, origin_ip[0], context) ==
mapping::MappingStatus::valid,
mapping_evaluator.EvaluatePoint(*T0, origin_ip[0], context) == mapping::MappingStatus::valid,
"Reference-point initialization encountered an invalid mapping."
);
@@ -104,8 +102,7 @@ namespace mean_field::utils {
T->SetIntPoint(&ip);
mapping::MappingPointContext context;
if (mapping_evaluator.EvaluatePoint(*T, ip, context) !=
mapping::MappingStatus::valid) {
if (mapping_evaluator.EvaluatePoint(*T, ip, context) != mapping::MappingStatus::valid) {
return false;
}
const mfem::Vector &current_x_phys = context.physical_position;

View File

@@ -4,21 +4,25 @@ module;
module mean_field;
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));
}
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));
}
int get_mesh_order(const mfem::Mesh &mesh) {
if (mesh.GetNodes() != nullptr) {
return mesh.GetNodes()->FESpace()->GetMaxElementOrder();
}
return 1;
}
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,100 @@
module;
#include <concepts>
#include <type_traits>
export module mean_field:eos.concepts;
export import :eos.relations;
export namespace mean_field::eos {
namespace detail {
template <typename EquationOfState, typename RelationType> struct ImplementsRelation : std::false_type { };
template <
typename EquationOfState,
typename Output,
typename... Inputs>
struct ImplementsRelation<
EquationOfState,
Relation<
Output,
Inputs...>> : std::bool_constant <
requires(
const std::remove_cvref_t<EquationOfState> &equationOfState,
QuantityValue<Inputs>... inputValues
) {
{equationOfState.evaluate(Relation<Output, Inputs...>{}, inputValues...)}
->std::same_as<QuantityValue<Output>>;
}>{};
template <typename EquationOfState, typename Catalog> struct ImplementsRelationCatalog : std::false_type { };
template <typename EquationOfState, typename... Relations>
struct ImplementsRelationCatalog<EquationOfState, RelationCatalog<Relations...>>
: std::bool_constant<(ImplementsRelation<EquationOfState, Relations>::value && ...)> { };
template <typename Candidate, typename = void> struct IsEquationOfStateModel : std::false_type { };
template <typename Candidate>
struct IsEquationOfStateModel<Candidate, std::void_t<typename std::remove_cvref_t<Candidate>::Relations>>
: std::bool_constant<
ValidRelationCatalog<typename std::remove_cvref_t<Candidate>::Relations> &&
ImplementsRelationCatalog<
std::remove_cvref_t<Candidate>,
typename std::remove_cvref_t<Candidate>::Relations>::value> { };
template <typename EquationOfState, typename RelationType, typename InputQuantity>
struct ImplementsPartialDerivative : std::false_type { };
template <
typename EquationOfState,
typename Output,
typename... Inputs,
typename InputQuantity>
struct ImplementsPartialDerivative<
EquationOfState,
Relation<
Output,
Inputs...>,
InputQuantity> : std::bool_constant <
(std::same_as<
InputQuantity,
Inputs> ||
...) &&
requires(
const std::remove_cvref_t<EquationOfState> &equationOfState,
QuantityValue<Inputs>... inputValues
) {
{equationOfState
.partialDerivative(Relation<Output, Inputs...>{}, WithRespectTo<InputQuantity>{}, inputValues...)}
->std::same_as<PartialDerivative<Output, InputQuantity>>;
}>{};
} // namespace detail
template <typename Candidate>
concept EquationOfStateModel = detail::IsEquationOfStateModel<Candidate>::value;
template <typename EquationOfState, typename RelationType>
concept SupportsRelation =
EquationOfStateModel<EquationOfState> && ThermodynamicRelationType<RelationType> &&
relationCatalogContains<typename std::remove_cvref_t<EquationOfState>::Relations, RelationType>;
template <typename EquationOfState, typename RelationType, typename InputQuantity>
concept SupportsPartialDerivative =
SupportsRelation<EquationOfState, RelationType> && ThermodynamicQuantityType<InputQuantity> &&
detail::ImplementsPartialDerivative<EquationOfState, RelationType, InputQuantity>::value;
template <typename Candidate>
concept StructureSeedEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, SpecificEnthalpyFromDensity>;
template <typename Candidate>
concept BarotropicClosureEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, DensityFromSpecificEnthalpy> &&
SupportsPartialDerivative<Candidate, DensityFromSpecificEnthalpy, quantity::SpecificEnthalpy>;
template <typename Candidate>
concept PressureForceEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, PressureFromSpecificEnthalpy> &&
SupportsPartialDerivative<Candidate, PressureFromSpecificEnthalpy, quantity::SpecificEnthalpy>;
} // namespace mean_field::eos

View File

@@ -1,16 +0,0 @@
export module mean_field:eos.base;
export namespace mean_field::eos {
class EquationOfState {
public:
virtual ~EquationOfState() = default;
[[nodiscard]] virtual double pressure_from_density(double density) const = 0;
[[nodiscard]] virtual double pressure_from_enthalpy(double enthalpy) const = 0;
[[nodiscard]] virtual double enthalpy_from_density(double density) const = 0;
[[nodiscard]] virtual double enthalpy_from_pressure(double pressure) const = 0;
[[nodiscard]] virtual double density_from_enthalpy(double enthalpy) const = 0;
[[nodiscard]] virtual double density_derivative_from_enthalpy(double enthalpy) const = 0;
[[nodiscard]] virtual double pressure_derivative_from_enthalpy(double enthalpy) const = 0;
[[nodiscard]] virtual double pressure_derivative_from_density(double density) const = 0;
};
} // namespace mean_field::eos

View File

@@ -0,0 +1,90 @@
module;
#include <stdexcept>
#include <string>
#include <utility>
export module mean_field:eos.evaluation;
export import :eos.concepts;
export namespace mean_field::eos {
enum class EvaluationErrorCode {
unsupported_relation,
unsupported_derivative,
wrong_input_count,
wrong_input_quantity,
nonfinite_input,
outside_domain,
nonfinite_result
};
class EvaluationError final : public std::domain_error {
public:
explicit EvaluationError(
const EvaluationErrorCode code,
std::string message
)
: std::domain_error(std::move(message)),
m_code(code) {
}
[[nodiscard]] EvaluationErrorCode code() const noexcept {
return m_code;
}
private:
EvaluationErrorCode m_code;
};
template <
ThermodynamicQuantityType OutputQuantity,
EquationOfStateModel EquationOfState,
QuantityValueType... InputValues>
requires SupportsRelation<
EquationOfState,
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>>
[[nodiscard]] constexpr QuantityValue<OutputQuantity> evaluate(
const EquationOfState &equationOfState,
const InputValues... inputValues
) noexcept(noexcept(equationOfState
.evaluate(
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>{},
inputValues...
))) {
return equationOfState.evaluate(Relation<OutputQuantity, QuantityOfT<InputValues>...>{}, inputValues...);
}
template <
ThermodynamicQuantityType OutputQuantity,
ThermodynamicQuantityType InputQuantity,
EquationOfStateModel EquationOfState,
QuantityValueType... InputValues>
requires SupportsPartialDerivative<
EquationOfState,
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>,
InputQuantity>
[[nodiscard]] constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
partialDerivative(
const EquationOfState &equationOfState,
const InputValues... inputValues
) noexcept(noexcept(equationOfState
.partialDerivative(
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>{},
WithRespectTo<InputQuantity>{},
inputValues...
))) {
return equationOfState.partialDerivative(
Relation<OutputQuantity, QuantityOfT<InputValues>...>{}, WithRespectTo<InputQuantity>{}, inputValues...
);
}
} // namespace mean_field::eos

View File

@@ -3,11 +3,18 @@ module;
#include <format>
#include <stdexcept>
export module mean_field:eos.polytrope;
export import :eos.base;
export import :eos.evaluation;
export namespace mean_field::eos {
class Polytrope final : public EquationOfState {
class Polytrope final {
public:
using Relations = RelationCatalog<
PressureFromDensity,
PressureFromSpecificEnthalpy,
SpecificEnthalpyFromDensity,
SpecificEnthalpyFromPressure,
DensityFromSpecificEnthalpy>;
Polytrope(
const double polytropic_index,
const double polytropic_constant
@@ -49,82 +56,128 @@ export namespace mean_field::eos {
return m_enthalpy_scale;
}
[[nodiscard]] double pressure_from_density(const double density) const override {
validate_nonnegativity(density, "density");
if (density == 0.0) {
return 0.0;
[[nodiscard]] PressureValue evaluate(
PressureFromDensity,
const DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return PressureValue{0.0};
}
return m_polytropic_constant * std::pow(density, 1.0 + 1.0 / m_polytropic_index);
return PressureValue{m_polytropic_constant * std::pow(density.value(), 1.0 + 1.0 / m_polytropic_index)};
}
[[nodiscard]] double enthalpy_from_density(const double density) const override {
validate_nonnegativity(density, "density");
if (density == 0.0) {
return 0.0;
[[nodiscard]] SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromDensity,
const DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return SpecificEnthalpyValue{0.0};
}
return m_enthalpy_scale * std::pow(density, 1.0 / m_polytropic_index);
return SpecificEnthalpyValue{m_enthalpy_scale * std::pow(density.value(), 1.0 / m_polytropic_index)};
}
[[nodiscard]] double density_from_enthalpy(const double enthalpy) const override {
validate_finite(enthalpy, "enthalpy");
[[nodiscard]] DensityValue evaluate(
DensityFromSpecificEnthalpy,
const SpecificEnthalpyValue specificEnthalpy
) const {
validate_finite(specificEnthalpy.value(), "specific enthalpy");
if (enthalpy <= 0.0) {
return 0.0;
if (specificEnthalpy.value() <= 0.0) {
return DensityValue{0.0};
}
return std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index);
return DensityValue{std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index)};
}
[[nodiscard]] double pressure_from_enthalpy(const double enthalpy) const override {
validate_finite(enthalpy, "enthalpy");
[[nodiscard]] PressureValue evaluate(
PressureFromSpecificEnthalpy,
const SpecificEnthalpyValue specificEnthalpy
) const {
const DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
if (enthalpy <= 0.0) {
return 0.0;
if (specificEnthalpy.value() <= 0.0) {
return PressureValue{0.0};
}
return density_from_enthalpy(enthalpy) * enthalpy / (m_polytropic_index + 1.0);
return PressureValue{density.value() * specificEnthalpy.value() / (m_polytropic_index + 1.0)};
}
[[nodiscard]] double density_derivative_from_enthalpy(const double enthalpy) const override {
validate_finite(enthalpy, "enthalpy");
if (enthalpy < 0.0) {
return 0.0;
[[nodiscard]] SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromPressure,
const PressureValue pressure
) const {
validate_nonnegativity(pressure.value(), "pressure");
if (pressure.value() == 0.0) {
return SpecificEnthalpyValue{0.0};
}
if (enthalpy == 0.0) {
return m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0;
}
const double indexPlusOne = m_polytropic_index + 1.0;
return m_polytropic_index / m_enthalpy_scale *
std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index - 1.0);
return SpecificEnthalpyValue{
indexPlusOne * std::pow(m_polytropic_constant, m_polytropic_index / indexPlusOne) *
std::pow(pressure.value(), 1.0 / indexPlusOne)
};
}
[[nodiscard]] double pressure_derivative_from_enthalpy(const double enthalpy) const override {
validate_finite(enthalpy, "enthalpy");
if (enthalpy <= 0.0) {
return 0.0;
[[nodiscard]] PartialDerivative<
quantity::Density,
quantity::SpecificEnthalpy>
partialDerivative(
DensityFromSpecificEnthalpy,
WithRespectTo<quantity::SpecificEnthalpy>,
const SpecificEnthalpyValue specificEnthalpy
) const {
validate_finite(specificEnthalpy.value(), "specific enthalpy");
if (specificEnthalpy.value() < 0.0) {
return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{0.0};
}
return density_from_enthalpy(enthalpy);
}
[[nodiscard]] double pressure_derivative_from_density(const double density) const override {
validate_nonnegativity(density, "density");
if (density == 0.0) {
return 0.0;
if (specificEnthalpy.value() == 0.0) {
return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{
m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0
};
}
return m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) *
std::pow(density, 1.0 / m_polytropic_index);
return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{
m_polytropic_index / m_enthalpy_scale *
std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index - 1.0)
};
}
[[nodiscard]] double enthalpy_from_pressure(const double pressure) const override {
validate_nonnegativity(pressure, "pressure");
const double np1 = m_polytropic_index + 1;
return np1 * std::pow(m_polytropic_constant, m_polytropic_index / np1) * std::pow(pressure, 1.0 / np1);
[[nodiscard]] PartialDerivative<
quantity::Pressure,
quantity::SpecificEnthalpy>
partialDerivative(
PressureFromSpecificEnthalpy,
WithRespectTo<quantity::SpecificEnthalpy>,
const SpecificEnthalpyValue specificEnthalpy
) const {
const DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
return PartialDerivative<quantity::Pressure, quantity::SpecificEnthalpy>{density.value()};
}
[[nodiscard]] PartialDerivative<
quantity::Pressure,
quantity::Density>
partialDerivative(
PressureFromDensity,
WithRespectTo<quantity::Density>,
const DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return PartialDerivative<quantity::Pressure, quantity::Density>{0.0};
}
return PartialDerivative<quantity::Pressure, quantity::Density>{
m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) *
std::pow(density.value(), 1.0 / m_polytropic_index)
};
}
private:
@@ -133,12 +186,12 @@ export namespace mean_field::eos {
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
)
throw EvaluationError(
EvaluationErrorCode::nonfinite_input, std::format(
"The {} must be finite. Instead a value of {} has been "
"provided",
quantity, value
)
);
}
}
@@ -149,13 +202,13 @@ export namespace mean_field::eos {
) {
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
)
throw EvaluationError(
EvaluationErrorCode::outside_domain, std::format(
"The {} must be non-negative. Instead a value of {} "
"has been "
"provided",
quantity, value
)
);
}
}

View File

@@ -0,0 +1,128 @@
module;
#include <memory>
#include <type_traits>
export module mean_field:eos.pressure_surface;
export import :eos.evaluation;
export namespace mean_field::eos {
namespace detail {
template <
ThermodynamicQuantityType InputQuantity,
typename SurfaceState>
[[nodiscard]] constexpr auto pressureSurfaceRelationInput(
const PressureValue targetPressure,
const SurfaceState &state
) {
if constexpr (std::same_as<InputQuantity, quantity::Pressure>) {
return targetPressure;
} else {
return state.value(InputQuantity{});
}
}
template <typename RelationType> struct PressureSurfaceRelationOperations;
template <typename CarrierQuantity, typename... InputQuantities>
struct PressureSurfaceRelationOperations<Relation<CarrierQuantity, InputQuantities...>> {
template <
typename EquationOfState,
typename SurfaceState>
[[nodiscard]] static QuantityValue<CarrierQuantity> requiredCarrierValue(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const SurfaceState &state
) {
return evaluate<CarrierQuantity>(
equationOfState, pressureSurfaceRelationInput<InputQuantities>(targetPressure, state)...
);
}
template <
typename InputQuantity,
typename EquationOfState,
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] static double inputJacobianContribution(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const SurfaceState &state,
const SurfaceVariation &variation
) {
if constexpr (std::same_as<InputQuantity, quantity::Pressure>) {
return 0.0;
} else {
const auto derivative = partialDerivative<CarrierQuantity, InputQuantity>(
equationOfState, pressureSurfaceRelationInput<InputQuantities>(targetPressure, state)...
);
return derivative.value() * variation.value(InputQuantity{}).value();
}
}
template <
typename EquationOfState,
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] static double carrierCorrectionJacobianAction(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const SurfaceState &state,
const SurfaceVariation &variation
) {
return (
0.0 + ... +
inputJacobianContribution<InputQuantities>(equationOfState, targetPressure, state, variation)
);
}
};
} // namespace detail
/*
* EOS-owned resolution of a constant-pressure condition into the carrier
* quantity used by an equation formulation. No field or solver concepts
* enter this type.
*/
template <EquationOfStateModel EquationOfState, ThermodynamicRelationType SelectedRelation>
class ResolvedPressureSurfaceRelation final {
public:
using RelationType = SelectedRelation;
using CarrierQuantity = RelationOutputT<RelationType>;
ResolvedPressureSurfaceRelation(
const EquationOfState &equationOfState,
const PressureValue targetPressure
) noexcept
: m_equationOfState(std::addressof(equationOfState)),
m_targetPressure(targetPressure) {
}
[[nodiscard]] PressureValue targetPressure() const noexcept {
return m_targetPressure;
}
template <typename SurfaceState>
[[nodiscard]] QuantityValue<CarrierQuantity> requiredCarrierValue(const SurfaceState &state) const {
return detail::PressureSurfaceRelationOperations<RelationType>::requiredCarrierValue(
*m_equationOfState, m_targetPressure, state
);
}
template <
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] double carrierCorrectionJacobianAction(
const SurfaceState &state,
const SurfaceVariation &variation
) const {
return detail::PressureSurfaceRelationOperations<RelationType>::carrierCorrectionJacobianAction(
*m_equationOfState, m_targetPressure, state, variation
);
}
private:
const EquationOfState *m_equationOfState;
PressureValue m_targetPressure;
};
} // namespace mean_field::eos

View File

@@ -0,0 +1,235 @@
module;
#include <compare>
#include <concepts>
#include <string_view>
#include <type_traits>
export module mean_field:eos.quantities;
export namespace mean_field::eos {
struct ThermodynamicQuantity { };
template <typename Candidate>
concept ThermodynamicQuantityType =
std::same_as<Candidate, std::remove_cv_t<Candidate>> && std::derived_from<Candidate, ThermodynamicQuantity>;
namespace quantity {
struct Density final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "density";
};
struct Pressure final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "pressure";
};
struct SpecificEnthalpy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "specific_enthalpy";
};
} // namespace quantity
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template <ThermodynamicQuantityType Quantity> class QuantityValue final {
public:
explicit constexpr QuantityValue(const double value) noexcept : m_value(value) {
}
[[nodiscard]] constexpr double value() const noexcept {
return m_value;
}
[[nodiscard]] friend constexpr bool operator==(
const QuantityValue &,
const QuantityValue &
) noexcept = default;
friend constexpr QuantityValue<Quantity> operator+(
const QuantityValue<Quantity> &lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return QuantityValue<Quantity>{lhs.m_value + rhs.m_value};
}
friend constexpr QuantityValue<Quantity> operator-(
const QuantityValue<Quantity> &lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return QuantityValue<Quantity>{lhs.m_value - rhs.m_value};
}
template <Numeric rhsT>
friend constexpr QuantityValue<Quantity> operator*(
const QuantityValue<Quantity> &lhs,
rhsT rhs
) noexcept {
return QuantityValue<Quantity>{lhs.m_value * static_cast<double>(rhs)};
}
template <Numeric lhsT>
friend constexpr QuantityValue<Quantity> operator*(
lhsT lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return QuantityValue<Quantity>{static_cast<double>(lhs) * rhs.m_value};
}
template <Numeric rhsT>
friend constexpr QuantityValue<Quantity> operator/(
const QuantityValue<Quantity> &lhs,
rhsT rhs
) noexcept {
return QuantityValue<Quantity>{lhs.m_value / static_cast<double>(rhs)};
}
template <Numeric compT>
friend constexpr std::partial_ordering operator<=>(
const QuantityValue<Quantity> &lhs,
compT rhs
) noexcept {
return lhs.m_value <=> static_cast<double>(rhs);
}
template <Numeric compT>
friend constexpr std::partial_ordering operator<=>(
compT lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return static_cast<double>(lhs) <=> rhs.m_value;
}
friend constexpr std::partial_ordering operator<=>(
const QuantityValue<Quantity> &lhs,
const QuantityValue<Quantity> &rhs
) noexcept {
return lhs.m_value <=> rhs.m_value;
}
private:
double m_value;
};
using DensityValue = QuantityValue<quantity::Density>;
using PressureValue = QuantityValue<quantity::Pressure>;
using SpecificEnthalpyValue = QuantityValue<quantity::SpecificEnthalpy>;
template <typename Candidate> struct IsQuantityValue : std::false_type { };
template <ThermodynamicQuantityType Quantity> struct IsQuantityValue<QuantityValue<Quantity>> : std::true_type { };
template <typename Candidate>
concept QuantityValueType = IsQuantityValue<std::remove_cvref_t<Candidate>>::value;
template <typename Candidate> struct QuantityOf;
template <ThermodynamicQuantityType Quantity> struct QuantityOf<QuantityValue<Quantity>> {
using Type = Quantity;
};
template <QuantityValueType Value> using QuantityOfT = typename QuantityOf<std::remove_cvref_t<Value>>::Type;
template <ThermodynamicQuantityType OutputQuantity, ThermodynamicQuantityType InputQuantity>
class PartialDerivative final {
public:
explicit constexpr PartialDerivative(const double value) noexcept : m_value(value) {
}
[[nodiscard]] constexpr double value() const noexcept {
return m_value;
}
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator+(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept;
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator-(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept;
template <Numeric rhsT>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator*(
const PartialDerivative<
OutputQuantity,
InputQuantity> &,
rhsT
) noexcept;
template <Numeric lhsT>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator*(
lhsT,
const PartialDerivative<
OutputQuantity,
InputQuantity> &
) noexcept;
template <Numeric rhsT>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator/(
const PartialDerivative<
OutputQuantity,
InputQuantity> &,
rhsT
) noexcept;
template <Numeric cmpT>
friend constexpr std::partial_ordering operator<=>(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
cmpT rhs
) noexcept {
return lhs.m_value <=> static_cast<double>(rhs);
}
template <Numeric cmpT>
friend constexpr std::partial_ordering operator<=>(
cmpT lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept {
return static_cast<double>(lhs) <=> rhs.m_value;
}
friend constexpr std::partial_ordering operator<=>(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept {
return lhs.m_value <=> rhs.m_value;
}
private:
double m_value;
};
template <ThermodynamicQuantityType Quantity> struct WithRespectTo final { };
} // namespace mean_field::eos

View File

@@ -0,0 +1,93 @@
module;
#include <concepts>
#include <cstddef>
#include <tuple>
#include <type_traits>
export module mean_field:eos.relations;
export import :eos.quantities;
export namespace mean_field::eos {
template <typename... Quantities> struct QuantityList final { };
template <typename Output, typename... Inputs> struct Relation final {
using OutputQuantity = Output;
using InputQuantities = QuantityList<Inputs...>;
static constexpr std::size_t inputCount = sizeof...(Inputs);
};
template <typename... Relations> struct RelationCatalog final {
static constexpr std::size_t size = sizeof...(Relations);
};
namespace detail {
template <typename... Types> struct TypesAreUnique;
template <typename Candidate> struct IsThermodynamicRelation : std::false_type { };
template <typename Output, typename... Inputs>
struct IsThermodynamicRelation<Relation<Output, Inputs...>>
: std::bool_constant<
ThermodynamicQuantityType<Output> && (ThermodynamicQuantityType<Inputs> && ...) &&
TypesAreUnique<Inputs...>::value> { };
template <typename... Types> struct TypesAreUnique : std::true_type { };
template <typename First, typename... Remaining>
struct TypesAreUnique<First, Remaining...>
: std::bool_constant<(!std::same_as<First, Remaining> && ...) && TypesAreUnique<Remaining...>::value> { };
template <typename Candidate> struct IsValidRelationCatalog : std::false_type { };
template <typename... Relations>
struct IsValidRelationCatalog<RelationCatalog<Relations...>>
: std::bool_constant<
(sizeof...(Relations) > 0) && (IsThermodynamicRelation<Relations>::value && ...) &&
TypesAreUnique<Relations...>::value> { };
template <typename Catalog, typename RelationType> struct CatalogContainsRelation : std::false_type { };
template <typename... Relations, typename RelationType>
struct CatalogContainsRelation<RelationCatalog<Relations...>, RelationType>
: std::bool_constant<(std::same_as<RelationType, Relations> || ...)> { };
template <typename RelationType, typename Quantity> struct RelationContainsInput : std::false_type { };
template <typename Output, typename... Inputs, typename Quantity>
struct RelationContainsInput<Relation<Output, Inputs...>, Quantity>
: std::bool_constant<(std::same_as<Quantity, Inputs> || ...)> { };
template <std::size_t Index, typename Quantities> struct QuantityAt;
template <std::size_t Index, typename... Quantities> struct QuantityAt<Index, QuantityList<Quantities...>> {
using Type = std::tuple_element_t<Index, std::tuple<Quantities...>>;
};
} // namespace detail
template <typename Candidate>
concept ThermodynamicRelationType = detail::IsThermodynamicRelation<std::remove_cv_t<Candidate>>::value;
template <typename Candidate>
concept ValidRelationCatalog = detail::IsValidRelationCatalog<std::remove_cv_t<Candidate>>::value;
template <typename Catalog, typename RelationType>
inline constexpr bool relationCatalogContains =
detail::CatalogContainsRelation<std::remove_cv_t<Catalog>, std::remove_cv_t<RelationType>>::value;
template <typename RelationType, typename Quantity>
inline constexpr bool relationContainsInput =
detail::RelationContainsInput<std::remove_cv_t<RelationType>, std::remove_cv_t<Quantity>>::value;
template <ThermodynamicRelationType RelationType> using RelationOutputT = typename RelationType::OutputQuantity;
template <std::size_t Index, ThermodynamicRelationType RelationType>
using RelationInputT = typename detail::QuantityAt<Index, typename RelationType::InputQuantities>::Type;
using PressureFromDensity = Relation<quantity::Pressure, quantity::Density>;
using PressureFromSpecificEnthalpy = Relation<quantity::Pressure, quantity::SpecificEnthalpy>;
using SpecificEnthalpyFromDensity = Relation<quantity::SpecificEnthalpy, quantity::Density>;
using SpecificEnthalpyFromPressure = Relation<quantity::SpecificEnthalpy, quantity::Pressure>;
using DensityFromSpecificEnthalpy = Relation<quantity::Density, quantity::SpecificEnthalpy>;
} // namespace mean_field::eos

View File

@@ -0,0 +1,645 @@
module;
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
export module mean_field:eos.runtime;
export import :eos.evaluation;
export namespace mean_field::eos {
class ThermodynamicQuantityId final {
public:
explicit constexpr ThermodynamicQuantityId(const std::string_view name) noexcept : m_name(name) {
}
[[nodiscard]] constexpr std::string_view name() const noexcept {
return m_name;
}
[[nodiscard]] friend constexpr bool operator==(
const ThermodynamicQuantityId &,
const ThermodynamicQuantityId &
) noexcept = default;
private:
std::string_view m_name;
};
template <typename Quantity>
concept RuntimeIdentifiedThermodynamicQuantity = ThermodynamicQuantityType<Quantity> && requires {
{ Quantity::identifier } -> std::convertible_to<std::string_view>;
} && (std::string_view{Quantity::identifier}.size() > 0);
template <RuntimeIdentifiedThermodynamicQuantity Quantity>
inline constexpr ThermodynamicQuantityId thermodynamicQuantityId{std::string_view{Quantity::identifier}};
struct RuntimeQuantityValue final {
ThermodynamicQuantityId quantity;
double value;
};
struct RuntimeRelationDescriptor final {
ThermodynamicQuantityId outputQuantity;
std::span<const ThermodynamicQuantityId> inputQuantities;
std::uint64_t partialDerivativeMask;
[[nodiscard]] constexpr bool hasPartialDerivative(const std::size_t inputIndex) const noexcept {
return inputIndex < inputQuantities.size() &&
(partialDerivativeMask & (std::uint64_t{1} << inputIndex)) != 0;
}
};
namespace detail {
template <typename RelationType> struct HasRuntimeQuantityIdentifiers : std::false_type { };
template <typename Output, typename... Inputs>
struct HasRuntimeQuantityIdentifiers<Relation<Output, Inputs...>>
: std::bool_constant<
RuntimeIdentifiedThermodynamicQuantity<Output> &&
(RuntimeIdentifiedThermodynamicQuantity<Inputs> && ...)> { };
template <typename RelationType> struct RuntimeRelationQuantities;
template <typename Output, typename... Inputs> struct RuntimeRelationQuantities<Relation<Output, Inputs...>> {
using Type = std::tuple<Output, Inputs...>;
};
template <typename... Relations>
using RuntimeCatalogQuantityTuple =
decltype(std::tuple_cat(std::declval<typename RuntimeRelationQuantities<Relations>::Type>()...));
template <
typename FirstQuantity,
typename SecondQuantity>
[[nodiscard]] consteval bool runtimeQuantityIdentifiersAreCompatible() {
if constexpr (std::same_as<FirstQuantity, SecondQuantity>) {
return true;
} else {
return thermodynamicQuantityId<FirstQuantity> != thermodynamicQuantityId<SecondQuantity>;
}
}
template <
typename QuantityTuple,
std::size_t First,
std::size_t... Offsets>
[[nodiscard]] consteval bool runtimeQuantityIdentifierIsUnambiguous(std::index_sequence<Offsets...>) {
return (
runtimeQuantityIdentifiersAreCompatible<
std::tuple_element_t<First, QuantityTuple>,
std::tuple_element_t<First + 1 + Offsets, QuantityTuple>>() &&
...
);
}
template <
typename QuantityTuple,
std::size_t... Indices>
[[nodiscard]] consteval bool runtimeQuantityIdentifiersAreUnambiguous(std::index_sequence<Indices...>) {
return (
runtimeQuantityIdentifierIsUnambiguous<QuantityTuple, Indices>(
std::make_index_sequence<std::tuple_size_v<QuantityTuple> - Indices - 1>{}
) &&
...
);
}
template <bool QuantitiesAreIdentified, typename... Relations>
struct RuntimeRelationsAreSupported : std::false_type { };
template <typename... Relations>
struct RuntimeRelationsAreSupported<true, Relations...>
: std::bool_constant<runtimeQuantityIdentifiersAreUnambiguous<RuntimeCatalogQuantityTuple<Relations...>>(
std::make_index_sequence<std::tuple_size_v<RuntimeCatalogQuantityTuple<Relations...>>>{}
)> { };
template <typename Catalog> struct RuntimeCatalogIsSupported : std::false_type { };
template <typename... Relations>
struct RuntimeCatalogIsSupported<RelationCatalog<Relations...>>
: RuntimeRelationsAreSupported<(HasRuntimeQuantityIdentifiers<Relations>::value && ...), Relations...> { };
} // namespace detail
template <typename Candidate>
concept RuntimeEquationOfStateModel =
EquationOfStateModel<Candidate> &&
detail::RuntimeCatalogIsSupported<typename std::remove_cvref_t<Candidate>::Relations>::value;
namespace detail {
template <typename EquationOfState, typename RelationType> struct RuntimeRelationStorage;
template <typename EquationOfState, typename Output, typename... Inputs>
struct RuntimeRelationStorage<EquationOfState, Relation<Output, Inputs...>> {
using RelationType = Relation<Output, Inputs...>;
static_assert(
sizeof...(Inputs) <= 64,
"Runtime EOS relation descriptors support at most 64 inputs."
);
inline static constexpr std::array<ThermodynamicQuantityId, sizeof...(Inputs)> inputQuantityIds{
thermodynamicQuantityId<Inputs>...
};
template <std::size_t... Indices>
[[nodiscard]] static consteval std::uint64_t makePartialDerivativeMask(std::index_sequence<Indices...>) {
using InputTuple = std::tuple<Inputs...>;
return (
std::uint64_t{0} | ... |
(SupportsPartialDerivative<EquationOfState, RelationType, std::tuple_element_t<Indices, InputTuple>>
? (std::uint64_t{1} << Indices)
: std::uint64_t{0})
);
}
inline static constexpr std::uint64_t partialDerivativeMask =
makePartialDerivativeMask(std::index_sequence_for<Inputs...>{});
inline static constexpr RuntimeRelationDescriptor descriptor{
thermodynamicQuantityId<Output>, std::span<const ThermodynamicQuantityId>{inputQuantityIds},
partialDerivativeMask
};
};
template <typename EquationOfState, typename Catalog> struct RuntimeCatalogStorage;
template <typename EquationOfState, typename... Relations>
struct RuntimeCatalogStorage<EquationOfState, RelationCatalog<Relations...>> {
inline static constexpr std::array descriptors{
RuntimeRelationStorage<EquationOfState, Relations>::descriptor...
};
};
[[nodiscard]] inline std::expected<
double,
EvaluationError>
runtimeEvaluationFailure(
const EvaluationErrorCode code,
std::string message
) {
return std::unexpected<EvaluationError>{EvaluationError{code, std::move(message)}};
}
template <
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] std::expected<
double,
EvaluationError>
evaluateRuntimeRelation(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...>,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto invoke = [&]<std::size_t... Indices>(std::index_sequence<Indices...>) {
return eos::evaluate<Output>(equationOfState, QuantityValue<Inputs>{inputValues[Indices].value}...)
.value();
};
try {
return invoke(std::index_sequence_for<Inputs...>{});
} catch (const EvaluationError &error) {
return std::unexpected<EvaluationError>{error};
}
}
template <
typename InputQuantity,
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] bool tryRuntimePartialDerivative(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...> relation,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues,
std::expected<
double,
EvaluationError> &result
) {
if (withRespectTo != thermodynamicQuantityId<InputQuantity>) {
return false;
}
if constexpr (SupportsPartialDerivative<EquationOfState, Relation<Output, Inputs...>, InputQuantity>) {
const auto invoke = [&]<std::size_t... Indices>(std::index_sequence<Indices...>) {
return eos::partialDerivative<Output, InputQuantity>(
equationOfState, QuantityValue<Inputs>{inputValues[Indices].value}...
)
.value();
};
try {
result = invoke(std::index_sequence_for<Inputs...>{});
} catch (const EvaluationError &error) {
result = std::unexpected<EvaluationError>{error};
}
} else {
result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_derivative,
"The requested EOS partial derivative is not available."
);
}
return true;
}
template <
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] std::expected<
double,
EvaluationError>
evaluateRuntimePartialDerivative(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...> relation,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) {
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_derivative,
"The requested quantity is not an input to the EOS relation."
);
const bool matched =
(tryRuntimePartialDerivative<Inputs>(equationOfState, relation, withRespectTo, inputValues, result) ||
...);
static_cast<void>(matched);
return result;
}
template <
typename EquationOfState,
typename RelationType>
[[nodiscard]] bool runtimeRelationMatches(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) {
const RuntimeRelationDescriptor &descriptor =
RuntimeRelationStorage<EquationOfState, RelationType>::descriptor;
if (descriptor.outputQuantity != outputQuantity ||
descriptor.inputQuantities.size() != inputValues.size()) {
return false;
}
for (std::size_t index = 0; index < inputValues.size(); ++index) {
if (descriptor.inputQuantities[index] != inputValues[index].quantity) {
return false;
}
}
return true;
}
template <typename EquationOfState, typename Catalog> struct RuntimeCatalogDispatch;
template <typename EquationOfState, typename... Relations>
struct RuntimeCatalogDispatch<EquationOfState, RelationCatalog<Relations...>> {
[[nodiscard]] static std::expected<
double,
EvaluationError>
evaluate(
const void *object,
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto &equationOfState = *static_cast<const EquationOfState *>(object);
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available."
);
const bool matched =
((runtimeRelationMatches<EquationOfState, Relations>(outputQuantity, inputValues)
? (result = evaluateRuntimeRelation(equationOfState, Relations{}, inputValues), true)
: false) ||
...);
static_cast<void>(matched);
return result;
}
[[nodiscard]] static std::expected<
double,
EvaluationError>
partialDerivative(
const void *object,
const ThermodynamicQuantityId outputQuantity,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto &equationOfState = *static_cast<const EquationOfState *>(object);
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available."
);
const bool matched =
((runtimeRelationMatches<EquationOfState, Relations>(outputQuantity, inputValues)
? (result = evaluateRuntimePartialDerivative(
equationOfState, Relations{}, withRespectTo, inputValues
),
true)
: false) ||
...);
static_cast<void>(matched);
return result;
}
};
template <RuntimeEquationOfStateModel EquationOfState>
using RuntimeAdapter = RuntimeCatalogDispatch<EquationOfState, typename EquationOfState::Relations>;
template <RuntimeEquationOfStateModel EquationOfState>
[[nodiscard]] constexpr std::span<const RuntimeRelationDescriptor> runtimeRelationDescriptors() noexcept {
return RuntimeCatalogStorage<EquationOfState, typename EquationOfState::Relations>::descriptors;
}
} // namespace detail
class EquationOfStateView final {
public:
template <RuntimeEquationOfStateModel EquationOfState>
explicit EquationOfStateView(EquationOfState &equationOfState) noexcept
: m_object(std::addressof(equationOfState)),
m_relations(detail::runtimeRelationDescriptors<std::remove_cv_t<EquationOfState>>()),
m_evaluate(&detail::RuntimeAdapter<std::remove_cv_t<EquationOfState>>::evaluate),
m_partialDerivative(&detail::RuntimeAdapter<std::remove_cv_t<EquationOfState>>::partialDerivative) {
}
[[nodiscard]] std::span<const RuntimeRelationDescriptor> relations() const noexcept {
return m_relations;
}
[[nodiscard]] bool supports(
const ThermodynamicQuantityId outputQuantity,
const std::span<const ThermodynamicQuantityId> inputQuantities
) const noexcept {
return findRelation(outputQuantity, inputQuantities) != nullptr;
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
RuntimeIdentifiedThermodynamicQuantity... InputQuantities>
[[nodiscard]] bool supports() const noexcept {
constexpr std::array<ThermodynamicQuantityId, sizeof...(InputQuantities)> inputs{
thermodynamicQuantityId<InputQuantities>...
};
return supports(thermodynamicQuantityId<OutputQuantity>, std::span<const ThermodynamicQuantityId>{inputs});
}
[[nodiscard]] std::expected<
RuntimeQuantityValue,
EvaluationError>
tryEvaluate(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) const {
const auto validation = validateRelationRequest(outputQuantity, inputValues);
if (!validation.has_value()) {
return std::unexpected<EvaluationError>{validation.error()};
}
auto result = m_evaluate(m_object, outputQuantity, inputValues);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return RuntimeQuantityValue{outputQuantity, *result};
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
QuantityValueType... InputValues>
[[nodiscard]] std::expected<
QuantityValue<OutputQuantity>,
EvaluationError>
tryEvaluate(const InputValues... inputValues) const {
constexpr bool inputsHaveRuntimeIdentifiers =
(RuntimeIdentifiedThermodynamicQuantity<QuantityOfT<InputValues>> && ...);
static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier.");
const std::array<RuntimeQuantityValue, sizeof...(InputValues)> runtimeInputs{
RuntimeQuantityValue{thermodynamicQuantityId<QuantityOfT<InputValues>>, inputValues.value()}...
};
auto result = tryEvaluate(
thermodynamicQuantityId<OutputQuantity>, std::span<const RuntimeQuantityValue>{runtimeInputs}
);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return QuantityValue<OutputQuantity>{result->value};
}
[[nodiscard]] std::expected<
double,
EvaluationError>
tryPartialDerivative(
const ThermodynamicQuantityId outputQuantity,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) const {
const auto validation = validateRelationRequest(outputQuantity, inputValues);
if (!validation.has_value()) {
return std::unexpected<EvaluationError>{validation.error()};
}
const RuntimeRelationDescriptor &descriptor = **validation;
bool derivativeAvailable = false;
for (std::size_t index = 0; index < descriptor.inputQuantities.size(); ++index) {
if (descriptor.inputQuantities[index] == withRespectTo) {
derivativeAvailable = descriptor.hasPartialDerivative(index);
break;
}
}
if (!derivativeAvailable) {
return runtimeFailure<double>(
EvaluationErrorCode::unsupported_derivative,
"The requested EOS partial derivative is not available."
);
}
return m_partialDerivative(m_object, outputQuantity, withRespectTo, inputValues);
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
RuntimeIdentifiedThermodynamicQuantity InputQuantity,
QuantityValueType... InputValues>
[[nodiscard]] std::expected<
PartialDerivative<
OutputQuantity,
InputQuantity>,
EvaluationError>
tryPartialDerivative(const InputValues... inputValues) const {
constexpr bool inputsHaveRuntimeIdentifiers =
(RuntimeIdentifiedThermodynamicQuantity<QuantityOfT<InputValues>> && ...);
static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier.");
const std::array<RuntimeQuantityValue, sizeof...(InputValues)> runtimeInputs{
RuntimeQuantityValue{thermodynamicQuantityId<QuantityOfT<InputValues>>, inputValues.value()}...
};
auto result = tryPartialDerivative(
thermodynamicQuantityId<OutputQuantity>, thermodynamicQuantityId<InputQuantity>,
std::span<const RuntimeQuantityValue>{runtimeInputs}
);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return PartialDerivative<OutputQuantity, InputQuantity>{*result};
}
private:
using RuntimeEvaluateFunction = std::expected<
double,
EvaluationError> (*)(
const void *,
ThermodynamicQuantityId,
std::span<const RuntimeQuantityValue>
);
using RuntimePartialDerivativeFunction = std::expected<
double,
EvaluationError> (*)(
const void *,
ThermodynamicQuantityId,
ThermodynamicQuantityId,
std::span<const RuntimeQuantityValue>
);
[[nodiscard]] const RuntimeRelationDescriptor *findRelation(
const ThermodynamicQuantityId outputQuantity,
const std::span<const ThermodynamicQuantityId> inputQuantities
) const noexcept {
for (const RuntimeRelationDescriptor &descriptor : m_relations) {
if (descriptor.outputQuantity != outputQuantity ||
descriptor.inputQuantities.size() != inputQuantities.size()) {
continue;
}
bool matches = true;
for (std::size_t index = 0; index < inputQuantities.size(); ++index) {
if (descriptor.inputQuantities[index] != inputQuantities[index]) {
matches = false;
break;
}
}
if (matches) {
return std::addressof(descriptor);
}
}
return nullptr;
}
[[nodiscard]] std::expected<
const RuntimeRelationDescriptor *,
EvaluationError>
validateRelationRequest(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) const {
bool outputAvailable = false;
bool inputCountAvailable = false;
for (const RuntimeRelationDescriptor &descriptor : m_relations) {
if (descriptor.outputQuantity != outputQuantity) {
continue;
}
outputAvailable = true;
if (descriptor.inputQuantities.size() != inputValues.size()) {
continue;
}
inputCountAvailable = true;
bool matches = true;
for (std::size_t index = 0; index < inputValues.size(); ++index) {
if (descriptor.inputQuantities[index] != inputValues[index].quantity) {
matches = false;
break;
}
}
if (matches) {
return std::addressof(descriptor);
}
}
if (!outputAvailable) {
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::unsupported_relation,
"The EOS does not provide a relation for output quantity '" + std::string{outputQuantity.name()} +
"'."
);
}
if (!inputCountAvailable) {
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::wrong_input_count, "No EOS relation for output quantity '" +
std::string{outputQuantity.name()} +
"' accepts the supplied number of inputs."
);
}
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::wrong_input_quantity, "No EOS relation for output quantity '" +
std::string{outputQuantity.name()} +
"' accepts the supplied input quantities."
);
}
template <typename Value>
[[nodiscard]] static std::expected<
Value,
EvaluationError>
runtimeFailure(
const EvaluationErrorCode code,
std::string message
) {
return std::unexpected<EvaluationError>{EvaluationError{code, std::move(message)}};
}
const void *m_object;
std::span<const RuntimeRelationDescriptor> m_relations;
RuntimeEvaluateFunction m_evaluate;
RuntimePartialDerivativeFunction m_partialDerivative;
};
} // namespace mean_field::eos

View File

@@ -1,6 +1,7 @@
module;
#include <array>
#include <cmath>
#include <concepts>
#include <cstddef>
#include <memory>
@@ -888,6 +889,282 @@ export namespace mean_field::field {
mfem::Array<int> m_trueToReduced;
};
/*
* Boundary rows expressed in a field's reduced solver ordering.
*
* This object is deliberately independent of any particular physical
* surface condition. Its template constructor below combines a field,
* a semantic boundary, and a domain schema. Consequently the same
* topology machinery can be used by any compiled surface formulation;
* it is not tied to enthalpy or pressure.
*/
class FieldBoundaryDofMap final {
public:
FieldBoundaryDofMap() = default;
FieldBoundaryDofMap(
const int fieldReducedSize,
const mfem::Array<int> &boundaryReducedDofs
)
: m_fieldReducedSize(fieldReducedSize),
m_boundaryReducedDofs(boundaryReducedDofs) {
if (m_fieldReducedSize < 0) {
throw std::invalid_argument("FieldBoundaryDofMap requires a non-negative field size.");
}
m_boundaryReducedDofMarker.SetSize(m_fieldReducedSize);
m_boundaryReducedDofMarker = 0;
int previousReducedDof = -1;
for (const int reducedDof : m_boundaryReducedDofs) {
if (reducedDof < 0 || reducedDof >= m_fieldReducedSize) {
throw std::invalid_argument("FieldBoundaryDofMap contains a DOF outside the reduced field vector.");
}
if (reducedDof <= previousReducedDof) {
throw std::invalid_argument("FieldBoundaryDofMap indices must be strictly increasing and unique.");
}
m_boundaryReducedDofMarker[reducedDof] = 1;
previousReducedDof = reducedDof;
}
}
[[nodiscard]] int field_size() const noexcept {
return m_fieldReducedSize;
}
[[nodiscard]] int size() const noexcept {
return m_boundaryReducedDofs.Size();
}
[[nodiscard]] bool empty() const noexcept {
return size() == 0;
}
[[nodiscard]] const mfem::Array<int> &reduced_dofs() const noexcept {
return m_boundaryReducedDofs;
}
[[nodiscard]] const mfem::Array<int> &reduced_dof_marker() const noexcept {
return m_boundaryReducedDofMarker;
}
[[nodiscard]] bool contains(const int reducedDof) const {
if (reducedDof < 0 || reducedDof >= m_fieldReducedSize) {
throw std::out_of_range("Reduced DOF index is outside FieldBoundaryDofMap.");
}
return m_boundaryReducedDofMarker[reducedDof] != 0;
}
private:
int m_fieldReducedSize{0};
mfem::Array<int> m_boundaryReducedDofs;
mfem::Array<int> m_boundaryReducedDofMarker;
};
/* Point-supported rows in a field's reduced solver ordering. */
class FieldPointDofMap final {
public:
FieldPointDofMap() = default;
FieldPointDofMap(
const int fieldReducedSize,
const mfem::Array<int> &pointReducedDofs
)
: m_selectedDofs(
fieldReducedSize,
pointReducedDofs
) {
}
[[nodiscard]] int field_size() const noexcept {
return m_selectedDofs.field_size();
}
[[nodiscard]] int size() const noexcept {
return m_selectedDofs.size();
}
[[nodiscard]] bool empty() const noexcept {
return m_selectedDofs.empty();
}
[[nodiscard]] const mfem::Array<int> &reduced_dofs() const noexcept {
return m_selectedDofs.reduced_dofs();
}
[[nodiscard]] const mfem::Array<int> &reduced_dof_marker() const noexcept {
return m_selectedDofs.reduced_dof_marker();
}
[[nodiscard]] bool contains(const int reducedDof) const {
return m_selectedDofs.contains(reducedDof);
}
private:
FieldBoundaryDofMap m_selectedDofs;
};
template <
MfemDomainField FieldT,
utils::domain::IsBoundary BoundaryT,
utils::domain::IsSchema SchemaT>
[[nodiscard]] FieldBoundaryDofMap make_field_boundary_dof_map(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const FieldDofMap &fieldDofMap
) {
static_assert(
SchemaT::template contains_boundary<BoundaryT>(),
"The requested boundary is not registered in the supplied DomainSchema."
);
MFEM_VERIFY(
!finiteElementSpace.Nonconforming(),
"Field boundary true-DOF resolution currently requires a conforming mfem::ParFiniteElementSpace."
);
MFEM_VERIFY(
fieldDofMap.full_size() == finiteElementSpace.GetTrueVSize(),
"The field map and finite-element space have incompatible true-DOF sizes."
);
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Field boundary DOF resolution requires an MFEM mesh.");
mfem::Array<int> boundaryVDofMarker(finiteElementSpace.GetVSize());
boundaryVDofMarker = 0;
mfem::Array<int> boundaryElementVDofs;
for (int boundaryElement = 0; boundaryElement < mesh->GetNBE(); ++boundaryElement) {
if (!SchemaT::template boundary_attribute_matches<BoundaryT>(mesh->GetBdrAttribute(boundaryElement))) {
continue;
}
finiteElementSpace.GetBdrElementVDofs(boundaryElement, boundaryElementVDofs);
for (const int encodedVDof : boundaryElementVDofs) {
const int vdof = mfem::FiniteElementSpace::DecodeDof(encodedVDof);
MFEM_VERIFY(
vdof >= 0 && vdof < finiteElementSpace.GetVSize(), "MFEM returned an invalid boundary vector DOF."
);
boundaryVDofMarker[vdof] = 1;
}
}
finiteElementSpace.Synchronize(boundaryVDofMarker);
mfem::Array<int> boundaryReducedDofMarker(fieldDofMap.reduced_size());
boundaryReducedDofMarker = 0;
for (int vdof = 0; vdof < boundaryVDofMarker.Size(); ++vdof) {
if (boundaryVDofMarker[vdof] == 0) {
continue;
}
const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof);
if (trueDof < 0) {
continue;
}
const std::optional<int> reducedDof = fieldDofMap.reduced_dof(trueDof);
MFEM_VERIFY(
reducedDof.has_value(),
"A boundary DOF selected for the field is absent from that field's reduced solver map."
);
boundaryReducedDofMarker[*reducedDof] = 1;
}
mfem::Array<int> boundaryReducedDofs;
mfem::FiniteElementSpace::MarkerToList(boundaryReducedDofMarker, boundaryReducedDofs);
return FieldBoundaryDofMap(fieldDofMap.reduced_size(), boundaryReducedDofs);
}
template <MfemDomainField FieldT>
[[nodiscard]] FieldPointDofMap make_field_point_dof_map(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const FieldDofMap &fieldDofMap,
const mfem::Vector &point,
const double tolerance
) {
MFEM_VERIFY(
!finiteElementSpace.Nonconforming(),
"Field point true-DOF resolution currently requires a conforming mfem::ParFiniteElementSpace."
);
MFEM_VERIFY(
fieldDofMap.full_size() == finiteElementSpace.GetTrueVSize(),
"The field map and finite-element space have incompatible true-DOF sizes."
);
MFEM_VERIFY(
std::isfinite(tolerance) && tolerance >= 0.0, "The field point tolerance must be finite and non-negative."
);
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Field point DOF resolution requires an MFEM mesh.");
MFEM_VERIFY(
point.Size() == mesh->SpaceDimension(), "The requested field point has the wrong coordinate dimension."
);
mfem::Array<int> pointVDofMarker(finiteElementSpace.GetVSize());
pointVDofMarker = 0;
mfem::Array<int> vertexVDofs;
for (int vertex = 0; vertex < mesh->GetNV(); ++vertex) {
const mfem::real_t *coordinates = mesh->GetVertex(vertex);
double distanceSquared = 0.0;
for (int component = 0; component < point.Size(); ++component) {
const double difference = coordinates[component] - point(component);
distanceSquared += difference * difference;
}
if (std::sqrt(distanceSquared) > tolerance) {
continue;
}
finiteElementSpace.GetVertexVDofs(vertex, vertexVDofs);
for (const int encodedVDof : vertexVDofs) {
const int vdof = mfem::FiniteElementSpace::DecodeDof(encodedVDof);
MFEM_VERIFY(
vdof >= 0 && vdof < finiteElementSpace.GetVSize(), "MFEM returned an invalid point vector DOF."
);
pointVDofMarker[vdof] = 1;
}
}
finiteElementSpace.Synchronize(pointVDofMarker);
mfem::Array<int> pointReducedDofMarker(fieldDofMap.reduced_size());
pointReducedDofMarker = 0;
for (int vdof = 0; vdof < pointVDofMarker.Size(); ++vdof) {
if (pointVDofMarker[vdof] == 0) {
continue;
}
const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof);
if (trueDof < 0) {
continue;
}
const std::optional<int> reducedDof = fieldDofMap.reduced_dof(trueDof);
MFEM_VERIFY(
reducedDof.has_value(),
"A point DOF selected for the field is absent from that field's reduced solver map."
);
pointReducedDofMarker[*reducedDof] = 1;
}
mfem::Array<int> pointReducedDofs;
mfem::FiniteElementSpace::MarkerToList(pointReducedDofMarker, pointReducedDofs);
const long long localPointDofCount = pointReducedDofs.Size();
long long globalPointDofCount = 0;
MPI_Allreduce(
&localPointDofCount, &globalPointDofCount, 1, MPI_LONG_LONG, MPI_SUM, finiteElementSpace.GetComm()
);
MFEM_VERIFY(
globalPointDofCount == finiteElementSpace.GetVDim(),
"The requested geometric point must identify exactly one field vertex globally."
);
return FieldPointDofMap(fieldDofMap.reduced_size(), pointReducedDofs);
}
/*
* Canonical adapter between an MFEM GridFunction and a reduced field
* vector.
@@ -1015,9 +1292,6 @@ export namespace mean_field::field {
[[nodiscard]]
FieldDofGridFunctionAdapter
make_field_dof_grid_function_adapter(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return FieldDofGridFunctionAdapter(
make_field_dof_map<FieldT, SchemaT>(finiteElementSpace),
finiteElementSpace
);
return FieldDofGridFunctionAdapter(make_field_dof_map<FieldT, SchemaT>(finiteElementSpace), finiteElementSpace);
}
} // namespace mean_field::field

View File

@@ -41,7 +41,11 @@ export namespace mean_field::integrators {
const mfem::GridFunction &compactification_coordinate,
utils::EOS_P<EOS_T> eos
)
: m_mapping(mapper, displacement, compactification_coordinate),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_eos(std::move(eos)) {
}

View File

@@ -8,259 +8,288 @@ import :mapping.compactification;
import :utils.user;
export namespace mean_field::mapping {
enum class FaceElementSide : uint8_t { element_1, element_2 };
enum class FaceElementSide : uint8_t { element_1, element_2 };
class ElementDisplacementData {
public:
ElementDisplacementData(
const mfem::FiniteElement &element, const mfem::Vector &displacement_dofs,
mfem::Ordering::Type ordering = mfem::Ordering::byNODES);
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;
[[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;
};
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;
};
struct CompactificationPointData {
double coordinate{0.0};
mfem::Vector coordinate_gradient;
};
[[nodiscard]] ElementDisplacementData
ElementDisplacementDataFromElementVDofs(const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs);
[[nodiscard]] ElementDisplacementData ElementDisplacementDataFromElementVDofs(
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs
);
class ElementCompactificationData {
public:
ElementCompactificationData(const mfem::FiniteElement &element,
const mfem::Vector &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;
[[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;
};
private:
const mfem::FiniteElement *m_element;
mfem::Vector m_dofs;
};
struct ElementMappingData {
const ElementDisplacementData &displacement;
const ElementCompactificationData &compactification;
};
struct ElementMappingData {
const ElementDisplacementData &displacement;
const ElementCompactificationData &compactification;
};
class DomainMapper {
public:
class Workspace {
public:
explicit Workspace(int dimension = 3);
class DomainMapper {
public:
class Workspace {
public:
explicit Workspace(int dimension = 3);
void SetDimension(int dimension);
void SetDimension(int dimension);
[[nodiscard]] int GetDimension() const noexcept;
[[nodiscard]] int GetDimension() const noexcept;
private:
friend class DomainMapper;
private:
friend class DomainMapper;
int m_dimension;
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_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_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_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;
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;
};
compactification::ExteriorMapResult m_exterior_result;
compactification::ExteriorMapVariation m_exterior_variation;
};
public:
DomainMapper(
utils::DomainMapperOptions options,
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map);
public:
DomainMapper(
utils::DomainMapperOptions options,
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map
);
DomainMapper(const DomainMapper &) = delete;
DomainMapper &operator=(const DomainMapper &) = delete;
DomainMapper(DomainMapper &&) = default;
DomainMapper &operator=(DomainMapper &&) = default;
DomainMapper(const DomainMapper &) = delete;
DomainMapper &operator=(const DomainMapper &) = delete;
DomainMapper(DomainMapper &&) = default;
DomainMapper &operator=(DomainMapper &&) = default;
[[nodiscard]] MappingStatus
EvaluatePoint(const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace, MappingPointContext &context) const;
[[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 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 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 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 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]] 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]] const compactification::ExteriorDomainMap &
GetExteriorMap() const noexcept;
[[nodiscard]] bool IsCompactifiedElement(const mfem::ElementTransformation &transformation) const noexcept;
[[nodiscard]] int GetDimension() const noexcept;
[[nodiscard]] const compactification::ExteriorDomainMap &GetExteriorMap() const noexcept;
private:
void ValidateElementData(const ElementMappingData &element_data) const;
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;
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]] 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 mfem::ElementTransformation &SelectFaceElementTransformation(
mfem::FaceElementTransformations &transformation,
FaceElementSide side
);
[[nodiscard]] static const mfem::IntegrationPoint &
SelectFaceElementIntegrationPoint(
mfem::FaceElementTransformations &transformation, FaceElementSide side);
[[nodiscard]] static const mfem::IntegrationPoint &SelectFaceElementIntegrationPoint(
mfem::FaceElementTransformations &transformation,
FaceElementSide side
);
utils::DomainMapperOptions m_options;
std::unique_ptr<const compactification::ExteriorDomainMap> m_exterior_map;
};
utils::DomainMapperOptions m_options;
std::unique_ptr<const compactification::ExteriorDomainMap> m_exterior_map;
};
class GridFunctionMappingEvaluator {
public:
/*
* The evaluator references the supplied grid functions and caches copies of
* their element-local DOFs. Call InvalidateCache() or Refresh() after either
* grid function's values are modified. Finite-element-space sequence changes
* are detected automatically.
*
* This object owns mutable workspace and cache state and is not thread-safe.
*/
GridFunctionMappingEvaluator(
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate);
class GridFunctionMappingEvaluator {
public:
/*
* The evaluator references the supplied grid functions and caches copies of
* their element-local DOFs. Call InvalidateCache() or Refresh() after either
* grid function's values are modified. Finite-element-space sequence changes
* are detected automatically.
*
* This object owns mutable workspace and cache state and is not thread-safe.
*/
GridFunctionMappingEvaluator(
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
);
/*
* Discard all element-local field data. The next evaluation reloads its
* requested element lazily. This operation is idempotent.
*/
void InvalidateCache() noexcept;
/*
* Discard all element-local field data. The next evaluation reloads its
* requested element lazily. This operation is idempotent.
*/
void InvalidateCache() noexcept;
/*
* Reload the currently cached element immediately. If no element has been
* evaluated yet, Refresh() is a validated no-op. If either finite-element
* space changed sequence, the old element ID is discarded and the next
* evaluation reloads lazily against the updated spaces.
*/
void Refresh();
/*
* Reload the currently cached element immediately. If no element has been
* evaluated yet, Refresh() is a validated no-op. If either finite-element
* space changed sequence, the old element ID is discarded and the next
* evaluation reloads lazily against the updated spaces.
*/
void Refresh();
[[nodiscard]] MappingStatus
EvaluatePoint(mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
MappingPointContext &context);
[[nodiscard]] MappingStatus EvaluatePoint(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
MappingPointContext &context
);
[[nodiscard]] MappingStatus
EvaluateVolume(mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
VolumeMappingContext &context);
[[nodiscard]] MappingStatus EvaluateVolume(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
VolumeMappingContext &context
);
[[nodiscard]] MappingStatus
EvaluateFace(mfem::FaceElementTransformations &transformation,
FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
FaceMappingContext &context);
[[nodiscard]] MappingStatus EvaluateFace(
mfem::FaceElementTransformations &transformation,
FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
FaceMappingContext &context
);
[[nodiscard]] VolumeQuadratureContext
GetQuadratureContext(mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point);
[[nodiscard]] VolumeQuadratureContext GetQuadratureContext(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point
);
[[nodiscard]] FaceQuadratureContext
GetFaceQuadratureContext(
mfem::FaceElementTransformations &transformation,
const mfem::IntegrationPoint &integration_point,
FaceElementSide side = FaceElementSide::element_1);
[[nodiscard]] FaceQuadratureContext GetFaceQuadratureContext(
mfem::FaceElementTransformations &transformation,
const mfem::IntegrationPoint &integration_point,
FaceElementSide side = FaceElementSide::element_1
);
void GetPhysicalPoint(mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
mfem::Vector &physical_position);
void GetPhysicalPoint(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
mfem::Vector &physical_position
);
private:
void ValidateFieldBindings() const;
[[nodiscard]] bool InvalidateForChangedSpaces();
void LoadElement(int element_id);
private:
void ValidateFieldBindings() const;
[[nodiscard]] bool InvalidateForChangedSpaces();
void LoadElement(int element_id);
const DomainMapper &m_mapper;
const mfem::GridFunction &m_displacement;
const mfem::GridFunction &m_compactification_coordinate;
const mfem::FiniteElementSpace *m_displacement_space;
const mfem::FiniteElementSpace *m_compactification_space;
long m_displacement_space_sequence;
long m_compactification_space_sequence;
DomainMapper::Workspace m_workspace;
const DomainMapper &m_mapper;
const mfem::GridFunction &m_displacement;
const mfem::GridFunction &m_compactification_coordinate;
const mfem::FiniteElementSpace *m_displacement_space;
const mfem::FiniteElementSpace *m_compactification_space;
long m_displacement_space_sequence;
long m_compactification_space_sequence;
DomainMapper::Workspace m_workspace;
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<ElementDisplacementData> m_displacement_data;
std::unique_ptr<ElementCompactificationData> m_compactification_data;
int m_cached_element_id{-1};
};
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<ElementDisplacementData> m_displacement_data;
std::unique_ptr<ElementCompactificationData> m_compactification_data;
int m_cached_element_id{-1};
};
} // namespace mean_field::mapping

View File

@@ -54,10 +54,19 @@ export import :operators.prepared_displacement_residual;
export import :model.structure_profile;
export import :model.structure.base;
export import :model.structure.polytropic;
export import :eos.base;
export import :eos.quantities;
export import :eos.relations;
export import :eos.concepts;
export import :eos.evaluation;
export import :eos.pressure_surface;
export import :eos.runtime;
export import :eos.polytrope;
export import :surface.base;
export import :surface.isobaric;
export import :surface.constant;
export import :surface.dependencies;
export import :surface.compiled;
export import :surface.compiler;
export import :model.stellar;
export import :operators.prepared_mass_normalization;
export import :operators.prepared_centering_constraint;
export import :operators.prepared_surface_constraint;
export import :operators.prepared_stellar_equilibrium;

View File

@@ -7,62 +7,84 @@ module;
export module mean_field:model.stellar;
export import :eos.base;
export import :eos.runtime;
export import :model.structure.base;
export import :surface.base;
export import :surface.compiler;
export namespace mean_field::models {
template <typename Candidate>
concept StructurePrescription =
std::derived_from<std::remove_cvref_t<Candidate>, mean_field::models::structure::StructureBase>;
namespace detail {
template <typename Candidate>
concept ConstEquationOfStateReference =
std::is_lvalue_reference_v<Candidate> && std::is_const_v<std::remove_reference_t<Candidate>> &&
eos::EquationOfStateModel<std::remove_cvref_t<Candidate>>;
} // namespace detail
template <typename Candidate>
concept SurfacePrescription = std::derived_from<std::remove_cvref_t<Candidate>, mean_field::surface::SurfaceBase>;
concept StructurePrescription = requires(
const std::remove_cvref_t<Candidate> &structurePrescription,
const structure::StructureSeedRequest &seedRequest
) {
{ structurePrescription.equationOfState() } noexcept -> detail::ConstEquationOfStateReference;
{ structurePrescription.targetMass() } noexcept -> std::same_as<double>;
{ structurePrescription.makeInitialSeed(seedRequest) } -> std::same_as<structure::StructureSeed>;
{ structurePrescription.validate() } -> std::same_as<void>;
};
/*
* Public ownership facade for a physical structure prescription and its
* stellar-surface prescription.
*
* The concrete prescriptions are allocated once at construction. Their
* stable addresses allow future prepared operators and contexts to borrow
* references without making ownership part of the user-facing API.
*/
template <StructurePrescription Candidate>
using StructureEquationOfStateT =
std::remove_cvref_t<decltype(std::declval<const std::remove_cvref_t<Candidate> &>().equationOfState())>;
template <typename Candidate, typename EquationOfState>
concept SurfacePrescription =
surface::ConstantPressureSurfaceType<Candidate> &&
surface::PressureSurfaceCompilable<surface::BarotropicSurfaceFormulation, std::remove_cvref_t<EquationOfState>>;
template <StructurePrescription Structure>
requires SurfacePrescription<surface::ConstantPressureSurface, StructureEquationOfStateT<Structure>>
class StellarModel final {
public:
template <
StructurePrescription StructureType,
SurfacePrescription SurfaceType>
using StructurePrescriptionType = Structure;
using SurfacePrescriptionType = surface::ConstantPressureSurface;
using EquationOfStateType = StructureEquationOfStateT<Structure>;
using SurfaceConstraintType =
surface::CompiledPressureSurfaceConstraintT<surface::BarotropicSurfaceFormulation, EquationOfStateType>;
template <typename StructureArgument>
requires std::same_as<
std::remove_cvref_t<StructureArgument>,
Structure>
explicit StellarModel(
StructureType &&structurePrescription,
SurfaceType &&surfacePrescription
StructureArgument &&structurePrescription,
const surface::ConstantPressureSurface surfacePrescription
)
: StellarModel(
std::make_unique<std::remove_cvref_t<StructureType>>(
std::forward<StructureType>(structurePrescription)
),
std::make_unique<std::remove_cvref_t<SurfaceType>>(std::forward<SurfaceType>(surfacePrescription))
: m_structurePrescription(
std::make_unique<Structure>(std::forward<StructureArgument>(structurePrescription))
),
m_surfacePrescription(std::make_unique<surface::ConstantPressureSurface>(surfacePrescription)),
m_compiledSurfaceConstraint(
std::make_unique<SurfaceConstraintType>(validateAndCompileSurface(
*m_structurePrescription,
*m_surfacePrescription
))
) {
}
~StellarModel() = default;
StellarModel(const StellarModel &) = delete;
StellarModel &operator=(const StellarModel &) = delete;
StellarModel(StellarModel &&) noexcept = default;
StellarModel &operator=(StellarModel &&) noexcept = default;
[[nodiscard]] const mean_field::models::structure::StructureBase &structurePrescription() const noexcept {
[[nodiscard]] const Structure &structurePrescription() const noexcept {
return *m_structurePrescription;
}
[[nodiscard]] const mean_field::surface::SurfaceBase &surfacePrescription() const noexcept {
[[nodiscard]] const surface::ConstantPressureSurface &surfacePrescription() const noexcept {
return *m_surfacePrescription;
}
[[nodiscard]] const mean_field::eos::EquationOfState &equationOfState() const noexcept {
[[nodiscard]] const EquationOfStateType &equationOfState() const noexcept {
return m_structurePrescription->equationOfState();
}
@@ -70,45 +92,99 @@ export namespace mean_field::models {
return m_structurePrescription->targetMass();
}
[[nodiscard]] mean_field::models::structure::StructureSeed
makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const {
[[nodiscard]] structure::StructureSeed makeInitialSeed(const structure::StructureSeedRequest &request) const {
return m_structurePrescription->makeInitialSeed(request);
}
[[nodiscard]] const mean_field::surface::ResolvedSurfaceCondition &resolvedSurfaceCondition() const noexcept {
return m_resolvedSurfaceCondition;
[[nodiscard]] const SurfaceConstraintType &compiledSurfaceConstraint() const noexcept {
return *m_compiledSurfaceConstraint;
}
private:
explicit StellarModel(
std::unique_ptr<mean_field::models::structure::StructureBase> structurePrescription,
std::unique_ptr<mean_field::surface::SurfaceBase> surfacePrescription
)
: m_structurePrescription(std::move(structurePrescription)),
m_surfacePrescription(std::move(surfacePrescription)),
m_resolvedSurfaceCondition(validateAndResolve(
*m_structurePrescription,
*m_surfacePrescription
)) {
}
[[nodiscard]] static mean_field::surface::ResolvedSurfaceCondition validateAndResolve(
const mean_field::models::structure::StructureBase &structurePrescription,
const mean_field::surface::SurfaceBase &surfacePrescription
[[nodiscard]] static SurfaceConstraintType validateAndCompileSurface(
const Structure &structurePrescription,
const surface::ConstantPressureSurface &surfacePrescription
) {
structurePrescription.validate();
const mean_field::eos::EquationOfState &equationOfState = structurePrescription.equationOfState();
surfacePrescription.validate(equationOfState);
return surfacePrescription.resolve(equationOfState);
return surface::compilePressureSurfaceConstraint<surface::BarotropicSurfaceFormulation>(
surfacePrescription, structurePrescription.equationOfState()
);
}
std::unique_ptr<mean_field::models::structure::StructureBase> m_structurePrescription;
std::unique_ptr<Structure> m_structurePrescription;
std::unique_ptr<surface::ConstantPressureSurface> m_surfacePrescription;
std::unique_ptr<SurfaceConstraintType> m_compiledSurfaceConstraint;
};
std::unique_ptr<mean_field::surface::SurfaceBase> m_surfacePrescription;
template <typename Structure>
StellarModel(
Structure &&,
surface::ConstantPressureSurface
) -> StellarModel<std::remove_cvref_t<Structure>>;
mean_field::surface::ResolvedSurfaceCondition m_resolvedSurfaceCondition;
namespace detail {
template <typename Candidate> struct IsStellarModel : std::false_type { };
template <typename Structure> struct IsStellarModel<StellarModel<Structure>> : std::true_type { };
} // namespace detail
template <typename Candidate>
concept StellarModelType = detail::IsStellarModel<std::remove_cvref_t<Candidate>>::value;
class StellarModelView final {
public:
template <typename Model>
requires StellarModelType<Model> &&
eos::RuntimeEquationOfStateModel<typename std::remove_cvref_t<Model>::EquationOfStateType>
explicit StellarModelView(Model &model) noexcept
: m_equationOfState(model.equationOfState()),
m_structurePrescription(std::addressof(model.structurePrescription())),
m_makeInitialSeed(&makeInitialSeedFor<typename std::remove_cvref_t<Model>::StructurePrescriptionType>),
m_targetMass(model.targetMass()),
m_surfaceCondition(model.compiledSurfaceConstraint().descriptor()),
m_surfaceDependencies(model.compiledSurfaceConstraint().runtimeDependencies()) {
}
[[nodiscard]] eos::EquationOfStateView equationOfState() const noexcept {
return m_equationOfState;
}
[[nodiscard]] double targetMass() const noexcept {
return m_targetMass;
}
[[nodiscard]] structure::StructureSeed makeInitialSeed(const structure::StructureSeedRequest &request) const {
return m_makeInitialSeed(m_structurePrescription, request);
}
[[nodiscard]] surface::PressureSurfaceDescriptor surfaceCondition() const noexcept {
return m_surfaceCondition;
}
[[nodiscard]] surface::RuntimeSurfaceConstraintDependencies surfaceDependencies() const noexcept {
return m_surfaceDependencies;
}
private:
using MakeInitialSeedFunction = structure::StructureSeed (*)(
const void *,
const structure::StructureSeedRequest &
);
template <StructurePrescription Structure>
[[nodiscard]] static structure::StructureSeed makeInitialSeedFor(
const void *structurePrescription,
const structure::StructureSeedRequest &request
) {
return static_cast<const Structure *>(structurePrescription)->makeInitialSeed(request);
}
eos::EquationOfStateView m_equationOfState;
const void *m_structurePrescription;
MakeInitialSeedFunction m_makeInitialSeed;
double m_targetMass;
surface::PressureSurfaceDescriptor m_surfaceCondition;
surface::RuntimeSurfaceConstraintDependencies m_surfaceDependencies;
};
} // namespace mean_field::models

View File

@@ -12,20 +12,20 @@ export import :model.structure.base;
import :utils.misc;
export namespace mean_field::models::structure {
class PolytropicStructure final : public StructureBase {
class PolytropicStructure final {
public:
explicit PolytropicStructure(
eos::Polytrope equationOfState,
double targetMass
);
[[nodiscard]] const eos::EquationOfState &equationOfState() const noexcept override;
[[nodiscard]] const eos::Polytrope &equationOfState() const noexcept;
[[nodiscard]] double targetMass() const noexcept override;
[[nodiscard]] double targetMass() const noexcept;
[[nodiscard]] StructureSeed makeInitialSeed(const StructureSeedRequest &request) const override;
[[nodiscard]] StructureSeed makeInitialSeed(const StructureSeedRequest &request) const;
void validate() const override;
void validate() const;
private:
struct LaneEmdenPoint {

View File

@@ -1,7 +1,7 @@
module;
#include <mfem.hpp>
export module mean_field:model.structure.base;
export import :eos.base;
export import :eos.runtime;
export namespace mean_field::models::structure {
struct StructureSeed {
@@ -23,7 +23,7 @@ export namespace mean_field::models::structure {
public:
virtual ~StructureBase() = default;
[[nodiscard]] virtual const eos::EquationOfState &equationOfState() const noexcept = 0;
[[nodiscard]] virtual eos::EquationOfStateView equationOfState() const noexcept = 0;
[[nodiscard]] virtual double targetMass() const noexcept = 0;
@@ -34,4 +34,4 @@ export namespace mean_field::models::structure {
protected:
StructureBase() = default;
};
} // namespace mean_field::models::structure
} // namespace mean_field::models::structure

View File

@@ -0,0 +1,103 @@
module;
#include <cmath>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_centering_constraint;
export import :field.mfem;
export namespace mean_field::operators {
struct PreparedCenteringConstraintReport final {
bool cachedCenterDisplacement{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return cachedCenterDisplacement;
}
};
/*
* Strong translational gauge: the material point at the computational
* origin has zero displacement. The three corresponding displacement
* residual rows replace redundant force-balance rows.
*/
class PreparedCenteringConstraint final {
public:
explicit PreparedCenteringConstraint(field::FieldPointDofMap centerRows)
: m_centerRows(std::move(centerRows)),
m_centerDisplacement(m_centerRows.size()) {
}
[[nodiscard]] PreparedCenteringConstraintReport Prepare(
const mfem::Vector &displacement,
const bool displacementChanged
) {
MFEM_VERIFY(
displacement.Size() == m_centerRows.field_size(),
"The centering constraint received a displacement vector with the wrong size."
);
PreparedCenteringConstraintReport report;
if (!m_isPrepared || displacementChanged) {
for (int centerIndex = 0; centerIndex < m_centerRows.size(); ++centerIndex) {
const double value = displacement(m_centerRows.reduced_dofs()[centerIndex]);
MFEM_VERIFY(
std::isfinite(value), "The centering constraint received a non-finite center displacement."
);
m_centerDisplacement(centerIndex) = value;
}
report.cachedCenterDisplacement = true;
}
m_isPrepared = true;
return report;
}
void ApplyResidualRows(mfem::Vector &displacementResidual) const {
VerifyPrepared();
MFEM_VERIFY(
displacementResidual.Size() == m_centerRows.field_size(),
"The centering constraint received a displacement residual with the wrong size."
);
for (int centerIndex = 0; centerIndex < m_centerRows.size(); ++centerIndex) {
displacementResidual(m_centerRows.reduced_dofs()[centerIndex]) = m_centerDisplacement(centerIndex);
}
}
void ApplyJacobianRows(
const mfem::Vector &displacementVariation,
mfem::Vector &displacementAction
) const {
VerifyPrepared();
MFEM_VERIFY(
displacementVariation.Size() == m_centerRows.field_size() &&
displacementAction.Size() == m_centerRows.field_size(),
"The centering constraint received a Jacobian vector with the wrong size."
);
for (const int centerRow : m_centerRows.reduced_dofs()) {
displacementAction(centerRow) = displacementVariation(centerRow);
}
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared;
}
[[nodiscard]] const field::FieldPointDofMap &GetCenterRows() const noexcept {
return m_centerRows;
}
private:
void VerifyPrepared() const {
MFEM_VERIFY(m_isPrepared, "The centering constraint must be prepared before row application.");
}
field::FieldPointDofMap m_centerRows;
mfem::Vector m_centerDisplacement;
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -1,7 +1,9 @@
module;
#include <compare>
#include <concepts>
#include <cstdint>
#include <type_traits>
#include <mfem.hpp>
@@ -16,9 +18,11 @@ export import :operators.context.gravity_field;
export import :operators.gravity_field;
export import :operators.gravity_field_jacobian;
export import :operators.prepared_barotropic_closure;
export import :operators.prepared_centering_constraint;
export import :operators.prepared_displacement_residual;
export import :operators.prepared_hydrostatic_equilibrium;
export import :operators.prepared_mass_normalization;
export import :operators.prepared_surface_constraint;
export import :physics.rigid_rotation;
export import :utils.blocks;
@@ -50,11 +54,14 @@ export namespace mean_field::operators {
PreparedHydrostaticEquilibriumReport hydrostatic;
PreparedDisplacementResidualReport displacement;
PreparedMassNormalizationReport massNormalization;
PreparedSurfaceConstraintReport surfaceConstraint;
PreparedCenteringConstraintReport centeringConstraint;
bool assembledResidual{false};
[[nodiscard]] bool DidAnyChildWork() const noexcept {
return gravity.DidAnyWork() || barotropicClosure.DidAnyWork() || hydrostatic.DidAnyWork() ||
displacement.DidAnyWork() || massNormalization.DidAnyWork();
displacement.DidAnyWork() || massNormalization.DidAnyWork() || surfaceConstraint.DidAnyWork() ||
centeringConstraint.DidAnyWork();
}
[[nodiscard]] bool DidAnyWork() const noexcept {
@@ -74,19 +81,27 @@ export namespace mean_field::operators {
class PreparedStellarEquilibriumOperator final : public mfem::Operator {
public:
template <models::StellarModelType Model>
requires std::same_as<
typename std::remove_cvref_t<Model>::EquationOfStateType,
eos::Polytrope> &&
SingleFieldPressureSurfaceConstraintFor<
typename std::remove_cvref_t<Model>::SurfaceConstraintType,
field::Enthalpy> &&
std::is_lvalue_reference_v<Model &&>
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass
);
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const models::StellarModel &stellarModel
);
Model &&stellarModel
)
: PreparedStellarEquilibriumOperator(
f,
domainMapper,
stellarModel.equationOfState(),
stellarModel.targetMass(),
PressureSurfaceConstraintView{stellarModel.compiledSurfaceConstraint()}
) {
}
PreparedStellarEquilibriumOperator(const PreparedStellarEquilibriumOperator &) = delete;
PreparedStellarEquilibriumOperator &operator=(const PreparedStellarEquilibriumOperator &) = delete;
@@ -122,6 +137,8 @@ export namespace mean_field::operators {
[[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept;
[[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept;
[[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept;
[[nodiscard]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
[[nodiscard]] const PreparedCenteringConstraint &GetCenteringConstraintOperator() const noexcept;
private:
struct ConstructionData;
@@ -133,6 +150,15 @@ export namespace mean_field::operators {
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
PressureSurfaceConstraintView surfaceConstraint
);
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData
);
@@ -150,6 +176,8 @@ export namespace mean_field::operators {
PreparedHydrostaticEquilibriumOperator m_hydrostaticOperator;
PreparedDisplacementResidualOperator m_displacementOperator;
PreparedMassNormalizationOperator m_massNormalizationOperator;
PreparedPressureSurfaceConstraint m_surfaceConstraintOperator;
PreparedCenteringConstraint m_centeringConstraintOperator;
StellarEquilibriumDependencies m_preparedDependencies;
mfem::Vector m_cachedResidual;

View File

@@ -0,0 +1,235 @@
module;
#include <cmath>
#include <concepts>
#include <memory>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_surface_constraint;
export import :field.mfem;
export import :surface.compiled;
namespace mean_field::operators::detail {
template <eos::ThermodynamicQuantityType Quantity> struct SingleQuantitySurfaceState final {
eos::QuantityValue<Quantity> quantityValue;
[[nodiscard]] eos::QuantityValue<Quantity> value(Quantity) const noexcept {
return quantityValue;
}
};
} // namespace mean_field::operators::detail
export namespace mean_field::operators {
/*
* Runtime enforcement currently supports a pointwise pressure constraint
* whose row field is also its sole state field. The concept is expressed
* entirely in compiled-constraint metadata: no thermodynamic carrier or
* concrete field is selected by this prepared layer.
*/
template <typename Candidate>
concept SingleFieldPressureSurfaceConstraint =
requires {
typename std::remove_cvref_t<Candidate>::PhysicalQuantity;
typename std::remove_cvref_t<Candidate>::CarrierQuantity;
typename std::remove_cvref_t<Candidate>::CarrierField;
typename std::remove_cvref_t<Candidate>::SurfaceDependencies;
} && std::same_as<typename std::remove_cvref_t<Candidate>::PhysicalQuantity, eos::quantity::Pressure> &&
std::same_as<
typename std::remove_cvref_t<Candidate>::SurfaceDependencies::RowField,
typename std::remove_cvref_t<Candidate>::CarrierField> &&
std::same_as<
typename std::remove_cvref_t<Candidate>::SurfaceDependencies::StateFieldTypes,
field::TypeList<typename std::remove_cvref_t<Candidate>::CarrierField>>;
template <typename Candidate, typename Field>
concept SingleFieldPressureSurfaceConstraintFor =
SingleFieldPressureSurfaceConstraint<Candidate> &&
std::same_as<typename std::remove_cvref_t<Candidate>::SurfaceDependencies::RowField, Field>;
/*
* Non-owning runtime bridge for a statically compiled pressure constraint.
* There is one function-pointer dispatch per complete row application;
* the concrete loop remains templated so EOS operations can be inlined.
*/
class PressureSurfaceConstraintView final {
public:
template <SingleFieldPressureSurfaceConstraint Constraint>
explicit PressureSurfaceConstraintView(const Constraint &constraint) noexcept
: m_constraint(std::addressof(constraint)),
m_applyResidualRows(&applyResidualRows<Constraint>),
m_applyJacobianRows(&applyJacobianRows<Constraint>),
m_descriptor(constraint.descriptor()) {
}
void ApplyResidualRows(
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
mfem::Vector &rowResidual
) const {
m_applyResidualRows(m_constraint, surfaceState, surfaceRows, rowResidual);
}
void ApplyJacobianRows(
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) const {
m_applyJacobianRows(m_constraint, surfaceState, surfaceRows, stateVariation, rowAction);
}
[[nodiscard]] surface::PressureSurfaceDescriptor descriptor() const noexcept {
return m_descriptor;
}
private:
using ApplyResidualRowsFunction = void (*)(
const void *,
const mfem::Vector &,
const field::FieldBoundaryDofMap &,
mfem::Vector &
);
using ApplyJacobianRowsFunction = void (*)(
const void *,
const mfem::Vector &,
const field::FieldBoundaryDofMap &,
const mfem::Vector &,
mfem::Vector &
);
template <SingleFieldPressureSurfaceConstraint Constraint>
static void applyResidualRows(
const void *constraint,
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
mfem::Vector &rowResidual
) {
using CarrierQuantity = typename Constraint::CarrierQuantity;
for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) {
const detail::SingleQuantitySurfaceState<CarrierQuantity> state{
eos::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
rowResidual(surfaceRows.reduced_dofs()[surfaceIndex]) =
static_cast<const Constraint *>(constraint)->residual(state);
}
}
template <SingleFieldPressureSurfaceConstraint Constraint>
static void applyJacobianRows(
const void *constraint,
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) {
using CarrierQuantity = typename Constraint::CarrierQuantity;
for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) {
const int reducedDof = surfaceRows.reduced_dofs()[surfaceIndex];
const detail::SingleQuantitySurfaceState<CarrierQuantity> state{
eos::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
const detail::SingleQuantitySurfaceState<CarrierQuantity> variation{
eos::QuantityValue<CarrierQuantity>{stateVariation(reducedDof)}
};
rowAction(reducedDof) = static_cast<const Constraint *>(constraint)->jacobianAction(state, variation);
}
}
const void *m_constraint;
ApplyResidualRowsFunction m_applyResidualRows;
ApplyJacobianRowsFunction m_applyJacobianRows;
surface::PressureSurfaceDescriptor m_descriptor;
};
struct PreparedSurfaceConstraintReport final {
bool cachedSurfaceState{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return cachedSurfaceState;
}
};
class PreparedPressureSurfaceConstraint final {
public:
PreparedPressureSurfaceConstraint(
field::FieldBoundaryDofMap surfaceRows,
const PressureSurfaceConstraintView constraint
)
: m_surfaceRows(std::move(surfaceRows)),
m_constraint(constraint),
m_surfaceState(m_surfaceRows.size()) {
}
[[nodiscard]] PreparedSurfaceConstraintReport Prepare(
const mfem::Vector &reducedState,
const bool stateChanged
) {
MFEM_VERIFY(
reducedState.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a state vector with the wrong size."
);
PreparedSurfaceConstraintReport report;
if (!m_isPrepared || stateChanged) {
for (int surfaceIndex = 0; surfaceIndex < m_surfaceRows.size(); ++surfaceIndex) {
const double value = reducedState(m_surfaceRows.reduced_dofs()[surfaceIndex]);
MFEM_VERIFY(std::isfinite(value), "The pressure surface constraint received non-finite state.");
m_surfaceState(surfaceIndex) = value;
}
report.cachedSurfaceState = true;
}
m_isPrepared = true;
return report;
}
void ApplyResidualRows(mfem::Vector &rowResidual) const {
VerifyPrepared();
MFEM_VERIFY(
rowResidual.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a residual vector with the wrong size."
);
m_constraint.ApplyResidualRows(m_surfaceState, m_surfaceRows, rowResidual);
}
void ApplyJacobianRows(
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) const {
VerifyPrepared();
MFEM_VERIFY(
stateVariation.Size() == m_surfaceRows.field_size() && rowAction.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a Jacobian vector with the wrong size."
);
m_constraint.ApplyJacobianRows(m_surfaceState, m_surfaceRows, stateVariation, rowAction);
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared;
}
[[nodiscard]] const field::FieldBoundaryDofMap &GetSurfaceRows() const noexcept {
return m_surfaceRows;
}
[[nodiscard]] surface::PressureSurfaceDescriptor GetPhysicalCondition() const noexcept {
return m_constraint.descriptor();
}
private:
void VerifyPrepared() const {
MFEM_VERIFY(m_isPrepared, "The pressure surface constraint must be prepared before row application.");
}
field::FieldBoundaryDofMap m_surfaceRows;
PressureSurfaceConstraintView m_constraint;
mfem::Vector m_surfaceState;
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,66 @@
module;
export module mean_field:surface.compiled;
export import :eos.pressure_surface;
export import :surface.constant;
export import :surface.dependencies;
export namespace mean_field::surface {
template <
eos::EquationOfStateModel EquationOfState,
SurfaceConstraintFormulationType Formulation,
eos::ThermodynamicRelationType SelectedRelation,
typename Dependencies>
class CompiledPressureSurfaceConstraint final {
public:
using PhysicalCondition = ConstantPressureSurface;
using PhysicalQuantity = eos::quantity::Pressure;
using CarrierQuantity = typename Formulation::CarrierQuantity;
using CarrierField = typename Formulation::CarrierField;
using Relation = SelectedRelation;
using SurfaceDependencies = Dependencies;
CompiledPressureSurfaceConstraint(
const ConstantPressureSurface condition,
const EquationOfState &equationOfState
) noexcept
: m_condition(condition),
m_resolvedRelation(
equationOfState,
condition.targetPressure()
) {
}
[[nodiscard]] eos::PressureValue targetPressure() const noexcept {
return m_condition.targetPressure();
}
[[nodiscard]] PressureSurfaceDescriptor descriptor() const noexcept {
return m_condition.descriptor();
}
[[nodiscard]] static constexpr RuntimeSurfaceConstraintDependencies runtimeDependencies() noexcept {
return SurfaceDependencies::runtimeDescription();
}
template <typename SurfaceState> [[nodiscard]] double residual(const SurfaceState &state) const {
return state.value(CarrierQuantity{}).value() - m_resolvedRelation.requiredCarrierValue(state).value();
}
template <
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] double jacobianAction(
const SurfaceState &state,
const SurfaceVariation &variation
) const {
return variation.value(CarrierQuantity{}).value() -
m_resolvedRelation.carrierCorrectionJacobianAction(state, variation);
}
private:
ConstantPressureSurface m_condition;
eos::ResolvedPressureSurfaceRelation<EquationOfState, Relation> m_resolvedRelation;
};
} // namespace mean_field::surface

View File

@@ -0,0 +1,143 @@
module;
#include <cstddef>
#include <tuple>
#include <type_traits>
export module mean_field:surface.compiler;
export import :surface.compiled;
export namespace mean_field::surface {
namespace detail {
template <typename RelationType, typename Formulation, typename EquationOfState>
struct PressureSurfaceRelationMatches : std::false_type { };
template <typename OutputQuantity, typename... InputQuantities, typename Formulation, typename EquationOfState>
struct PressureSurfaceRelationMatches<
eos::Relation<OutputQuantity, InputQuantities...>,
Formulation,
EquationOfState>
: std::bool_constant<
std::same_as<OutputQuantity, typename Formulation::CarrierQuantity> &&
(std::same_as<eos::quantity::Pressure, InputQuantities> || ...) &&
((std::same_as<eos::quantity::Pressure, InputQuantities> ||
(surfaceBindingCount<typename Formulation::StateBindings, InputQuantities> == 1 &&
eos::SupportsPartialDerivative<
EquationOfState,
eos::Relation<OutputQuantity, InputQuantities...>,
InputQuantities>)) &&
...)> { };
template <typename Catalog, typename Formulation, typename EquationOfState>
struct MatchingPressureSurfaceRelations;
template <typename... Relations, typename Formulation, typename EquationOfState>
struct MatchingPressureSurfaceRelations<eos::RelationCatalog<Relations...>, Formulation, EquationOfState> {
using Tuple = decltype(std::tuple_cat(
std::conditional_t<
PressureSurfaceRelationMatches<Relations, Formulation, EquationOfState>::value,
std::tuple<Relations>,
std::tuple<>>{}...
));
static constexpr std::size_t count = std::tuple_size_v<Tuple>;
};
template <std::size_t Count, typename Tuple> struct UniquePressureSurfaceRelation {
using Type = void;
};
template <typename Tuple> struct UniquePressureSurfaceRelation<1, Tuple> {
using Type = std::tuple_element_t<0, Tuple>;
};
template <typename Dependencies, typename Field> struct AppendSurfaceDependency;
template <typename RowField, typename... StateFields, typename Field>
struct AppendSurfaceDependency<SurfaceConstraintDependencies<RowField, StateFields...>, Field> {
using Type = SurfaceConstraintDependencies<RowField, StateFields..., Field>;
};
template <typename Dependencies, typename InputQuantity, typename Bindings>
struct AppendPressureSurfaceInputDependency {
using Type =
typename AppendSurfaceDependency<Dependencies, SurfaceFieldForQuantityT<Bindings, InputQuantity>>::Type;
};
template <typename Dependencies, typename Bindings>
struct AppendPressureSurfaceInputDependency<Dependencies, eos::quantity::Pressure, Bindings> {
using Type = Dependencies;
};
template <typename Dependencies, typename Bindings, typename... InputQuantities>
struct AppendPressureSurfaceInputDependencies;
template <typename Dependencies, typename Bindings>
struct AppendPressureSurfaceInputDependencies<Dependencies, Bindings> {
using Type = Dependencies;
};
template <typename Dependencies, typename Bindings, typename FirstInput, typename... RemainingInputs>
struct AppendPressureSurfaceInputDependencies<Dependencies, Bindings, FirstInput, RemainingInputs...> {
using WithFirst = typename AppendPressureSurfaceInputDependency<Dependencies, FirstInput, Bindings>::Type;
using Type = typename AppendPressureSurfaceInputDependencies<WithFirst, Bindings, RemainingInputs...>::Type;
};
template <typename RelationType, typename Formulation> struct PressureSurfaceDependenciesForRelation;
template <typename OutputQuantity, typename... InputQuantities, typename Formulation>
struct PressureSurfaceDependenciesForRelation<eos::Relation<OutputQuantity, InputQuantities...>, Formulation> {
using InitialDependencies =
SurfaceConstraintDependencies<typename Formulation::CarrierField, typename Formulation::CarrierField>;
using Type = typename AppendPressureSurfaceInputDependencies<
InitialDependencies,
typename Formulation::StateBindings,
InputQuantities...>::Type;
};
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
struct PressureSurfaceCompilation {
using Matches =
MatchingPressureSurfaceRelations<typename EquationOfState::Relations, Formulation, EquationOfState>;
using Relation = typename UniquePressureSurfaceRelation<Matches::count, typename Matches::Tuple>::Type;
};
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
requires(PressureSurfaceCompilation<Formulation, EquationOfState>::Matches::count == 1)
struct CompiledPressureSurfaceConstraintType {
using Compilation = PressureSurfaceCompilation<Formulation, EquationOfState>;
using Relation = typename Compilation::Relation;
using Dependencies = typename PressureSurfaceDependenciesForRelation<Relation, Formulation>::Type;
using Type = CompiledPressureSurfaceConstraint<EquationOfState, Formulation, Relation, Dependencies>;
};
} // namespace detail
template <typename Formulation, typename EquationOfState>
concept PressureSurfaceCompilable =
SurfaceConstraintFormulationType<Formulation> && eos::EquationOfStateModel<EquationOfState> &&
(detail::PressureSurfaceCompilation<std::remove_cvref_t<Formulation>, std::remove_cvref_t<EquationOfState>>::
Matches::count == 1);
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
requires PressureSurfaceCompilable<Formulation, EquationOfState>
using CompiledPressureSurfaceConstraintT = typename detail::CompiledPressureSurfaceConstraintType<
std::remove_cvref_t<Formulation>,
std::remove_cvref_t<EquationOfState>>::Type;
template <
SurfaceConstraintFormulationType Formulation,
eos::EquationOfStateModel EquationOfState>
requires PressureSurfaceCompilable<
Formulation,
EquationOfState>
[[nodiscard]] CompiledPressureSurfaceConstraintT<
Formulation,
EquationOfState>
compilePressureSurfaceConstraint(
const ConstantPressureSurface condition,
const EquationOfState &equationOfState
) noexcept {
return CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>{condition, equationOfState};
}
} // namespace mean_field::surface

View File

@@ -0,0 +1,65 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
#include <type_traits>
export module mean_field:surface.constant;
export import :eos.quantities;
export namespace mean_field::surface {
struct PressureSurfaceDescriptor final {
double targetPressure;
};
/*
* The only physical surface prescription currently supported by
* MeanField. It says nothing about which thermodynamic variable appears
* in a nonlinear state vector; resolving pressure into that representation
* is an EOS responsibility.
*/
class ConstantPressureSurface final {
public:
using PhysicalQuantity = eos::quantity::Pressure;
using TargetValue = eos::PressureValue;
explicit ConstantPressureSurface(const TargetValue targetPressure) : m_targetPressure(targetPressure) {
if (!std::isfinite(targetPressure.value())) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be finite. Instead P = {} was provided.",
targetPressure.value()
)
);
}
if (targetPressure.value() < 0.0) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be non-negative. Instead P = {} was provided.",
targetPressure.value()
)
);
}
}
[[nodiscard]] TargetValue targetPressure() const noexcept {
return m_targetPressure;
}
[[nodiscard]] PressureSurfaceDescriptor descriptor() const noexcept {
return PressureSurfaceDescriptor{.targetPressure = m_targetPressure.value()};
}
private:
TargetValue m_targetPressure;
};
template <typename Candidate>
concept ConstantPressureSurfaceType = std::same_as<std::remove_cvref_t<Candidate>, ConstantPressureSurface>;
// Familiar physical terminology retained as a synonym, not as a second
// surface-condition type.
using Isobaric = ConstantPressureSurface;
} // namespace mean_field::surface

View File

@@ -0,0 +1,186 @@
module;
#include <array>
#include <concepts>
#include <cstddef>
#include <span>
#include <string_view>
#include <type_traits>
export module mean_field:surface.dependencies;
export import :eos.relations;
export import :field.registry;
export namespace mean_field::surface {
template <typename Candidate>
concept SurfaceFieldType = requires {
{ Candidate::name } -> std::convertible_to<std::string_view>;
} && (std::string_view{Candidate::name}.size() > 0);
class SurfaceFieldId final {
public:
explicit constexpr SurfaceFieldId(const std::string_view name) noexcept : m_name(name) {
}
[[nodiscard]] constexpr std::string_view name() const noexcept {
return m_name;
}
[[nodiscard]] friend constexpr bool operator==(
const SurfaceFieldId &,
const SurfaceFieldId &
) noexcept = default;
private:
std::string_view m_name;
};
template <SurfaceFieldType Field> inline constexpr SurfaceFieldId surfaceFieldId{std::string_view{Field::name}};
template <eos::ThermodynamicQuantityType ThermodynamicQuantity, SurfaceFieldType Field>
struct SurfaceStateBinding final {
using Quantity = ThermodynamicQuantity;
using FieldType = Field;
};
template <typename... Bindings> struct SurfaceStateBindings final { };
namespace detail {
template <typename... Types> struct SurfaceTypesAreUnique : std::true_type { };
template <typename First, typename... Remaining>
struct SurfaceTypesAreUnique<First, Remaining...>
: std::bool_constant<
(!std::same_as<First, Remaining> && ...) && SurfaceTypesAreUnique<Remaining...>::value> { };
template <typename Bindings> struct SurfaceBindingsAreValid : std::false_type { };
template <typename... Bindings>
struct SurfaceBindingsAreValid<SurfaceStateBindings<Bindings...>>
: std::bool_constant<
(sizeof...(Bindings) > 0) &&
(requires {
typename Bindings::Quantity;
typename Bindings::FieldType;
} && ...) &&
(eos::ThermodynamicQuantityType<
typename Bindings::Quantity> && ...) &&
(SurfaceFieldType<typename Bindings::FieldType> && ...) &&
SurfaceTypesAreUnique<
typename Bindings::Quantity...>::value> { };
template <typename Bindings, typename Quantity> struct SurfaceBindingCount;
template <typename Quantity, typename... Bindings>
struct SurfaceBindingCount<SurfaceStateBindings<Bindings...>, Quantity>
: std::integral_constant<
std::size_t,
(std::size_t{0} + ... +
(std::same_as<Quantity, typename Bindings::Quantity> ? std::size_t{1} : std::size_t{0}))> {
};
template <typename Bindings, typename Quantity> struct SurfaceFieldForQuantity;
template <typename Quantity, typename First, typename... Remaining>
struct SurfaceFieldForQuantity<SurfaceStateBindings<First, Remaining...>, Quantity>
: std::conditional_t<
std::same_as<Quantity, typename First::Quantity>,
std::type_identity<typename First::FieldType>,
SurfaceFieldForQuantity<SurfaceStateBindings<Remaining...>, Quantity>> { };
template <typename Candidate, std::size_t CarrierBindingCount>
struct CarrierFieldMatchesSurfaceBinding : std::false_type { };
template <typename Candidate>
struct CarrierFieldMatchesSurfaceBinding<Candidate, 1>
: std::bool_constant<std::same_as<
typename SurfaceFieldForQuantity<
typename Candidate::StateBindings,
typename Candidate::CarrierQuantity>::type,
typename Candidate::CarrierField>> { };
template <
typename Candidate,
bool BindingsAreValid = SurfaceBindingsAreValid<typename Candidate::StateBindings>::value>
struct FormulationBindingsMatchCarrier : std::false_type { };
template <typename Candidate>
struct FormulationBindingsMatchCarrier<Candidate, true>
: CarrierFieldMatchesSurfaceBinding<
Candidate,
SurfaceBindingCount<
typename Candidate::StateBindings,
typename Candidate::CarrierQuantity>::value> { };
template <typename Candidate, typename = void>
struct IsSurfaceConstraintFormulation : std::false_type { };
template <typename Candidate>
struct IsSurfaceConstraintFormulation<
Candidate,
std::void_t<
typename Candidate::CarrierQuantity,
typename Candidate::CarrierField,
typename Candidate::StateBindings>>
: std::bool_constant<
eos::ThermodynamicQuantityType<typename Candidate::CarrierQuantity> &&
SurfaceFieldType<typename Candidate::CarrierField> &&
FormulationBindingsMatchCarrier<Candidate>::value> { };
} // namespace detail
template <typename Candidate>
concept ValidSurfaceStateBindings = detail::SurfaceBindingsAreValid<std::remove_cv_t<Candidate>>::value;
template <ValidSurfaceStateBindings Bindings, typename Quantity>
inline constexpr std::size_t surfaceBindingCount = detail::SurfaceBindingCount<Bindings, Quantity>::value;
template <ValidSurfaceStateBindings Bindings, typename Quantity>
requires(surfaceBindingCount<Bindings, Quantity> == 1)
using SurfaceFieldForQuantityT = typename detail::SurfaceFieldForQuantity<Bindings, Quantity>::type;
template <
eos::ThermodynamicQuantityType CarrierThermodynamicQuantity,
SurfaceFieldType CarrierFieldType,
ValidSurfaceStateBindings Bindings>
requires(
surfaceBindingCount<Bindings, CarrierThermodynamicQuantity> == 1 &&
std::same_as<SurfaceFieldForQuantityT<Bindings, CarrierThermodynamicQuantity>, CarrierFieldType>
)
struct SurfaceConstraintFormulation final {
using CarrierQuantity = CarrierThermodynamicQuantity;
using CarrierField = CarrierFieldType;
using StateBindings = Bindings;
};
using BarotropicSurfaceFormulation = SurfaceConstraintFormulation<
eos::quantity::SpecificEnthalpy,
field::Enthalpy,
SurfaceStateBindings<SurfaceStateBinding<eos::quantity::SpecificEnthalpy, field::Enthalpy>>>;
template <typename Candidate>
concept SurfaceConstraintFormulationType =
detail::IsSurfaceConstraintFormulation<std::remove_cv_t<Candidate>>::value;
struct RuntimeSurfaceConstraintDependencies final {
SurfaceFieldId residualRowField;
std::span<const SurfaceFieldId> stateFields;
};
template <SurfaceFieldType ResidualField, SurfaceFieldType... StateFields>
struct SurfaceConstraintDependencies final {
using RowField = ResidualField;
using StateFieldTypes = field::TypeList<StateFields...>;
inline static constexpr std::array<SurfaceFieldId, sizeof...(StateFields)> runtimeStateFields{
surfaceFieldId<StateFields>...
};
[[nodiscard]] static constexpr RuntimeSurfaceConstraintDependencies runtimeDescription() noexcept {
return RuntimeSurfaceConstraintDependencies{
.residualRowField = surfaceFieldId<ResidualField>,
.stateFields = std::span<const SurfaceFieldId>{runtimeStateFields}
};
}
};
} // namespace mean_field::surface

View File

@@ -1,64 +0,0 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
export module mean_field:surface.isobaric;
export import :surface.base;
export namespace mean_field::surface {
class Isobaric final : public SurfaceBase {
public:
explicit Isobaric(const double targetPressure = 0.0) : m_targetPressure(targetPressure) {
validateTargetPressure();
}
[[nodiscard]] double targetPressure() const noexcept {
return m_targetPressure;
}
[[nodiscard]] ResolvedSurfaceCondition
resolve(const mean_field::eos::EquationOfState &equationOfState) const override {
return ResolvedSurfaceCondition{resolveTargetEnthalpy(equationOfState)};
}
void validate(const mean_field::eos::EquationOfState &equationOfState) const override {
static_cast<void>(resolveTargetEnthalpy(equationOfState));
}
private:
[[nodiscard]] double resolveTargetEnthalpy(const mean_field::eos::EquationOfState &equationOfState) const {
validateTargetPressure();
const double targetEnthalpy = equationOfState.enthalpy_from_pressure(m_targetPressure);
if (!std::isfinite(targetEnthalpy) || targetEnthalpy < 0.0) {
throw std::domain_error(
std::format(
"The equation of state resolved the isobaric "
"target P = {} to the invalid enthalpy h = {}.",
m_targetPressure, targetEnthalpy
)
);
}
return targetEnthalpy;
}
void validateTargetPressure() const {
if (!std::isfinite(m_targetPressure) || m_targetPressure < 0.0) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be finite and "
"non-negative. Instead P = {} was provided.",
m_targetPressure
)
);
}
}
double m_targetPressure;
};
} // namespace mean_field::surface

View File

@@ -1,53 +0,0 @@
module;
#include <cmath>
#include <stdexcept>
export module mean_field:surface.base;
export import :eos.base;
export namespace mean_field::surface {
struct ResolvedSurfaceCondition final {
double targetEnthalpy{0.0};
explicit ResolvedSurfaceCondition(const double requestedTargetEnthalpy)
: targetEnthalpy(requestedTargetEnthalpy) {
if (!std::isfinite(targetEnthalpy) || targetEnthalpy < 0.0) {
throw std::invalid_argument(
"A resolved surface enthalpy must be finite and "
"non-negative."
);
}
}
[[nodiscard]] double residual(const double enthalpy) const {
if (!std::isfinite(enthalpy)) {
throw std::invalid_argument("A surface enthalpy value must be finite.");
}
return enthalpy - targetEnthalpy;
}
[[nodiscard]] static double jacobianAction(const double enthalpyVariation) {
if (!std::isfinite(enthalpyVariation)) {
throw std::invalid_argument("A surface enthalpy variation must be finite.");
}
return enthalpyVariation;
}
};
class SurfaceBase {
public:
virtual ~SurfaceBase() = default;
[[nodiscard]] virtual ResolvedSurfaceCondition
resolve(const mean_field::eos::EquationOfState &equationOfState) const = 0;
virtual void validate(const mean_field::eos::EquationOfState &equationOfState) const = 0;
protected:
SurfaceBase() = default;
};
} // namespace mean_field::surface

File diff suppressed because it is too large Load Diff

View File

@@ -11,82 +11,90 @@ export module mean_field:utils.misc;
import :utils.domain;
export namespace mean_field::utils {
constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4;
constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4;
bool is_vacuum(const mfem::ElementTransformation &Tr,
mfem::Array<mfem::Vector *> elvec) {
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
bool is_vacuum(
const mfem::ElementTransformation &Tr,
mfem::Array<mfem::Vector *> elvec
) {
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
const int size_elvec = elvec.Size();
for (int i = 0; i < size_elvec; i++) {
if (elvec[i]) {
*elvec[i] = 0.0;
}
}
return true;
}
return false;
}
bool is_vacuum(const mfem::ElementTransformation &Tr,
const mfem::Array2D<mfem::DenseMatrix *> &elmats) {
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
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;
if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
const int size_elvec = elvec.Size();
for (int i = 0; i < size_elvec; i++) {
if (elvec[i]) {
*elvec[i] = 0.0;
}
}
return true;
}
}
return false;
}
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";
bool is_vacuum(
const mfem::ElementTransformation &Tr,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
constexpr double G = 1.0;
constexpr double MASS = 1.0;
constexpr double RADIUS = 1.0;
if (Schema::template attribute_belongs_to<domain::Vacuum>(Tr.Attribute)) {
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;
}
[[maybe_unused]] constexpr char HOST[10] = "localhost";
[[maybe_unused]] constexpr int PORT = 19916;
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";
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>>;
constexpr double G = 1.0;
constexpr double MASS = 1.0;
constexpr double RADIUS = 1.0;
template <typename T>
concept is_real = std::is_floating_point_v<T> || is_xad<T>;
[[maybe_unused]] constexpr char HOST[10] = "localhost";
[[maybe_unused]] constexpr int PORT = 19916;
template <is_real T>
using EOS_P = std::function<T(const T &rho, const T &temp)>;
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>>;
enum class DOMAINS : uint8_t {
CORE = 1 << 0,
ENVELOPE = 1 << 1,
VACUUM = 1 << 2,
STELLAR = CORE | ENVELOPE,
ALL = CORE | ENVELOPE | VACUUM
};
template <typename T>
concept is_real = std::is_floating_point_v<T> || is_xad<T>;
DOMAINS operator|(DOMAINS lhs, DOMAINS rhs);
template <is_real T> using EOS_P = std::function<T(const T &rho, const T &temp)>;
DOMAINS operator&(DOMAINS lhs, DOMAINS rhs);
enum class DOMAINS : uint8_t {
CORE = 1 << 0,
ENVELOPE = 1 << 1,
VACUUM = 1 << 2,
STELLAR = CORE | ENVELOPE,
ALL = CORE | ENVELOPE | VACUUM
};
int get_mesh_order(const mfem::Mesh &mesh);
DOMAINS operator|(
DOMAINS lhs,
DOMAINS rhs
);
DOMAINS operator&(
DOMAINS lhs,
DOMAINS rhs
);
int get_mesh_order(const mfem::Mesh &mesh);
} // namespace mean_field::utils