perf(jacobian-action): major updates to jacobian action application by removing redudant quadrature work. ~5x increase in speed

This commit is contained in:
2026-09-02 17:01:50 -04:00
parent 85500fef3b
commit 25510008dd
74 changed files with 8967 additions and 814 deletions

View File

@@ -489,7 +489,8 @@ namespace mean_field::mapping {
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
CompactificationPointData &point_data
CompactificationPointData &point_data,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const {
const mfem::FiniteElement &element = compactification.GetElement();
const mfem::Vector &dofs = compactification.GetDofs();
@@ -509,13 +510,17 @@ namespace mean_field::mapping {
return MappingStatus::non_finite_input;
}
transformation.SetIntPoint(&integration_point);
workspace.m_compactification_shape.SetSize(dof_count);
workspace.m_compactification_dshape.SetSize(dof_count, m_options.dimension);
element.CalcShape(integration_point, workspace.m_compactification_shape);
element.CalcPhysDShape(transformation, workspace.m_compactification_dshape);
if (inverse_mesh_jacobian != nullptr) {
workspace.m_reference_dshape.SetSize(dof_count, m_options.dimension);
element.CalcDShape(integration_point, workspace.m_reference_dshape);
mfem::Mult(workspace.m_reference_dshape, *inverse_mesh_jacobian, workspace.m_compactification_dshape);
} else {
element.CalcPhysDShape(transformation, workspace.m_compactification_dshape);
}
point_data.coordinate = dofs * workspace.m_compactification_shape;
point_data.coordinate_gradient.SetSize(m_options.dimension);
@@ -539,10 +544,9 @@ namespace mean_field::mapping {
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
mfem::Vector &value,
mfem::DenseMatrix &jacobian
mfem::DenseMatrix &jacobian,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const {
transformation.SetIntPoint(&integration_point);
const mfem::FiniteElement &element = field.GetElement();
const mfem::DenseMatrix &dof_matrix = field.GetDofMatrix();
@@ -550,7 +554,13 @@ namespace mean_field::mapping {
workspace.m_mesh_dshape.SetSize(element.GetDof(), m_options.dimension);
element.CalcShape(integration_point, workspace.m_shape);
element.CalcPhysDShape(transformation, workspace.m_mesh_dshape);
if (inverse_mesh_jacobian != nullptr) {
workspace.m_reference_dshape.SetSize(element.GetDof(), m_options.dimension);
element.CalcDShape(integration_point, workspace.m_reference_dshape);
mfem::Mult(workspace.m_reference_dshape, *inverse_mesh_jacobian, workspace.m_mesh_dshape);
} else {
element.CalcPhysDShape(transformation, workspace.m_mesh_dshape);
}
value.SetSize(m_options.dimension);
dof_matrix.MultTranspose(workspace.m_shape, value);
@@ -586,7 +596,7 @@ namespace mean_field::mapping {
EvaluateField(
element_data.displacement, transformation, integration_point, workspace, workspace.m_field_value,
workspace.m_field_jacobian
workspace.m_field_jacobian, nullptr
);
if (!vector_is_finite(context.reference_position) || !vector_is_finite(workspace.m_field_value) ||
@@ -608,7 +618,7 @@ namespace mean_field::mapping {
if (context.compactified) {
const MappingStatus coordinate_status = EvaluateCompactificationCoordinate(
element_data.compactification, transformation, integration_point, workspace,
workspace.m_compactification_point
workspace.m_compactification_point, nullptr
);
if (coordinate_status != MappingStatus::valid)
@@ -663,7 +673,6 @@ namespace mean_field::mapping {
if (point_status != MappingStatus::valid)
return point_status;
transformation.SetIntPoint(&integration_point);
mfem::Mult(context.mapping.mapping_jacobian, transformation.Jacobian(), workspace.m_full_element_jacobian);
context.quadrature.J_inv.SetSize(m_options.dimension, m_options.dimension);
@@ -766,6 +775,21 @@ namespace mean_field::mapping {
const MappingPointContext &base_context,
Workspace &workspace,
MappingPointVariation &variation
) const {
return EvaluatePointVariationImpl(
element_data, direction, transformation, integration_point, base_context, workspace, variation, nullptr
);
}
MappingStatus DomainMapper::EvaluatePointVariationImpl(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const MappingPointContext &base_context,
Workspace &workspace,
MappingPointVariation &variation,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const {
ValidateElementData(element_data);
const ElementMappingData direction_data{
@@ -786,8 +810,13 @@ namespace mean_field::mapping {
"domain."
);
if (inverse_mesh_jacobian == nullptr) {
transformation.SetIntPoint(&integration_point);
}
EvaluateField(
direction, transformation, integration_point, workspace, workspace.m_field_value, workspace.m_field_jacobian
direction, transformation, integration_point, workspace, workspace.m_field_value,
workspace.m_field_jacobian, inverse_mesh_jacobian
);
if (!vector_is_finite(workspace.m_field_value) || !matrix_is_finite(workspace.m_field_jacobian))
@@ -799,7 +828,7 @@ namespace mean_field::mapping {
if (base_context.compactified) {
const MappingStatus coordinate_status = EvaluateCompactificationCoordinate(
element_data.compactification, transformation, integration_point, workspace,
workspace.m_compactification_point
workspace.m_compactification_point, inverse_mesh_jacobian
);
if (coordinate_status != MappingStatus::valid)
@@ -872,27 +901,28 @@ namespace mean_field::mapping {
Workspace &workspace,
VolumeMappingVariation &variation
) const {
const MappingStatus point_status = EvaluatePointVariation(
mfem::Mult(base_context.quadrature.J_inv, base_context.mapping.mapping_jacobian, workspace.m_matrix_temp_2);
const MappingStatus point_status = EvaluatePointVariationImpl(
element_data, direction, transformation, integration_point, base_context.mapping, workspace,
variation.mapping
variation.mapping, &workspace.m_matrix_temp_2
);
if (point_status != MappingStatus::valid)
return point_status;
transformation.SetIntPoint(&integration_point);
mfem::Mult(
variation.mapping.mapping_jacobian_variation, transformation.Jacobian(), workspace.m_full_element_jacobian
base_context.quadrature.J_inv, variation.mapping.mapping_jacobian_variation, workspace.m_matrix_temp_1
);
mfem::Mult(base_context.quadrature.J_inv, workspace.m_full_element_jacobian, workspace.m_matrix_temp_1);
variation.inverse_element_jacobian_variation.SetSize(m_options.dimension, m_options.dimension);
mfem::Mult(
workspace.m_matrix_temp_1, base_context.quadrature.J_inv, variation.inverse_element_jacobian_variation
workspace.m_matrix_temp_1, base_context.mapping.inverse_mapping_jacobian,
variation.inverse_element_jacobian_variation
);
variation.inverse_element_jacobian_variation *= -1.0;
variation.weight_variation =
integration_point.weight * transformation.Weight() * variation.mapping.mapping_determinant_variation;
variation.weight_variation = base_context.quadrature.weight / base_context.mapping.mapping_determinant *
variation.mapping.mapping_determinant_variation;
if (!matrix_is_finite(variation.inverse_element_jacobian_variation) ||
!std::isfinite(variation.weight_variation))

View File

@@ -217,16 +217,22 @@ namespace mean_field::mapping {
);
MFEM_VERIFY(std::isfinite(determinant_variation), "The mapping determinant variation must be finite.");
mfem::DenseMatrix determinant_correction(dimension, dimension);
ComputeHDivMassTensor(context, determinant_correction);
determinant_correction *= determinant_variation / determinant;
const mfem::DenseMatrix &jacobian = context.mapping_jacobian;
const mfem::DenseMatrix &jacobianVariation = variation.mapping_jacobian_variation;
const double inverseDeterminant = 1.0 / determinant;
const double determinantScale = determinant_variation * inverseDeterminant;
mfem::DenseMatrix right_jacobian_variation(dimension, dimension);
mfem::MultAtB(context.mapping_jacobian, variation.mapping_jacobian_variation, right_jacobian_variation);
mfem::MultAtB(variation.mapping_jacobian_variation, context.mapping_jacobian, mass_tensor_variation);
mass_tensor_variation += right_jacobian_variation;
mass_tensor_variation *= 1 / determinant;
mass_tensor_variation -= determinant_correction;
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
double gram{0.0};
double gramVariation{0.0};
for (int inner = 0; inner < dimension; ++inner) {
gram += jacobian(inner, row) * jacobian(inner, column);
gramVariation += jacobian(inner, row) * jacobianVariation(inner, column) +
jacobianVariation(inner, row) * jacobian(inner, column);
}
mass_tensor_variation(row, column) = inverseDeterminant * (gramVariation - determinantScale * gram);
}
}
}
} // namespace mean_field::mapping
} // namespace mean_field::mapping

View File

@@ -1,8 +1,12 @@
module;
#include <cmath>
#include <format>
#include <numbers>
#include <stdexcept>
#include <utility>
module mean_field;
import :model.structure.polytropic;
namespace mean_field::models::structure {
@@ -24,64 +28,18 @@ namespace mean_field::models::structure {
}
StructureSeed PolytropicStructure::makeInitialSeed(const StructureSeedRequest &request) const {
validateSeedRequest(request);
const seed::RadialProfile profile = seed::generateLaneEmdenProfile(
m_equationOfState, dimensions::DensityValue{request.centralDensity}, request.radialSampleCount
);
const double polytropicIndex = m_equationOfState.polytropic_index();
const std::vector<LaneEmdenPoint> laneEmdenSolution = solveLaneEmden(polytropicIndex);
const double surfaceCoordinate = laneEmdenSolution.back().coordinate;
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);
if (!std::isfinite(radialScaleSquared) || radialScaleSquared <= 0.0) {
throw std::runtime_error(
"The polytropic Lane-Emden radial scale is not "
"finite and positive."
);
}
const double radialScale = std::sqrt(radialScaleSquared);
StructureSeed seed;
seed.radius.SetSize(request.radialSampleCount);
seed.density.SetSize(request.radialSampleCount);
seed.enthalpy.SetSize(request.radialSampleCount);
seed.stellarRadius = radialScale * surfaceCoordinate;
seed.centralDensity = request.centralDensity;
seed.centralEnthalpy = centralEnthalpy;
std::size_t interpolationIndex = 0;
for (int sampleIndex = 0; sampleIndex < request.radialSampleCount; ++sampleIndex) {
const double sampleFraction =
static_cast<double>(sampleIndex) / static_cast<double>(request.radialSampleCount - 1);
const double dimensionlessRadius = sampleFraction * surfaceCoordinate;
const double laneEmdenValue =
interpolateLaneEmdenValue(laneEmdenSolution, dimensionlessRadius, interpolationIndex);
const double density = request.centralDensity * std::pow(laneEmdenValue, polytropicIndex);
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;
seed.density(0) = request.centralDensity;
seed.enthalpy(0) = centralEnthalpy;
const int surfaceIndex = request.radialSampleCount - 1;
seed.radius(surfaceIndex) = seed.stellarRadius;
seed.density(surfaceIndex) = 0.0;
seed.enthalpy(surfaceIndex) = 0.0;
return seed;
return {
.radius = profile.radius,
.density = profile.density,
.enthalpy = profile.specificEnthalpy,
.stellarRadius = profile.stellarRadius.value(),
.centralDensity = profile.centralDensity.value(),
.centralEnthalpy = profile.centralSpecificEnthalpy.value()
};
}
void PolytropicStructure::validate() const {
@@ -90,8 +48,7 @@ namespace mean_field::models::structure {
if (!std::isfinite(polytropicIndex) || polytropicIndex < 1.0 || polytropicIndex >= 5.0) {
throw std::invalid_argument(
std::format(
"PolytropicStructure requires a finite-radius "
"polytrope with 1 <= n < 5. Instead n = {} was "
"PolytropicStructure requires a finite-radius polytrope with 1 <= n < 5. Instead n = {} was "
"provided.",
polytropicIndex
)
@@ -101,163 +58,10 @@ namespace mean_field::models::structure {
if (!std::isfinite(m_targetMass) || m_targetMass <= 0.0) {
throw std::invalid_argument(
std::format(
"The target stellar mass must be finite and "
"positive. Instead a value of {} was provided.",
"The target stellar mass must be finite and positive. Instead a value of {} was provided.",
m_targetMass
)
);
}
}
void PolytropicStructure::validateSeedRequest(const StructureSeedRequest &request) {
if (!std::isfinite(request.centralDensity) || request.centralDensity <= 0.0) {
throw std::invalid_argument(
std::format(
"The seed central density must be finite and "
"positive. Instead a value of {} was provided.",
request.centralDensity
)
);
}
if (request.radialSampleCount < 2) {
throw std::invalid_argument(
std::format(
"A polytropic seed requires at least two radial "
"samples. Instead {} samples were requested.",
request.radialSampleCount
)
);
}
}
PolytropicStructure::LaneEmdenDerivative PolytropicStructure::evaluateLaneEmdenRhs(
const double coordinate,
const double value,
const double derivative,
const double polytropicIndex
) {
const double nonnegativeValue = std::max(value, 0.0);
return {
.value = derivative,
.derivative = -2.0 * derivative / coordinate - std::pow(nonnegativeValue, polytropicIndex)
};
}
PolytropicStructure::LaneEmdenPoint PolytropicStructure::takeLaneEmdenStep(
const LaneEmdenPoint &point,
const double step,
const double polytropicIndex
) {
const LaneEmdenDerivative first =
evaluateLaneEmdenRhs(point.coordinate, point.value, point.derivative, polytropicIndex);
const LaneEmdenDerivative second = evaluateLaneEmdenRhs(
point.coordinate + 0.5 * step, point.value + 0.5 * step * first.value,
point.derivative + 0.5 * step * first.derivative, polytropicIndex
);
const LaneEmdenDerivative third = evaluateLaneEmdenRhs(
point.coordinate + 0.5 * step, point.value + 0.5 * step * second.value,
point.derivative + 0.5 * step * second.derivative, polytropicIndex
);
const LaneEmdenDerivative fourth = evaluateLaneEmdenRhs(
point.coordinate + step, point.value + step * third.value, point.derivative + step * third.derivative,
polytropicIndex
);
return {
.coordinate = point.coordinate + step,
.value = point.value + step / 6.0 * (first.value + 2.0 * second.value + 2.0 * third.value + fourth.value),
.derivative =
point.derivative +
step / 6.0 * (first.derivative + 2.0 * second.derivative + 2.0 * third.derivative + fourth.derivative)
};
}
std::vector<PolytropicStructure::LaneEmdenPoint> PolytropicStructure::solveLaneEmden(const double polytropicIndex) {
constexpr double initialCoordinate = 1.0e-6;
constexpr double integrationStep = 1.0e-3;
constexpr int maximumStepCount = 2'000'000;
const double coordinateSquared = initialCoordinate * initialCoordinate;
const double coordinateCubed = coordinateSquared * initialCoordinate;
const double coordinateFourth = coordinateSquared * coordinateSquared;
LaneEmdenPoint point{
.coordinate = initialCoordinate,
.value = 1.0 - coordinateSquared / 6.0 + polytropicIndex * coordinateFourth / 120.0,
.derivative = -initialCoordinate / 3.0 + polytropicIndex * coordinateCubed / 30.0
};
std::vector<LaneEmdenPoint> solution;
solution.reserve(8192);
solution.push_back({.coordinate = 0.0, .value = 1.0, .derivative = 0.0});
solution.push_back(point);
for (int stepIndex = 0; stepIndex < maximumStepCount; ++stepIndex) {
LaneEmdenPoint nextPoint = takeLaneEmdenStep(point, integrationStep, polytropicIndex);
if (!std::isfinite(nextPoint.value)) {
throw std::runtime_error(
"The Lane-Emden integration produced a non-finite "
"solution before reaching the stellar surface."
);
}
if (nextPoint.value <= 0.0) {
const double rootFraction = point.value / (point.value - nextPoint.value);
solution.push_back(
{.coordinate = point.coordinate + rootFraction * (nextPoint.coordinate - point.coordinate),
.value = 0.0,
.derivative = point.derivative + rootFraction * (nextPoint.derivative - point.derivative)}
);
return solution;
}
solution.push_back(nextPoint);
point = nextPoint;
}
throw std::runtime_error(
"The Lane-Emden integration did not reach its first zero "
"within the configured step limit."
);
}
double PolytropicStructure::interpolateLaneEmdenValue(
const std::vector<LaneEmdenPoint> &solution,
const double coordinate,
std::size_t &lowerIndex
) {
while (lowerIndex + 1 < solution.size() && solution[lowerIndex + 1].coordinate < coordinate) {
++lowerIndex;
}
if (lowerIndex + 1 >= solution.size()) {
return 0.0;
}
const LaneEmdenPoint &lower = solution[lowerIndex];
const LaneEmdenPoint &upper = solution[lowerIndex + 1];
const double interval = upper.coordinate - lower.coordinate;
if (interval <= 0.0) {
throw std::runtime_error(
"The Lane-Emden interpolation grid is not strictly "
"increasing."
);
}
const double fraction = (coordinate - lower.coordinate) / interval;
return std::clamp(lower.value + fraction * (upper.value - lower.value), 0.0, 1.0);
}
}; // namespace mean_field::models::structure
} // namespace mean_field::models::structure

View File

@@ -3,7 +3,6 @@ module;
module mean_field;
import :operators.gravity_field_jacobian;
import :operators.kernels.gravity_field;
import :utils.blocks;
namespace {
@@ -202,7 +201,6 @@ namespace mean_field::operators {
const context::gravity_field::GravityFieldGeometryContext &geometry_context =
m_linearization_context.GetGeometryContext();
const mfem::Vector &density = m_linearization_context.GetDensityTrue();
const mfem::Vector &displacement = geometry_context.GetDisplacementTrue();
const mfem::Vector &gravity_gradient = m_linearization_context.GetGravityGradientTrue();
const mfem::Vector density_direction = make_read_only_value_view(direction, m_state_offsets, density_block);
@@ -244,14 +242,13 @@ namespace mean_field::operators {
geometry_context.GetMassOperator().Mult(gravity_gradient_direction, gravity_gradient_action);
geometry_context.GetSourceOperator().Mult(density_direction, source_action);
kernels::apply_mapped_hdiv_mass_variation(
m_fem, m_domain_mapper, gravity_gradient, displacement, displacement_direction_true,
mass_variation_action_true
geometry_context.GetMassOperator().MultDisplacementVariationTrue(
gravity_gradient, displacement_direction_true, mass_variation_action_true
);
flux_map.gather(mass_variation_action_true, mass_variation_action);
kernels::apply_mapped_source_variation(
m_fem, m_domain_mapper, density, displacement, displacement_direction_true, source_variation_action_true
geometry_context.GetSourceOperator().MultDisplacementVariationTrue(
density, displacement_direction_true, source_variation_action_true
);
potential_map.gather(source_variation_action_true, source_variation_action);

View File

@@ -12,10 +12,11 @@ import :field.registry;
import :utils.domain;
namespace {
namespace eos = mean_field::eos;
namespace dimensions = mean_field::dimensions;
namespace eos = mean_field::eos;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using ClosureDomain = mean_field::field::FieldDomainT<mean_field::field::Density>;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using ClosureDomain = mean_field::field::FieldDomainT<mean_field::field::Density>;
enum class ClosureAction { residual, density, enthalpy };
@@ -339,7 +340,9 @@ namespace {
const double density = elementDensityInput * densityShape;
const double equationOfStateDensity =
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{baseEnthalpy})
eos::evaluate<dimensions::quantity::Density>(
barotrope, dimensions::SpecificEnthalpyValue{baseEnthalpy}
)
.value();
integrand = density - equationOfStateDensity;
@@ -348,7 +351,7 @@ namespace {
const double densityDerivative =
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{baseEnthalpy}
barotrope, dimensions::SpecificEnthalpyValue{baseEnthalpy}
)
.value();
@@ -640,16 +643,18 @@ 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 equationOfStateDensity =
eos::evaluate<eos::quantity::Density>(barotrope, eos::SpecificEnthalpyValue{enthalpyValue}).value();
const double equationOfStateDensity = eos::evaluate<dimensions::quantity::Density>(
barotrope, dimensions::SpecificEnthalpyValue{enthalpyValue}
)
.value();
const double closureValue = densityValue - equationOfStateDensity;
const double closureValue = densityValue - equationOfStateDensity;
const double geometryActionValue = closureValue * mappingVariation.weight_variation;
const double geometryActionValue = closureValue * mappingVariation.weight_variation;
MFEM_VERIFY(
std::isfinite(closureValue) && std::isfinite(geometryActionValue),

View File

@@ -490,6 +490,8 @@ namespace mean_field::operators::kernels {
mfem::DenseMatrix gravity_gradient_shape;
mfem::DenseMatrix mass_tensor_variation;
mapping::VolumeMappingContext mapping_context;
mapping::VolumeMappingVariation mapping_variation;
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
const mfem::FiniteElement &gravity_gradient_element = *f.gravityFluxFes->GetFE(element_id);
@@ -555,7 +557,6 @@ namespace mean_field::operators::kernels {
const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q);
transformation->SetIntPoint(&integration_point);
mapping::VolumeMappingContext mapping_context;
const mapping::MappingStatus status = domain_mapper.EvaluateVolume(
mapping_data, *transformation, integration_point, workspace, mapping_context
);
@@ -568,7 +569,6 @@ namespace mean_field::operators::kernels {
<< ", status: " << static_cast<int>(status)
);
mapping::VolumeMappingVariation mapping_variation;
const mapping::MappingStatus variation_status = domain_mapper.EvaluateVolumeVariation(
mapping_data, displacement_variation_data, *transformation, integration_point, mapping_context,
workspace, mapping_variation

View File

@@ -12,9 +12,10 @@ module mean_field;
import :operators.kernels.pressure_force;
namespace {
namespace eos = mean_field::eos;
namespace dimensions = mean_field::dimensions;
namespace eos = mean_field::eos;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
@@ -416,14 +417,15 @@ namespace {
if (pressureForceAction == PressureForceAction::residual ||
pressureForceAction == PressureForceAction::displacement) {
pressureFactor =
eos::evaluate<eos::quantity::Pressure>(barotrope, eos::SpecificEnthalpyValue{enthalpyValue})
.value();
pressureFactor = eos::evaluate<dimensions::quantity::Pressure>(
barotrope, dimensions::SpecificEnthalpyValue{enthalpyValue}
)
.value();
} else {
const double enthalpyVariationValue = elementEnthalpyVariation * enthalpyShape;
pressureFactor = eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, eos::SpecificEnthalpyValue{enthalpyValue}
barotrope, dimensions::SpecificEnthalpyValue{enthalpyValue}
)
.value() *
enthalpyVariationValue;

View File

@@ -287,7 +287,6 @@ namespace mean_field::operators {
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseDensity;
@@ -311,18 +310,19 @@ namespace mean_field::operators {
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
data.elementId = elementId;
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
data.enthalpyDofTransformation = m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, displacementDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, compactificationDofs);
baseDensityLocal.GetSubVector(data.densityDofs, elementBaseDensity);
baseEnthalpyLocal.GetSubVector(data.enthalpyDofs, elementBaseEnthalpy);
displacementLocal.GetSubVector(displacementDofs, elementDisplacement);
displacementLocal.GetSubVector(data.displacementDofs, elementDisplacement);
m_fem.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (data.densityDofTransformation != nullptr) {
@@ -331,8 +331,8 @@ namespace mean_field::operators {
if (data.enthalpyDofTransformation != nullptr) {
data.enthalpyDofTransformation->InvTransformPrimal(elementBaseEnthalpy);
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(elementDisplacement);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementDisplacement);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
@@ -363,6 +363,9 @@ namespace mean_field::operators {
data.densityBasis.SetSize(quadraturePointCount, densityDofCount);
data.enthalpyBasis.SetSize(quadraturePointCount, enthalpyDofCount);
data.inverseElementJacobians.SetSize(
quadraturePointCount, m_fem.mesh->Dimension() * m_fem.mesh->Dimension()
);
data.weightedResidual.SetSize(quadraturePointCount);
data.quadratureWeights.SetSize(quadraturePointCount);
data.weightedEnthalpyDerivative.SetSize(quadraturePointCount);
@@ -388,6 +391,18 @@ namespace mean_field::operators {
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
);
MFEM_VERIFY(
!mappingContext.mapping.compactified,
"Prepared barotropic closure support unexpectedly includes a compactified element."
);
for (int row = 0; row < m_fem.mesh->Dimension(); ++row) {
for (int column = 0; column < m_fem.mesh->Dimension(); ++column) {
data.inverseElementJacobians(quadraturePoint, row * m_fem.mesh->Dimension() + column) =
mappingContext.quadrature.J_inv(row, column);
}
}
densityElement.CalcShape(integrationPoint, densityShape);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
@@ -401,7 +416,7 @@ namespace mean_field::operators {
const double density = elementBaseDensity * densityShape;
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
const double quadratureWeight = mappingContext.quadrature.weight;
const eos::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const dimensions::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double eosDensity =
eos::evaluate<eos::quantity::Density>(m_equationOfState, specificEnthalpy).value();
const double enthalpyDerivative =
@@ -485,10 +500,7 @@ namespace mean_field::operators {
ApplyThermodynamicActionFull(m_densityVariationTrue, m_enthalpyVariationTrue, m_fullThermodynamicAction);
kernels::apply_barotropic_closure_displacement_action(
m_fem, m_domainMapper, m_equationOfState, m_baseDensityTrue, m_baseEnthalpyTrue, m_baseDisplacementTrue,
m_displacementVariationTrue, m_fullDisplacementAction
);
ApplyDisplacementActionFull(m_displacementVariationTrue, m_fullDisplacementAction);
MFEM_VERIFY(
m_fullThermodynamicAction.Size() == m_densityMap.full_size() &&
@@ -589,6 +601,86 @@ namespace mean_field::operators {
local_to_true(*m_fem.densityFes, localAction, actionTrue);
}
void PreparedBarotropicClosureOperator::ApplyDisplacementActionFull(
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
) const {
MFEM_VERIFY(
displacementVariationTrue.Size() == m_displacementMap.full_size(),
"The full displacement variation has the wrong size."
);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacementVariationLocal);
m_localDisplacementAction.SetSize(m_fem.densityFes->GetVSize());
m_localDisplacementAction = 0.0;
const int dimension = m_fem.mesh->Dimension();
for (const ElementPAData &data : m_elements) {
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
}
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
const mfem::DenseMatrix &directionDofs = directionData.GetDofMatrix();
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
MFEM_VERIFY(
transformation != nullptr,
"Prepared barotropic closure displacement action received a null element transformation."
);
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(data.elementId);
const mfem::FiniteElement &enthalpyElement = *m_fem.enthalpyFes->GetFE(data.elementId);
const mfem::IntegrationRule &integrationRule =
get_eos_rule(m_fem, m_equationOfState, densityElement, enthalpyElement, *transformation);
MFEM_VERIFY(
data.inverseElementJacobians.Height() == integrationRule.GetNPoints() &&
data.inverseElementJacobians.Width() == dimension * dimension,
"Prepared barotropic closure inverse-Jacobian data has an incompatible size."
);
m_referenceDShape.SetSize(displacementElement.GetDof(), dimension);
m_referenceDisplacementJacobian.SetSize(dimension, dimension);
m_quadratureDisplacementAction.SetSize(integrationRule.GetNPoints());
for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadraturePoint);
displacementElement.CalcDShape(integrationPoint, m_referenceDShape);
mfem::MultAtB(directionDofs, m_referenceDShape, m_referenceDisplacementJacobian);
double logarithmicJacobianVariation{0.0};
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
logarithmicJacobianVariation +=
data.inverseElementJacobians(quadraturePoint, row * dimension + column) *
m_referenceDisplacementJacobian(column, row);
}
}
m_quadratureDisplacementAction(quadraturePoint) =
data.weightedResidual(quadraturePoint) * logarithmicJacobianVariation;
MFEM_VERIFY(
std::isfinite(m_quadratureDisplacementAction(quadraturePoint)),
"Prepared barotropic closure displacement action encountered a non-finite quadrature value."
);
}
m_elementDisplacementAction.SetSize(data.densityDofs.Size());
data.densityBasis.MultTranspose(m_quadratureDisplacementAction, m_elementDisplacementAction);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->TransformDual(m_elementDisplacementAction);
}
m_localDisplacementAction.AddElementVector(data.densityDofs, m_elementDisplacementAction);
}
local_to_true(*m_fem.densityFes, m_localDisplacementAction, actionTrue);
}
bool PreparedBarotropicClosureOperator::IsPrepared() const noexcept {
return m_isPrepared && m_context.IsPrepared();
}

View File

@@ -0,0 +1,223 @@
module;
#include <array>
#include <cmath>
#include <cstdint>
#include <memory>
#include <utility>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_central_density_stellar_equilibrium;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using PhysicalForm = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
using BorderedForm = mean_field::operators::CentralDensityStellarEquilibriumForm;
[[nodiscard]] std::array<
int,
BorderedForm::value_block_count>
make_value_sizes(const mean_field::operators::StellarEquilibriumLayout &physicalLayout) {
std::array<int, BorderedForm::value_block_count> sizes{};
for (int block = 0; block < PhysicalForm::value_block_count; ++block) {
sizes[block] = physicalLayout.value_offsets()[block + 1] - physicalLayout.value_offsets()[block];
}
sizes[PhysicalForm::value_block_count] = 1;
return sizes;
}
[[nodiscard]] std::array<
int,
BorderedForm::residual_block_count>
make_residual_sizes(const mean_field::operators::StellarEquilibriumLayout &physicalLayout) {
std::array<int, BorderedForm::residual_block_count> sizes{};
for (int block = 0; block < PhysicalForm::residual_block_count; ++block) {
sizes[block] = physicalLayout.residual_offsets()[block + 1] - physicalLayout.residual_offsets()[block];
}
sizes[PhysicalForm::residual_block_count] = 1;
return sizes;
}
[[nodiscard]] mean_field::operators::CentralDensityDependencies
make_phase_dependencies(const mean_field::operators::StellarEquilibriumDependencies &dependencies) {
return {.enthalpy = {.identity = dependencies.enthalpy.identity, .revision = dependencies.enthalpy.revision}};
}
void validate_finite_scalar(
const double value,
const char *message
) {
MFEM_VERIFY(std::isfinite(value), message);
}
} // namespace
namespace mean_field::operators {
field::FieldPointDofMap PreparedCentralDensityStellarEquilibriumOperator::MakeCenterDofMap(const fem::FEM &f) {
MFEM_VERIFY(
f.mesh != nullptr && f.enthalpyFes != nullptr,
"The central-density phase requires the mesh and enthalpy finite-element space."
);
const field::FieldDofMap enthalpyMap = field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes);
mfem::Vector origin(f.mesh->SpaceDimension());
origin = 0.0;
return field::make_field_point_dof_map<field::Enthalpy>(*f.enthalpyFes, enthalpyMap, origin, 1.0e-12);
}
PreparedCentralDensityStellarEquilibriumOperator::PreparedCentralDensityStellarEquilibriumOperator(
fem::FEM &f,
std::unique_ptr<PreparedStellarEquilibriumOperator> physicalOperator,
models::CompiledFixedCentralDensity centralDensity,
field::FieldPointDofMap centerDof
)
: mfem::Operator(
physicalOperator->Height() + 1,
physicalOperator->Width() + 1
),
m_physicalOperator(std::move(physicalOperator)),
m_centralDensity(std::move(centralDensity)),
m_phaseConstraint(
std::move(centerDof),
f.mesh->GetComm()
),
m_rootManifest(
make_value_sizes(m_physicalOperator->GetLayout()),
make_residual_sizes(m_physicalOperator->GetLayout()),
m_physicalOperator->GetTargetMass(),
m_physicalOperator->GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure,
m_physicalOperator->GetSurfaceConstraintOperator().GetSurfaceRows().size(),
CentralDensityManifestInput{
.targetDensity = m_centralDensity.targetDensity().value(),
.targetEnthalpy = m_centralDensity.targetEnthalpy().value(),
.centerDofCount = 1
}
) {
MFEM_VERIFY(
Width() == m_rootManifest.layout().value_offsets().Last() &&
Height() == m_rootManifest.layout().residual_offsets().Last(),
"The central-density bordered root has inconsistent dimensions."
);
}
PreparedCentralDensityStellarEquilibriumReport PreparedCentralDensityStellarEquilibriumOperator::Prepare(
const mfem::Vector &state,
const StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
MFEM_VERIFY(state.Size() == Width(), "The central-density bordered root received a state with the wrong size.");
const auto stateView = m_rootManifest.stateView(state);
const mfem::Vector enthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term);
const mfem::Vector border = stateView.block(utils::blocks::fixed_central_density_phase.central_value_term);
validate_finite_scalar(border(0), "The central-density bordered root received a non-finite border value.");
mfem::Vector physicalState(const_cast<mfem::real_t *>(state.GetData()), m_physicalOperator->Width());
m_isPrepared = false;
PreparedCentralDensityStellarEquilibriumReport report;
report.physical = m_physicalOperator->Prepare(physicalState, dependencies, rotation);
report.phase =
m_phaseConstraint.Prepare(m_centralDensity, enthalpy, border(0), make_phase_dependencies(dependencies));
if (report.physical.assembledResidual || report.phase.DidAnyWork() || m_cachedResidual.Size() != Height()) {
AssembleResidual();
report.assembledResidual = true;
}
m_isPrepared = true;
return report;
}
void PreparedCentralDensityStellarEquilibriumOperator::AssembleResidual() {
mfem::Vector physicalResidual;
m_physicalOperator->BuildResidual(physicalResidual);
m_cachedResidual.SetSize(Height());
m_cachedResidual = 0.0;
mfem::Vector physicalDestination(m_cachedResidual.GetData(), physicalResidual.Size());
physicalDestination = physicalResidual;
const auto residualView = m_rootManifest.residualView(m_cachedResidual);
mfem::Vector enthalpyResidual = residualView.block(utils::blocks::enthalpy_field.specific_term);
mfem::Vector phaseResidual = residualView.block(utils::blocks::fixed_central_density_phase.central_value_term);
m_phaseConstraint.AddResidual(enthalpyResidual, phaseResidual);
}
void PreparedCentralDensityStellarEquilibriumOperator::BuildResidual(mfem::Vector &residual) const {
VerifyPrepared();
residual = m_cachedResidual;
}
void PreparedCentralDensityStellarEquilibriumOperator::Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
direction.Size() == Width(), "The central-density bordered root received a direction with the wrong size."
);
const auto directionView = m_rootManifest.directionView(direction);
const mfem::Vector enthalpyDirection = directionView.block(utils::blocks::enthalpy_field.specific_term);
const mfem::Vector borderDirection =
directionView.block(utils::blocks::fixed_central_density_phase.central_value_term);
validate_finite_scalar(
borderDirection(0), "The central-density bordered root received a non-finite border direction."
);
mfem::Vector physicalDirection(const_cast<mfem::real_t *>(direction.GetData()), m_physicalOperator->Width());
mfem::Vector physicalAction;
m_physicalOperator->Mult(physicalDirection, physicalAction);
action.SetSize(Height());
action = 0.0;
mfem::Vector physicalDestination(action.GetData(), physicalAction.Size());
physicalDestination = physicalAction;
const auto actionView = m_rootManifest.residualView(action);
mfem::Vector enthalpyAction = actionView.block(utils::blocks::enthalpy_field.specific_term);
mfem::Vector phaseAction = actionView.block(utils::blocks::fixed_central_density_phase.central_value_term);
m_phaseConstraint.ApplyJacobian(
{.enthalpyVariation = enthalpyDirection, .borderVariation = borderDirection(0)},
{.enthalpyAction = enthalpyAction, .phaseAction = phaseAction}
);
}
bool PreparedCentralDensityStellarEquilibriumOperator::IsPrepared() const noexcept {
return m_isPrepared && m_physicalOperator->IsPrepared() && m_phaseConstraint.IsPrepared();
}
const CentralDensityStellarEquilibriumLayout &
PreparedCentralDensityStellarEquilibriumOperator::GetLayout() const noexcept {
return m_rootManifest.layout();
}
const CentralDensityStellarEquilibriumRootManifest &
PreparedCentralDensityStellarEquilibriumOperator::GetRootManifest() const noexcept {
return m_rootManifest;
}
const PreparedStellarEquilibriumOperator &
PreparedCentralDensityStellarEquilibriumOperator::GetPhysicalOperator() const noexcept {
return *m_physicalOperator;
}
const PreparedCentralDensityConstraint &
PreparedCentralDensityStellarEquilibriumOperator::GetCentralDensityConstraint() const noexcept {
return m_phaseConstraint;
}
RootConstraintReport PreparedCentralDensityStellarEquilibriumOperator::GetFixedMassReport() const {
VerifyPrepared();
return m_physicalOperator->GetFixedMassReport();
}
CentralDensityConstraintReport PreparedCentralDensityStellarEquilibriumOperator::GetCentralDensityReport() const {
VerifyPrepared();
return m_phaseConstraint.GetConstraintReport();
}
void PreparedCentralDensityStellarEquilibriumOperator::VerifyPrepared() const {
MFEM_VERIFY(IsPrepared(), "The central-density bordered root must be prepared before application.");
}
} // namespace mean_field::operators

View File

@@ -8,6 +8,8 @@ import :operators.kernels.gravity_displacement_force;
import :operators.prepared_gravity_displacement_force;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool relevant_revisions_match(
const mean_field::operators::context::gravity_field::GravityFieldRevisions &left,
const mean_field::operators::context::gravity_field::GravityFieldRevisions &right
@@ -15,6 +17,71 @@ namespace {
return left.discretization == right.discretization && left.displacement == right.displacement &&
left.density == right.density && left.gravity_gradient == right.gravity_gradient;
}
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
} else {
localVector = trueVector;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
} else {
trueVector = localVector;
}
}
[[nodiscard]] int vector_dof_index(
const mfem::Ordering::Type ordering,
const int scalarDof,
const int component,
const int scalarDofCount,
const int dimension
) {
if (ordering == mfem::Ordering::byNODES) {
return scalarDof + component * scalarDofCount;
}
MFEM_VERIFY(ordering == mfem::Ordering::byVDIM, "Unsupported displacement ordering.");
return scalarDof * dimension + component;
}
[[nodiscard]] const mfem::IntegrationRule &get_gravity_force_rule(
const mean_field::fem::FEM &f,
const mfem::ElementTransformation &transformation
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
const mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::GravityForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
rule.integration_rule != nullptr,
"The quadrature policy did not return a gravity-displacement-force integration rule."
);
return *rule.integration_rule;
}
} // namespace
namespace mean_field::operators {
@@ -41,6 +108,122 @@ namespace mean_field::operators {
);
}
void PreparedGravityDisplacementForceOperator::PrepareElementData() {
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
mfem::Vector baseDensityLocal;
mfem::Vector baseGravityGradientLocal;
mfem::Vector baseDisplacementLocal;
true_to_local(*m_fem.densityFes, m_gravityContext.GetDensityTrue(), baseDensityLocal);
true_to_local(*m_fem.gravityFluxFes, m_gravityContext.GetGravityGradientTrue(), baseGravityGradientLocal);
true_to_local(
*m_fem.displacementFes, m_gravityContext.GetGeometryContext().GetDisplacementTrue(), baseDisplacementLocal
);
mapping::DomainMapper::Workspace workspace(m_domainMapper.GetDimension());
mapping::VolumeMappingContext mappingContext;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseDensity;
mfem::Vector elementBaseGravityGradient;
mfem::Vector elementBaseDisplacement;
mfem::Vector elementCompactification;
mfem::Vector densityShape;
mfem::Vector baseGravityReferenceValue;
mfem::DenseMatrix gravityGradientShape;
const int dimension = m_domainMapper.GetDimension();
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr, "Prepared gravity force received a null transformation.");
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
}
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
data.elementId = elementId;
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
data.gravityGradientDofTransformation =
m_fem.gravityFluxFes->GetElementVDofs(elementId, data.gravityGradientDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, compactificationDofs);
baseDensityLocal.GetSubVector(data.densityDofs, elementBaseDensity);
baseGravityGradientLocal.GetSubVector(data.gravityGradientDofs, elementBaseGravityGradient);
baseDisplacementLocal.GetSubVector(data.displacementDofs, elementBaseDisplacement);
m_fem.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(elementBaseDensity);
}
if (data.gravityGradientDofTransformation != nullptr) {
data.gravityGradientDofTransformation->InvTransformPrimal(elementBaseGravityGradient);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementBaseDisplacement);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId);
const mfem::FiniteElement &gravityGradientElement = *m_fem.gravityFluxFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId);
data.integrationRule = &get_gravity_force_rule(m_fem, *transformation);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementBaseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData, .compactification = compactificationData
};
const int quadraturePointCount = data.integrationRule->GetNPoints();
data.mappingJacobians.SetSize(quadraturePointCount, dimension * dimension);
data.inverseMeshJacobians.SetSize(quadraturePointCount, dimension * dimension);
data.baseGravityReferenceValues.SetSize(quadraturePointCount, dimension);
data.baseDensityValues.SetSize(quadraturePointCount);
data.referenceWeights.SetSize(quadraturePointCount);
densityShape.SetSize(densityElement.GetDof());
gravityGradientShape.SetSize(gravityGradientElement.GetDof(), dimension);
baseGravityReferenceValue.SetSize(dimension);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid && !mappingContext.mapping.compactified,
"Prepared gravity force encountered an invalid stellar mapping."
);
densityElement.CalcShape(integrationPoint, densityShape);
gravityGradientElement.CalcVShape(*transformation, gravityGradientShape);
gravityGradientShape.MultTranspose(elementBaseGravityGradient, baseGravityReferenceValue);
data.baseDensityValues(quadraturePoint) = elementBaseDensity * densityShape;
data.referenceWeights(quadraturePoint) = integrationPoint.weight * transformation->Weight();
const mfem::DenseMatrix &inverseMeshJacobian = transformation->InverseJacobian();
for (int row = 0; row < dimension; ++row) {
data.baseGravityReferenceValues(quadraturePoint, row) = baseGravityReferenceValue(row);
for (int column = 0; column < dimension; ++column) {
const int entry = row * dimension + column;
data.mappingJacobians(quadraturePoint, entry) =
mappingContext.mapping.mapping_jacobian(row, column);
data.inverseMeshJacobians(quadraturePoint, entry) = inverseMeshJacobian(row, column);
}
}
}
}
}
PreparedGravityDisplacementForceReport PreparedGravityDisplacementForceOperator::Prepare() {
MFEM_VERIFY(
m_gravityContext.IsPrepared(), "PreparedGravityDisplacementForceOperator requires the shared "
@@ -59,6 +242,7 @@ namespace mean_field::operators {
);
m_cachedResidual.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
PrepareElementData();
m_preparedRevisions = requestedRevisions;
++m_residualPreparationCount;
@@ -130,6 +314,117 @@ namespace mean_field::operators {
++m_displacementJacobianStatistics.applications;
}
void PreparedGravityDisplacementForceOperator::ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &gravityGradientVariationTrue,
mfem::Vector &actionTrue
) const {
true_to_local(*m_fem.densityFes, densityVariationTrue, m_densityVariationLocal);
true_to_local(*m_fem.gravityFluxFes, gravityGradientVariationTrue, m_gravityGradientVariationLocal);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacementVariationLocal);
m_localAction.SetSize(m_fem.displacementFes->GetVSize());
m_localAction = 0.0;
const int dimension = m_domainMapper.GetDimension();
const mfem::Ordering::Type ordering = m_fem.displacementFes->GetOrdering();
for (const ElementPAData &data : m_elements) {
MFEM_VERIFY(data.integrationRule != nullptr, "Prepared gravity force has no integration rule.");
m_densityVariationLocal.GetSubVector(data.densityDofs, m_elementDensityVariation);
m_gravityGradientVariationLocal.GetSubVector(data.gravityGradientDofs, m_elementGravityGradientVariation);
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
}
if (data.gravityGradientDofTransformation != nullptr) {
data.gravityGradientDofTransformation->InvTransformPrimal(m_elementGravityGradientVariation);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
}
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(data.elementId);
const mfem::FiniteElement &gravityGradientElement = *m_fem.gravityFluxFes->GetFE(data.elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
MFEM_VERIFY(transformation != nullptr, "Prepared gravity force received a null transformation.");
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
const mfem::DenseMatrix &directionDofs = directionData.GetDofMatrix();
const int scalarDisplacementDofCount = displacementElement.GetDof();
m_densityShape.SetSize(densityElement.GetDof());
m_displacementShape.SetSize(scalarDisplacementDofCount);
m_gravityGradientShape.SetSize(gravityGradientElement.GetDof(), dimension);
m_referenceDisplacementDShape.SetSize(scalarDisplacementDofCount, dimension);
m_referenceDisplacementJacobian.SetSize(dimension, dimension);
m_displacementJacobianVariation.SetSize(dimension, dimension);
m_mappingJacobian.SetSize(dimension, dimension);
m_inverseMeshJacobian.SetSize(dimension, dimension);
m_baseGravityReferenceValue.SetSize(dimension);
m_gravityVariationReferenceValue.SetSize(dimension);
m_mappedBaseGravity.SetSize(dimension);
m_mappedGravityVariation.SetSize(dimension);
m_mappedGeometryVariation.SetSize(dimension);
m_forceValue.SetSize(dimension);
m_elementAction.SetSize(data.displacementDofs.Size());
m_elementAction = 0.0;
for (int quadraturePoint = 0; quadraturePoint < data.integrationRule->GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
densityElement.CalcShape(integrationPoint, m_densityShape);
displacementElement.CalcShape(integrationPoint, m_displacementShape);
displacementElement.CalcDShape(integrationPoint, m_referenceDisplacementDShape);
mfem::MultAtB(directionDofs, m_referenceDisplacementDShape, m_referenceDisplacementJacobian);
transformation->SetIntPoint(&integrationPoint);
gravityGradientElement.CalcVShape(*transformation, m_gravityGradientShape);
m_gravityGradientShape.MultTranspose(
m_elementGravityGradientVariation, m_gravityVariationReferenceValue
);
for (int row = 0; row < dimension; ++row) {
m_baseGravityReferenceValue(row) = data.baseGravityReferenceValues(quadraturePoint, row);
for (int column = 0; column < dimension; ++column) {
const int entry = row * dimension + column;
m_mappingJacobian(row, column) = data.mappingJacobians(quadraturePoint, entry);
m_inverseMeshJacobian(row, column) = data.inverseMeshJacobians(quadraturePoint, entry);
}
}
mfem::Mult(m_referenceDisplacementJacobian, m_inverseMeshJacobian, m_displacementJacobianVariation);
m_mappingJacobian.Mult(m_baseGravityReferenceValue, m_mappedBaseGravity);
m_mappingJacobian.Mult(m_gravityVariationReferenceValue, m_mappedGravityVariation);
m_displacementJacobianVariation.Mult(m_baseGravityReferenceValue, m_mappedGeometryVariation);
const double densityVariationValue = m_elementDensityVariation * m_densityShape;
const double baseDensityValue = data.baseDensityValues(quadraturePoint);
m_forceValue = 0.0;
m_forceValue.Add(densityVariationValue, m_mappedBaseGravity);
m_forceValue.Add(baseDensityValue, m_mappedGravityVariation);
m_forceValue.Add(baseDensityValue, m_mappedGeometryVariation);
m_forceValue *= data.referenceWeights(quadraturePoint);
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof =
vector_dof_index(ordering, scalarDof, component, scalarDisplacementDofCount, dimension);
m_elementAction(vectorDof) += m_displacementShape(scalarDof) * m_forceValue(component);
}
}
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(m_elementAction);
}
m_localAction.AddElementVector(data.displacementDofs, m_elementAction);
}
local_to_true(*m_fem.displacementFes, m_localAction, actionTrue);
}
void PreparedGravityDisplacementForceOperator::ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
@@ -145,10 +440,8 @@ namespace mean_field::operators {
m_gravityContext.GetGravityGradientMap().scatter(gravityGradientVariation, m_gravityGradientVariationTrue);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
kernels::apply_gravity_displacement_force_complete_action(
m_fem, m_domainMapper, m_gravityContext.GetDensityTrue(), m_densityVariationTrue,
m_gravityContext.GetGravityGradientTrue(), m_gravityGradientVariationTrue, m_displacementVariationTrue,
m_gravityContext.GetGeometryContext().GetDisplacementTrue(), m_actionTrue
ApplyPreparedCompleteJacobianActionTrue(
m_densityVariationTrue, m_displacementVariationTrue, m_gravityGradientVariationTrue, m_actionTrue
);
action.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, action);

View File

@@ -174,9 +174,15 @@ namespace {
"non-finite mapping determinant."
);
m_inverse_element_jacobian = mapping_context.quadrature.J_inv;
return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
}
[[nodiscard]] const mfem::DenseMatrix &GetInverseElementJacobian() const noexcept {
return m_inverse_element_jacobian;
}
private:
void LoadElement(const int element_id) {
if (element_id == m_cached_element_id) {
@@ -230,6 +236,7 @@ namespace {
std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data;
mean_field::mapping::DomainMapper::Workspace m_workspace;
mfem::DenseMatrix m_inverse_element_jacobian;
int m_cached_element_id{-1};
};
} // namespace
@@ -336,6 +343,9 @@ namespace mean_field::operators {
data.potential_dof_transformation =
m_fem.gravityPotentialFes->GetElementDofs(element_id, data.potential_dofs);
data.displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(element_id, data.displacement_dofs);
const mfem::FiniteElement &density_element = *m_fem.densityFes->GetFE(element_id);
const mfem::FiniteElement &potential_element = *m_fem.gravityPotentialFes->GetFE(element_id);
@@ -344,6 +354,7 @@ namespace mean_field::operators {
const mfem::IntegrationRule &integration_rule =
get_source_rule(m_fem, density_element, potential_element, transformation);
data.integration_rule = &integration_rule;
const int quadrature_point_count = integration_rule.GetNPoints();
@@ -355,6 +366,9 @@ namespace mean_field::operators {
data.potential_basis.SetSize(quadrature_point_count, potential_dof_count);
const int dimension = m_fem.mesh->Dimension();
data.inverse_element_jacobians.SetSize(quadrature_point_count, dimension * dimension);
data.quadrature_data.SetSize(quadrature_point_count);
mfem::Vector density_shape(density_dof_count);
@@ -381,6 +395,14 @@ namespace mean_field::operators {
const double coefficient_value = source_coefficient.Eval(transformation, integration_point);
const mfem::DenseMatrix &inverse_element_jacobian = source_coefficient.GetInverseElementJacobian();
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
data.inverse_element_jacobians(quadrature_point, row * dimension + column) =
inverse_element_jacobian(row, column);
}
}
transformation.SetIntPoint(&integration_point);
const double quadrature_value = integration_point.weight * transformation.Weight() * coefficient_value;
@@ -463,6 +485,98 @@ namespace mean_field::operators {
m_potential_map.gather(m_action_true, action);
}
void PreparedMappedGravitySourceOperator::MultDisplacementVariationTrue(
const mfem::Vector &densityTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const {
MFEM_VERIFY(
m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before applying a displacement variation."
);
MFEM_VERIFY(
densityTrue.Size() == m_fem.densityFes->GetTrueVSize(), "The full density vector has the wrong size."
);
MFEM_VERIFY(
displacementVariationTrue.Size() == m_fem.displacementFes->GetTrueVSize(),
"The full displacement variation has the wrong size."
);
true_to_local(*m_fem.densityFes, densityTrue, m_density_local);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacement_variation_local);
m_local_variation_action.SetSize(m_fem.gravityPotentialFes->GetVSize());
m_local_variation_action = 0.0;
const int dimension = m_fem.mesh->Dimension();
for (const ElementPAData &data : m_elements) {
MFEM_VERIFY(
data.integration_rule != nullptr,
"Prepared gravity source displacement variation has no integration rule."
);
m_density_local.GetSubVector(data.density_dofs, m_element_density);
m_displacement_variation_local.GetSubVector(data.displacement_dofs, m_element_displacement_variation);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->InvTransformPrimal(m_element_density);
}
if (data.displacement_dof_transformation != nullptr) {
data.displacement_dof_transformation->InvTransformPrimal(m_element_displacement_variation);
}
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(data.element_id);
const mapping::ElementDisplacementData direction_data = mapping::ElementDisplacementDataFromElementVDofs(
displacement_element, m_element_displacement_variation
);
const mfem::DenseMatrix &direction_dofs = direction_data.GetDofMatrix();
MFEM_VERIFY(
data.inverse_element_jacobians.Height() == data.integration_rule->GetNPoints() &&
data.inverse_element_jacobians.Width() == dimension * dimension,
"Prepared gravity source inverse-Jacobian data has an incompatible size."
);
m_reference_displacement_dshape.SetSize(displacement_element.GetDof(), dimension);
m_reference_displacement_jacobian.SetSize(dimension, dimension);
m_quadrature_variation_action.SetSize(data.integration_rule->GetNPoints());
data.density_basis.Mult(m_element_density, m_quadrature_variation_action);
for (int quadrature_point = 0; quadrature_point < data.integration_rule->GetNPoints(); ++quadrature_point) {
const mfem::IntegrationPoint &integration_point = data.integration_rule->IntPoint(quadrature_point);
displacement_element.CalcDShape(integration_point, m_reference_displacement_dshape);
mfem::MultAtB(direction_dofs, m_reference_displacement_dshape, m_reference_displacement_jacobian);
double logarithmic_jacobian_variation{0.0};
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
logarithmic_jacobian_variation +=
data.inverse_element_jacobians(quadrature_point, row * dimension + column) *
m_reference_displacement_jacobian(column, row);
}
}
m_quadrature_variation_action(quadrature_point) *=
data.quadrature_data(quadrature_point) * logarithmic_jacobian_variation;
MFEM_VERIFY(
std::isfinite(m_quadrature_variation_action(quadrature_point)),
"Prepared gravity source displacement variation encountered a non-finite quadrature value."
);
}
m_element_variation_action.SetSize(data.potential_dofs.Size());
data.potential_basis.MultTranspose(m_quadrature_variation_action, m_element_variation_action);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->TransformDual(m_element_variation_action);
}
m_local_variation_action.AddElementVector(data.potential_dofs, m_element_variation_action);
}
local_to_true(*m_fem.gravityPotentialFes, m_local_variation_action, actionVariationTrue);
}
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential,
mfem::Vector &action

View File

@@ -35,6 +35,128 @@ namespace {
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &local_vector,
mfem::Vector &true_vector
) {
true_vector.SetSize(finite_element_space.GetTrueVSize());
true_vector = 0.0;
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector);
} else {
true_vector = local_vector;
}
}
mean_field::quadrature::MappingKind get_mapping_kind(
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::ElementTransformation &transformation
) {
return domain_mapper.IsCompactifiedElement(transformation) ? mean_field::quadrature::MappingKind::kelvin
: mean_field::quadrature::MappingKind::general;
}
const mfem::IntegrationRule &get_hdiv_mass_rule(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::FiniteElement &element,
const mfem::ElementTransformation &transformation
) {
using GravityField = mean_field::field::Field<mean_field::field::Gravity>;
const mean_field::quadrature::Query query =
GravityField::make_query<mean_field::field::Gravity::Form::HDivMass>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {},
mean_field::utils::DOMAINS::ALL, get_mapping_kind(domain_mapper, transformation)
);
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
resolution.integration_rule != nullptr,
"The quadrature policy did not return an H(div) mass integration rule."
);
return *resolution.integration_rule;
}
int frozen_mapping_width(const int dimension) {
return 3 * dimension + 4 * dimension * dimension + 3;
}
void freeze_mapping_context(
const mean_field::mapping::VolumeMappingContext &context,
const int quadrature_point,
mfem::DenseMatrix &data
) {
const int dimension = context.mapping.reference_position.Size();
const int displacement_jacobian_start = 3 * dimension;
const int mapping_jacobian_start = displacement_jacobian_start + dimension * dimension;
const int inverse_mapping_start = mapping_jacobian_start + dimension * dimension;
const int inverse_element_start = inverse_mapping_start + dimension * dimension;
const int scalar_start = inverse_element_start + dimension * dimension;
for (int component = 0; component < dimension; ++component) {
data(quadrature_point, component) = context.mapping.reference_position(component);
data(quadrature_point, dimension + component) = context.mapping.displaced_position(component);
data(quadrature_point, 2 * dimension + component) = context.mapping.physical_position(component);
}
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
const int entry = row * dimension + column;
data(quadrature_point, displacement_jacobian_start + entry) =
context.mapping.displacement_jacobian(row, column);
data(quadrature_point, mapping_jacobian_start + entry) = context.mapping.mapping_jacobian(row, column);
data(quadrature_point, inverse_mapping_start + entry) =
context.mapping.inverse_mapping_jacobian(row, column);
data(quadrature_point, inverse_element_start + entry) = context.quadrature.J_inv(row, column);
}
}
data(quadrature_point, scalar_start) = context.mapping.mapping_determinant;
data(quadrature_point, scalar_start + 1) = context.quadrature.weight;
data(quadrature_point, scalar_start + 2) = context.mapping.compactified ? 1.0 : 0.0;
}
void thaw_mapping_context(
const mfem::DenseMatrix &data,
const int quadrature_point,
const int dimension,
mean_field::mapping::VolumeMappingContext &context
) {
const int displacement_jacobian_start = 3 * dimension;
const int mapping_jacobian_start = displacement_jacobian_start + dimension * dimension;
const int inverse_mapping_start = mapping_jacobian_start + dimension * dimension;
const int inverse_element_start = inverse_mapping_start + dimension * dimension;
const int scalar_start = inverse_element_start + dimension * dimension;
context.mapping.reference_position.SetSize(dimension);
context.mapping.displaced_position.SetSize(dimension);
context.mapping.physical_position.SetSize(dimension);
context.mapping.displacement_jacobian.SetSize(dimension, dimension);
context.mapping.mapping_jacobian.SetSize(dimension, dimension);
context.mapping.inverse_mapping_jacobian.SetSize(dimension, dimension);
context.quadrature.J_inv.SetSize(dimension, dimension);
for (int component = 0; component < dimension; ++component) {
context.mapping.reference_position(component) = data(quadrature_point, component);
context.mapping.displaced_position(component) = data(quadrature_point, dimension + component);
context.mapping.physical_position(component) = data(quadrature_point, 2 * dimension + component);
}
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
const int entry = row * dimension + column;
context.mapping.displacement_jacobian(row, column) =
data(quadrature_point, displacement_jacobian_start + entry);
context.mapping.mapping_jacobian(row, column) = data(quadrature_point, mapping_jacobian_start + entry);
context.mapping.inverse_mapping_jacobian(row, column) =
data(quadrature_point, inverse_mapping_start + entry);
context.quadrature.J_inv(row, column) = data(quadrature_point, inverse_element_start + entry);
}
}
context.mapping.mapping_determinant = data(quadrature_point, scalar_start);
context.mapping.compactified = data(quadrature_point, scalar_start + 2) != 0.0;
context.quadrature.detJ = context.mapping.mapping_determinant;
context.quadrature.weight = data(quadrature_point, scalar_start + 1);
}
int find_representative_element(
const mean_field::fem::FEM &f,
const mfem::Array<int> &marker
@@ -238,7 +360,8 @@ namespace mean_field::operators {
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
),
m_variationWorkspace(domain_mapper.GetDimension()) {
MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh.");
MFEM_VERIFY(
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
@@ -285,6 +408,78 @@ namespace mean_field::operators {
validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id);
}
void PreparedMappedHDivMassOperator::PrepareVariationData() {
m_variationElements.clear();
m_variationElements.reserve(m_fem.mesh->GetNE());
mfem::Vector displacementLocal;
true_to_local(*m_fem.displacementFes, m_displacement_true, displacementLocal);
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mapping::VolumeMappingContext mappingContext;
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
m_variationElements.emplace_back();
ElementVariationData &data = m_variationElements.back();
data.elementId = elementId;
data.gravityGradientDofTransformation =
m_fem.gravityFluxFes->GetElementVDofs(elementId, data.gravityGradientDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, data.compactificationDofs);
displacementLocal.GetSubVector(data.displacementDofs, elementDisplacement);
m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs, elementCompactification);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementDisplacement);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
data.baseDisplacement = elementDisplacement;
data.compactification = elementCompactification;
const mfem::FiniteElement &gravityGradientElement = *m_fem.gravityFluxFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr, "Prepared H(div) variation data received a null element transformation."
);
data.integrationRule = &get_hdiv_mass_rule(m_fem, m_domain_mapper, gravityGradientElement, *transformation);
data.frozenMappingData.SetSize(
data.integrationRule->GetNPoints(), frozen_mapping_width(m_domain_mapper.GetDimension())
);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData, .compactification = compactificationData
};
for (int quadraturePoint = 0; quadraturePoint < data.integrationRule->GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
const mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, m_variationWorkspace, mappingContext
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Prepared H(div) variation data encountered an invalid mapping. Element: "
<< elementId << ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(status)
);
freeze_mapping_context(mappingContext, quadraturePoint, data.frozenMappingData);
}
}
}
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
MFEM_VERIFY(
displacement.Size() == m_displacement_map.reduced_size(),
@@ -346,6 +541,8 @@ namespace mean_field::operators {
m_stellar_mass_form->Assemble();
m_vacuum_mass_form->Assemble();
PrepareVariationData();
m_is_prepared = true;
++m_preparation_count;
}
@@ -378,6 +575,110 @@ namespace mean_field::operators {
m_flux_map.gather(m_action_true, action);
}
void PreparedMappedHDivMassOperator::MultDisplacementVariationTrue(
const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared before applying a displacement variation."
);
MFEM_VERIFY(
gravityGradientTrue.Size() == m_fem.gravityFluxFes->GetTrueVSize(),
"The full gravity-gradient vector has the wrong size."
);
MFEM_VERIFY(
displacementVariationTrue.Size() == m_fem.displacementFes->GetTrueVSize(),
"The full displacement variation has the wrong size."
);
true_to_local(*m_fem.gravityFluxFes, gravityGradientTrue, m_gravityGradientLocal);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacementVariationLocal);
m_localVariationAction.SetSize(m_fem.gravityFluxFes->GetVSize());
m_localVariationAction = 0.0;
const int dimension = m_domain_mapper.GetDimension();
for (const ElementVariationData &data : m_variationElements) {
MFEM_VERIFY(
data.integrationRule != nullptr &&
data.frozenMappingData.Height() == data.integrationRule->GetNPoints() &&
data.frozenMappingData.Width() == frozen_mapping_width(dimension),
"Prepared H(div) variation data is incomplete."
);
m_gravityGradientLocal.GetSubVector(data.gravityGradientDofs, m_elementGravityGradient);
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.gravityGradientDofTransformation != nullptr) {
data.gravityGradientDofTransformation->InvTransformPrimal(m_elementGravityGradient);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
}
const mfem::FiniteElement &gravityGradientElement = *m_fem.gravityFluxFes->GetFE(data.elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
MFEM_VERIFY(
transformation != nullptr,
"Prepared H(div) displacement variation received a null element transformation."
);
const mapping::ElementDisplacementData baseDisplacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = baseDisplacementData, .compactification = compactificationData
};
m_elementVariationAction.SetSize(gravityGradientElement.GetDof());
m_elementVariationAction = 0.0;
m_gravityGradientValue.SetSize(dimension);
m_massTensorVariationAction.SetSize(dimension);
m_gravityGradientShape.SetSize(gravityGradientElement.GetDof(), dimension);
m_massTensorVariation.SetSize(dimension, dimension);
for (int quadraturePoint = 0; quadraturePoint < data.integrationRule->GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
thaw_mapping_context(data.frozenMappingData, quadraturePoint, dimension, m_baseMappingContext);
const mapping::MappingStatus status = m_domain_mapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation, integrationPoint, m_baseMappingContext,
m_variationWorkspace, m_mappingVariation
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Prepared H(div) displacement variation encountered an invalid mapping variation. Element: "
<< data.elementId << ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(status)
);
mapping::ComputeHDivMassTensorVariation(
m_baseMappingContext.mapping, m_mappingVariation.mapping, m_massTensorVariation
);
transformation->SetIntPoint(&integrationPoint);
gravityGradientElement.CalcVShape(*transformation, m_gravityGradientShape);
m_gravityGradientShape.MultTranspose(m_elementGravityGradient, m_gravityGradientValue);
m_massTensorVariation.Mult(m_gravityGradientValue, m_massTensorVariationAction);
const double referenceWeight = integrationPoint.weight * transformation->Weight();
m_gravityGradientShape.AddMult(m_massTensorVariationAction, m_elementVariationAction, referenceWeight);
}
if (data.gravityGradientDofTransformation != nullptr) {
data.gravityGradientDofTransformation->TransformDual(m_elementVariationAction);
}
m_localVariationAction.AddElementVector(data.gravityGradientDofs, m_elementVariationAction);
}
local_to_true(*m_fem.gravityFluxFes, m_localVariationAction, actionVariationTrue);
}
void PreparedMappedHDivMassOperator::AssembleDiagonal(mfem::Vector &diagonal) const {
mfem::Vector true_diagonal;
AssembleTrueDiagonal(true_diagonal);

View File

@@ -992,6 +992,7 @@ namespace mean_field::operators {
mfem::Vector elementDisplacementVariation;
mfem::Vector weightedQuadratureVariation;
mfem::Vector elementAction;
mapping::VolumeMappingVariation variation;
for (const ElementPAData &data : m_elements) {
MFEM_VERIFY(
@@ -1040,11 +1041,7 @@ namespace mean_field::operators {
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
mapping::VolumeMappingVariation variation;
const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolumeVariation(
const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation, integrationPoint,
data.baseMappingContexts[quadraturePoint], workspace, variation
);

View File

@@ -42,6 +42,25 @@ namespace {
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(), "Local vector has the wrong size.");
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
} else {
trueVector = localVector;
}
}
const mfem::IntegrationRule &get_mass_normalization_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &densityElement,
@@ -212,6 +231,13 @@ namespace mean_field::operators {
return report;
}
PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare(
const models::CompiledFixedMass &constraint,
const MassNormalizationDependencies &dependencies
) {
return Prepare({.targetMass = constraint.targetMass().value()}, dependencies);
}
void PreparedMassNormalizationOperator::BuildStaticPlan() {
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
@@ -432,6 +458,7 @@ namespace mean_field::operators {
true_to_local(*m_fem.displacementFes, displacementVariation, displacementVariationLocal);
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mapping::VolumeMappingVariation variation;
mfem::Vector elementDisplacementVariation;
double localAction = 0.0;
@@ -464,8 +491,6 @@ namespace mean_field::operators {
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
for (const QuadraturePointData &point : data.quadraturePoints) {
mapping::VolumeMappingVariation variation;
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation, point.integrationPoint, point.mappingContext,
workspace, variation
@@ -558,6 +583,137 @@ namespace mean_field::operators {
++m_actionStatistics.completeApplications;
}
void PreparedMassNormalizationOperator::ApplyJacobian(
const FixedMassJacobianInput &input,
mfem::Vector &action
) const {
ApplyCompleteJacobianAction(input.densityVariation, input.displacementVariation, action);
}
void PreparedMassNormalizationOperator::AssembleDensityTransposeAction(
const double residualDual,
mfem::Vector &densityDual
) const {
mfem::Vector localDual(m_fem.densityFes->GetVSize());
localDual = 0.0;
mfem::Vector elementDual;
for (const ElementPAData &data : m_elements) {
elementDual.SetSize(data.densityDofs.Size());
elementDual = 0.0;
for (const QuadraturePointData &point : data.quadraturePoints) {
elementDual.Add(residualDual * point.mappingContext.quadrature.weight, point.densityShape);
}
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->TransformDual(elementDual);
}
localDual.AddElementVector(data.densityDofs, elementDual);
}
mfem::Vector trueDual;
local_to_true(*m_fem.densityFes, localDual, trueDual);
densityDual.SetSize(m_gravityContext.GetDensityMap().reduced_size());
m_gravityContext.GetDensityMap().gather(trueDual, densityDual);
}
void PreparedMassNormalizationOperator::AssembleDisplacementTransposeAction(
const double residualDual,
mfem::Vector &displacementDual
) const {
mfem::Vector localDual(m_fem.displacementFes->GetVSize());
localDual = 0.0;
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mapping::VolumeMappingVariation variation;
mfem::Vector elementDirection;
mfem::Vector elementDual;
for (const ElementPAData &data : m_elements) {
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
const mapping::ElementDisplacementData baseDisplacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = baseDisplacementData, .compactification = compactificationData
};
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
elementDirection.SetSize(data.displacementDofs.Size());
elementDual.SetSize(data.displacementDofs.Size());
elementDual = 0.0;
for (int elementDof = 0; elementDof < elementDirection.Size(); ++elementDof) {
elementDirection = 0.0;
elementDirection(elementDof) = 1.0;
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDirection);
double elementDofAction = 0.0;
for (const QuadraturePointData &point : data.quadraturePoints) {
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation, point.integrationPoint, point.mappingContext,
workspace, variation
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Stateless mapping variation failed in the mass-normalization transpose action. Element: "
<< data.elementId << ", status: " << static_cast<int>(status)
);
elementDofAction += point.density * variation.weight_variation;
}
elementDual(elementDof) = residualDual * elementDofAction;
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(elementDual);
}
localDual.AddElementVector(data.displacementDofs, elementDual);
}
mfem::Vector trueDual;
local_to_true(*m_fem.displacementFes, localDual, trueDual);
displacementDual.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(trueDual, displacementDual);
}
void PreparedMassNormalizationOperator::ApplyCompleteJacobianTransposeAction(
const double residualDual,
mfem::Vector &densityDual,
mfem::Vector &displacementDual
) const {
VerifyPrepared();
MFEM_VERIFY(std::isfinite(residualDual), "Mass-normalization transpose action received a non-finite dual.");
AssembleDensityTransposeAction(residualDual, densityDual);
AssembleDisplacementTransposeAction(residualDual, displacementDual);
++m_actionStatistics.transposeApplications;
}
void PreparedMassNormalizationOperator::ApplyJacobianTranspose(
const mfem::Vector &residualDual,
FixedMassJacobianTransposeOutput output
) const {
MFEM_VERIFY(residualDual.Size() == 1, "Fixed-mass transpose action requires one residual dual value.");
ApplyCompleteJacobianTransposeAction(residualDual(0), output.densityDual, output.displacementDual);
}
double PreparedMassNormalizationOperator::GlobalSum(const double localValue) const {
double globalValue = 0.0;
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm());
@@ -720,6 +876,44 @@ namespace mean_field::operators {
action(m_layout.offset(massResidual)) = massAction(0);
}
void PreparedMassNormalizationJacobianOperator::MultTranspose(
const mfem::Vector &residualDual,
mfem::Vector &stateDual
) const {
MFEM_VERIFY(
m_preparedOperator.IsPrepared(),
"Prepared mass-normalization MFEM adapter requires a prepared row operator."
);
MFEM_VERIFY(
residualDual.Size() == Height(),
"Prepared mass-normalization MFEM adapter received a residual dual with the wrong size."
);
using Form = utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue = utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
mfem::Vector densityDual;
mfem::Vector displacementDual;
m_preparedOperator.ApplyCompleteJacobianTransposeAction(
residualDual(m_layout.offset(massResidual)), densityDual, displacementDual
);
stateDual.SetSize(Width());
stateDual = 0.0;
mfem::Vector densityBlock(stateDual.GetData() + m_layout.offset(densityValue), m_layout.size(densityValue));
densityBlock = densityDual;
mfem::Vector displacementBlock(
stateDual.GetData() + m_layout.offset(displacementValue), m_layout.size(displacementValue)
);
displacementBlock = displacementDual;
}
const MassNormalizationLayout &PreparedMassNormalizationJacobianOperator::GetLayout() const noexcept {
return m_layout;
}

View File

@@ -619,7 +619,7 @@ namespace mean_field::operators {
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const double enthalpy = quadratureEnthalpy(quadraturePoint);
const eos::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const dimensions::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double pressure =
eos::evaluate<eos::quantity::Pressure>(m_equationOfState, specificEnthalpy).value();
@@ -813,11 +813,13 @@ namespace mean_field::operators {
localAction = 0.0;
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mfem::Vector elementDisplacementVariation;
mfem::Vector elementAction;
mfem::DenseMatrix referenceDisplacementDShape;
mfem::DenseMatrix referenceDisplacementJacobian;
mfem::DenseMatrix inverseElementJacobianVariation;
mfem::DenseMatrix matrixTemporary;
mfem::DenseMatrix physicalTestGradientVariation;
const int dimension = m_fem.mesh->Dimension();
@@ -850,13 +852,11 @@ namespace mean_field::operators {
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation);
const mapping::ElementMappingData mappingData{
.displacement = *data.baseDisplacementData, .compactification = *data.compactificationData
};
const int quadraturePointCount = data.integrationRule->GetNPoints();
const int quadraturePointCount = data.integrationRule->GetNPoints();
const int scalarDisplacementDofCount = displacementElement.GetDof();
const int scalarDisplacementDofCount = displacementElement.GetDof();
const mfem::DenseMatrix &directionDofs = directionData.GetDofMatrix();
MFEM_VERIFY(
static_cast<int>(data.baseMappingContexts.size()) == quadraturePointCount &&
@@ -869,31 +869,32 @@ namespace mean_field::operators {
elementAction = 0.0;
referenceDisplacementDShape.SetSize(scalarDisplacementDofCount, dimension);
referenceDisplacementJacobian.SetSize(dimension, dimension);
inverseElementJacobianVariation.SetSize(dimension, dimension);
matrixTemporary.SetSize(dimension, dimension);
physicalTestGradientVariation.SetSize(scalarDisplacementDofCount, dimension);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
displacementElement.CalcDShape(integrationPoint, referenceDisplacementDShape);
mfem::MultAtB(directionDofs, referenceDisplacementDShape, referenceDisplacementJacobian);
mapping::VolumeMappingVariation variation;
const mfem::DenseMatrix &inverseElementJacobian =
data.baseMappingContexts[quadraturePoint].quadrature.J_inv;
mfem::Mult(inverseElementJacobian, referenceDisplacementJacobian, matrixTemporary);
const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation, integrationPoint,
data.baseMappingContexts[quadraturePoint], workspace, variation
);
double logarithmicJacobianVariation{0.0};
for (int component = 0; component < dimension; ++component) {
logarithmicJacobianVariation += matrixTemporary(component, component);
}
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"Stateless mapping variation failed while applying "
"the prepared pressure-force displacement Jacobian. "
"Element: "
<< data.elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
);
mfem::Mult(matrixTemporary, inverseElementJacobian, inverseElementJacobianVariation);
inverseElementJacobianVariation *= -1.0;
mfem::Mult(
data.referenceTestGradients[quadraturePoint], variation.inverse_element_jacobian_variation,
data.referenceTestGradients[quadraturePoint], inverseElementJacobianVariation,
physicalTestGradientVariation
);
@@ -905,12 +906,13 @@ namespace mean_field::operators {
displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension
);
const double gradientWeightVariation =
data.quadratureWeights(quadraturePoint) *
physicalTestGradientVariation(scalarDof, component) +
variation.weight_variation * physicalTestGradient(scalarDof, component);
const double gradientWeightVariation = data.quadratureWeights(quadraturePoint) *
physicalTestGradientVariation(scalarDof, component) +
data.quadratureWeights(quadraturePoint) *
logarithmicJacobianVariation *
physicalTestGradient(scalarDof, component);
const double contribution = data.pressure(quadraturePoint) * gradientWeightVariation;
const double contribution = data.pressure(quadraturePoint) * gradientWeightVariation;
MFEM_VERIFY(
std::isfinite(gradientWeightVariation) && std::isfinite(contribution),

View File

@@ -1,5 +1,6 @@
module;
#include <array>
#include <mfem.hpp>
module mean_field;
@@ -7,6 +8,75 @@ module mean_field;
import :operators.kernels.rotational_displacement_force;
import :operators.prepared_rotational_displacement_force;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
} else {
localVector = trueVector;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
} else {
trueVector = localVector;
}
}
[[nodiscard]] int vector_dof_index(
const mfem::Ordering::Type ordering,
const int scalarDof,
const int component,
const int scalarDofCount,
const int dimension
) {
if (ordering == mfem::Ordering::byNODES) {
return scalarDof + component * scalarDofCount;
}
MFEM_VERIFY(ordering == mfem::Ordering::byVDIM, "Unsupported displacement ordering.");
return scalarDof * dimension + component;
}
[[nodiscard]] const mfem::IntegrationRule &get_rotation_force_rule(
const mean_field::fem::FEM &f,
const mfem::ElementTransformation &transformation
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
const mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 1>{1},
mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
rule.integration_rule != nullptr,
"The quadrature policy did not return a rotational-displacement-force integration rule."
);
return *rule.integration_rule;
}
} // namespace
namespace mean_field::operators {
PreparedRotationalDisplacementForceOperator::PreparedRotationalDisplacementForceOperator(
const fem::FEM &f,
@@ -49,6 +119,105 @@ namespace mean_field::operators {
);
}
void PreparedRotationalDisplacementForceOperator::PrepareElementData() {
MFEM_VERIFY(m_rotation.has_value(), "Prepared rotational force has no frozen rotation state.");
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
mfem::Vector baseDensityLocal;
mfem::Vector baseDisplacementLocal;
true_to_local(*m_fem.densityFes, m_context.GetBaseDensityTrue(), baseDensityLocal);
true_to_local(*m_fem.displacementFes, m_context.GetDisplacementTrue(), baseDisplacementLocal);
mapping::DomainMapper::Workspace workspace(m_domainMapper.GetDimension());
mapping::VolumeMappingContext mappingContext;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseDensity;
mfem::Vector elementBaseDisplacement;
mfem::Vector elementCompactification;
mfem::Vector densityShape;
mfem::Vector potentialGradient;
const int dimension = m_domainMapper.GetDimension();
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr, "Prepared rotational force received a null transformation.");
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
}
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
data.elementId = elementId;
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, compactificationDofs);
baseDensityLocal.GetSubVector(data.densityDofs, elementBaseDensity);
baseDisplacementLocal.GetSubVector(data.displacementDofs, elementBaseDisplacement);
m_fem.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(elementBaseDensity);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementBaseDisplacement);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId);
data.integrationRule = &get_rotation_force_rule(m_fem, *transformation);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementBaseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData, .compactification = compactificationData
};
const int quadraturePointCount = data.integrationRule->GetNPoints();
data.inverseElementJacobians.SetSize(quadraturePointCount, dimension * dimension);
data.centrifugalAccelerations.SetSize(quadraturePointCount, dimension);
data.baseDensityValues.SetSize(quadraturePointCount);
data.quadratureWeights.SetSize(quadraturePointCount);
densityShape.SetSize(densityElement.GetDof());
potentialGradient.SetSize(dimension);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid && !mappingContext.mapping.compactified,
"Prepared rotational force encountered an invalid stellar mapping."
);
densityElement.CalcShape(integrationPoint, densityShape);
m_rotation->potential_gradient(mappingContext.mapping.physical_position, potentialGradient);
data.baseDensityValues(quadraturePoint) = elementBaseDensity * densityShape;
data.quadratureWeights(quadraturePoint) = mappingContext.quadrature.weight;
for (int row = 0; row < dimension; ++row) {
data.centrifugalAccelerations(quadraturePoint, row) = -potentialGradient(row);
for (int column = 0; column < dimension; ++column) {
data.inverseElementJacobians(quadraturePoint, row * dimension + column) =
mappingContext.quadrature.J_inv(row, column);
}
}
}
}
}
PreparedRotationalDisplacementForceReport PreparedRotationalDisplacementForceOperator::Prepare(
const context::rotational_displacement_force::RotationalDisplacementForceStateView &state,
const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies,
@@ -83,6 +252,7 @@ namespace mean_field::operators {
);
m_cachedResidual.SetSize(m_context.GetDisplacementMap().reduced_size());
m_context.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
PrepareElementData();
++m_residualPreparationCount;
report.preparedResidual = true;
@@ -143,6 +313,97 @@ namespace mean_field::operators {
++m_displacementJacobianStatistics.applications;
}
void PreparedRotationalDisplacementForceOperator::ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
) const {
true_to_local(*m_fem.densityFes, densityVariationTrue, m_densityVariationLocal);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacementVariationLocal);
m_localAction.SetSize(m_fem.displacementFes->GetVSize());
m_localAction = 0.0;
const int dimension = m_domainMapper.GetDimension();
const mfem::Ordering::Type ordering = m_fem.displacementFes->GetOrdering();
for (const ElementPAData &data : m_elements) {
MFEM_VERIFY(data.integrationRule != nullptr, "Prepared rotational force has no integration rule.");
m_densityVariationLocal.GetSubVector(data.densityDofs, m_elementDensityVariation);
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
}
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(data.elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
const mfem::DenseMatrix &directionDofs = directionData.GetDofMatrix();
const int scalarDisplacementDofCount = displacementElement.GetDof();
m_densityShape.SetSize(densityElement.GetDof());
m_displacementShape.SetSize(scalarDisplacementDofCount);
m_referenceDisplacementDShape.SetSize(scalarDisplacementDofCount, dimension);
m_referenceDisplacementJacobian.SetSize(dimension, dimension);
m_physicalPositionVariation.SetSize(dimension);
m_centrifugalAcceleration.SetSize(dimension);
m_centrifugalAccelerationVariation.SetSize(dimension);
m_weightedForce.SetSize(dimension);
m_elementAction.SetSize(data.displacementDofs.Size());
m_elementAction = 0.0;
for (int quadraturePoint = 0; quadraturePoint < data.integrationRule->GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
densityElement.CalcShape(integrationPoint, m_densityShape);
displacementElement.CalcShape(integrationPoint, m_displacementShape);
displacementElement.CalcDShape(integrationPoint, m_referenceDisplacementDShape);
mfem::MultAtB(directionDofs, m_referenceDisplacementDShape, m_referenceDisplacementJacobian);
directionDofs.MultTranspose(m_displacementShape, m_physicalPositionVariation);
m_rotation->potential_gradient_directional_derivative(
m_physicalPositionVariation, m_centrifugalAccelerationVariation
);
m_centrifugalAccelerationVariation *= -1.0;
double logarithmicJacobianVariation{0.0};
for (int row = 0; row < dimension; ++row) {
m_centrifugalAcceleration(row) = data.centrifugalAccelerations(quadraturePoint, row);
for (int column = 0; column < dimension; ++column) {
logarithmicJacobianVariation +=
data.inverseElementJacobians(quadraturePoint, row * dimension + column) *
m_referenceDisplacementJacobian(column, row);
}
}
const double densityVariationValue = m_elementDensityVariation * m_densityShape;
const double baseDensityValue = data.baseDensityValues(quadraturePoint);
m_weightedForce = 0.0;
m_weightedForce.Add(densityVariationValue, m_centrifugalAcceleration);
m_weightedForce.Add(baseDensityValue, m_centrifugalAccelerationVariation);
m_weightedForce.Add(baseDensityValue * logarithmicJacobianVariation, m_centrifugalAcceleration);
m_weightedForce *= data.quadratureWeights(quadraturePoint);
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof =
vector_dof_index(ordering, scalarDof, component, scalarDisplacementDofCount, dimension);
m_elementAction(vectorDof) += m_displacementShape(scalarDof) * m_weightedForce(component);
}
}
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(m_elementAction);
}
m_localAction.AddElementVector(data.displacementDofs, m_elementAction);
}
local_to_true(*m_fem.displacementFes, m_localAction, actionTrue);
}
void PreparedRotationalDisplacementForceOperator::ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
@@ -155,10 +416,7 @@ namespace mean_field::operators {
m_context.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
m_context.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
kernels::apply_rotational_displacement_force_complete_action(
m_fem, m_domainMapper, *m_rotation, m_context.GetBaseDensityTrue(), m_densityVariationTrue,
m_displacementVariationTrue, m_context.GetDisplacementTrue(), m_actionTrue
);
ApplyPreparedCompleteJacobianActionTrue(m_densityVariationTrue, m_displacementVariationTrue, m_actionTrue);
action.SetSize(m_context.GetDisplacementMap().reduced_size());
m_context.GetDisplacementMap().gather(m_actionTrue, action);

View File

@@ -22,26 +22,34 @@ namespace {
);
}
[[nodiscard]] mean_field::operators::StellarEquilibriumLayout make_layout(
using StellarRootForm = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
[[nodiscard]] std::array<
int,
StellarRootForm::value_block_count>
make_value_sizes(
const mean_field::field::FieldDofMap &densityMap,
const int surfaceDeformationParameterCount,
const mean_field::field::FieldDofMap &gravityFluxMap,
const mean_field::field::FieldDofMap &gravityPotentialMap,
const mean_field::field::FieldDofMap &enthalpyMap
) {
using Form = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
return {densityMap.reduced_size(), surfaceDeformationParameterCount, gravityFluxMap.reduced_size(),
gravityPotentialMap.reduced_size(), enthalpyMap.reduced_size(), 1};
}
const std::array<int, Form::value_block_count> valueSizes{
densityMap.reduced_size(), surfaceDeformationParameterCount, gravityFluxMap.reduced_size(),
gravityPotentialMap.reduced_size(), enthalpyMap.reduced_size(), 1
};
const std::array<int, Form::residual_block_count> residualSizes{
gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
surfaceDeformationParameterCount, enthalpyMap.reduced_size(), 1
};
return {valueSizes, residualSizes};
[[nodiscard]] std::array<
int,
StellarRootForm::residual_block_count>
make_residual_sizes(
const mean_field::field::FieldDofMap &densityMap,
const int surfaceDeformationParameterCount,
const mean_field::field::FieldDofMap &gravityFluxMap,
const mean_field::field::FieldDofMap &gravityPotentialMap,
const mean_field::field::FieldDofMap &enthalpyMap
) {
return {gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
surfaceDeformationParameterCount, enthalpyMap.reduced_size(), 1};
}
[[nodiscard]] mfem::Array<int> make_gravity_state_offsets(
@@ -70,47 +78,6 @@ namespace {
return offsets;
}
template <int index>
[[nodiscard]] mfem::Vector make_value_view(
const mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::value_block<index> block
) {
MFEM_VERIFY(
vector.Size() == layout.value_offsets().Last(),
"The coupled vector does not match the stellar-equilibrium value layout."
);
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + layout.offset(block), layout.size(block));
}
template <int index>
[[nodiscard]] mfem::Vector make_residual_view(
mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
MFEM_VERIFY(
vector.Size() == layout.residual_offsets().Last(),
"The coupled vector does not match the stellar-equilibrium residual layout."
);
return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block));
}
template <int index>
void assign_residual_block(
mfem::Vector &coupledResidual,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::residual_block<index> block,
const mfem::Vector &blockResidual,
const char *message
) {
MFEM_VERIFY(layout.size(block) == blockResidual.Size(), message);
mfem::Vector destination = make_residual_view(coupledResidual, layout, block);
destination = blockResidual;
}
void assign_gravity_block(
mfem::Vector &gravityState,
const mfem::Array<int> &offsets,
@@ -264,7 +231,8 @@ namespace mean_field::operators {
field::FieldDofMap enthalpyMap;
field::FieldBoundaryDofMap pressureSurfaceRows;
StellarEquilibriumLayout layout;
std::array<int, StellarRootForm::value_block_count> valueSizes;
std::array<int, StellarRootForm::residual_block_count> residualSizes;
mfem::Array<int> gravityStateOffsets;
mfem::Array<int> gravityResidualOffsets;
@@ -307,7 +275,14 @@ namespace mean_field::operators {
enthalpyMap
)
),
layout(make_layout(
valueSizes(make_value_sizes(
densityMap,
domainDeformation.parameterCount(),
gravityFluxMap,
gravityPotentialMap,
enthalpyMap
)),
residualSizes(make_residual_sizes(
densityMap,
domainDeformation.parameterCount(),
gravityFluxMap,
@@ -339,7 +314,7 @@ namespace mean_field::operators {
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const double targetMass,
models::CompiledFixedMass fixedMassConstraint,
const PressureSurfaceConstraintView surfaceConstraint,
deformation::PreparedDomainDeformationRuntime domainDeformation
)
@@ -347,7 +322,7 @@ namespace mean_field::operators {
f,
domainMapper,
equationOfState,
targetMass,
std::move(fixedMassConstraint),
surfaceConstraint,
MakeConstructionData(
f,
@@ -360,15 +335,31 @@ namespace mean_field::operators {
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const double targetMass,
models::CompiledFixedMass fixedMassConstraint,
const PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData
)
: mfem::Operator(
constructionData.layout.residual_offsets().Last(),
constructionData.layout.value_offsets().Last()
StellarEquilibriumLayout(
constructionData.valueSizes,
constructionData.residualSizes
)
.residual_offsets()
.Last(),
StellarEquilibriumLayout(
constructionData.valueSizes,
constructionData.residualSizes
)
.value_offsets()
.Last()
),
m_rootManifest(
constructionData.valueSizes,
constructionData.residualSizes,
fixedMassConstraint.targetMass().value(),
surfaceConstraint.descriptor().targetPressure,
constructionData.pressureSurfaceRows.size()
),
m_layout(constructionData.layout),
m_gravityStateOffsets(constructionData.gravityStateOffsets),
m_gravityContext(
f,
@@ -413,14 +404,11 @@ namespace mean_field::operators {
surfaceConstraint
),
m_domainDeformation(std::move(constructionData.domainDeformation)),
m_targetMass(targetMass) {
MFEM_VERIFY(
std::isfinite(m_targetMass) && m_targetMass > 0.0,
"PreparedStellarEquilibriumOperator requires a finite, positive target mass."
);
m_fixedMassConstraint(std::move(fixedMassConstraint)) {
MFEM_VERIFY(
Width() == m_layout.value_offsets().Last() && Height() == m_layout.residual_offsets().Last(),
Width() == m_rootManifest.layout().value_offsets().Last() &&
Height() == m_rootManifest.layout().residual_offsets().Last(),
"PreparedStellarEquilibriumOperator has inconsistent block dimensions."
);
@@ -502,27 +490,18 @@ namespace mean_field::operators {
);
}
m_isPrepared = false;
m_isPrepared = false;
using Form = utils::blocks::surface_deformed_stellar_equilibrium_form;
constexpr auto densityValue = utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto surfaceDeformationValue =
utils::blocks::get_value_block<Form>(utils::blocks::surface_deformation_field.parameters_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto bernoulliValue =
utils::blocks::get_value_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
const auto rootState = m_rootManifest.stateView(state);
const mfem::Vector reducedDensity = make_value_view(state, m_layout, densityValue);
const mfem::Vector surfaceDeformationParameters = make_value_view(state, m_layout, surfaceDeformationValue);
const mfem::Vector gravityGradient = make_value_view(state, m_layout, gravityGradientValue);
const mfem::Vector gravityPotential = make_value_view(state, m_layout, gravityPotentialValue);
const mfem::Vector reducedEnthalpy = make_value_view(state, m_layout, enthalpyValue);
const mfem::Vector bernoulli = make_value_view(state, m_layout, bernoulliValue);
const mfem::Vector reducedDensity = rootState.block(utils::blocks::density_field.mass_term);
const mfem::Vector surfaceDeformationParameters =
rootState.block(utils::blocks::surface_deformation_field.parameters_term);
const mfem::Vector gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
const mfem::Vector gravityPotential = rootState.block(utils::blocks::gravity_field.poisson_term);
const mfem::Vector reducedEnthalpy = rootState.block(utils::blocks::enthalpy_field.specific_term);
const mfem::Vector bernoulli =
rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
const bool generatedGeometryChanged =
!wasPrepared || dependencies.discretization != m_preparedDependencies.discretization ||
@@ -569,7 +548,7 @@ namespace mean_field::operators {
);
report.massNormalization = m_massNormalizationOperator.Prepare(
{.targetMass = m_targetMass}, make_mass_dependencies(dependencies, m_generatedDisplacementDependency)
m_fixedMassConstraint, make_mass_dependencies(dependencies, m_generatedDisplacementDependency)
);
report.surfaceConstraint = m_surfaceConstraintOperator.Prepare(
@@ -588,21 +567,6 @@ namespace mean_field::operators {
}
void PreparedStellarEquilibriumOperator::AssembleResidual() {
using Form = utils::blocks::surface_deformed_stellar_equilibrium_form;
constexpr auto gravityGradientResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto surfaceShapeResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::surface_deformation_field.shape_equilibrium_term);
constexpr auto enthalpyResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
mfem::Vector gravity;
mfem::Vector closure;
mfem::Vector surfaceShape;
@@ -621,41 +585,29 @@ namespace mean_field::operators {
m_massNormalizationOperator.BuildResidual(mass);
m_cachedResidual.SetSize(Height());
m_cachedResidual = 0.0;
m_cachedResidual = 0.0;
const auto residualView = m_rootManifest.residualView(m_cachedResidual);
MFEM_VERIFY(
gravity.Size() == m_layout.size(gravityGradientResidual) + m_layout.size(gravityPotentialResidual),
gravity.Size() == residualView.block(utils::blocks::gravity_field.gradient_term).Size() +
residualView.block(utils::blocks::gravity_field.poisson_term).Size(),
"The gravity residual has the wrong size."
);
mfem::Vector gravityGradient(gravity.GetData(), m_layout.size(gravityGradientResidual));
mfem::Vector gravityGradient(
gravity.GetData(), residualView.block(utils::blocks::gravity_field.gradient_term).Size()
);
mfem::Vector gravityPotential(
gravity.GetData() + m_layout.size(gravityGradientResidual), m_layout.size(gravityPotentialResidual)
gravity.GetData() + gravityGradient.Size(),
residualView.block(utils::blocks::gravity_field.poisson_term).Size()
);
assign_residual_block(
m_cachedResidual, m_layout, gravityGradientResidual, gravityGradient,
"The gravity-gradient residual has the wrong size."
);
assign_residual_block(
m_cachedResidual, m_layout, gravityPotentialResidual, gravityPotential,
"The gravity-potential residual has the wrong size."
);
assign_residual_block(
m_cachedResidual, m_layout, densityResidual, closure, "The closure residual has the wrong size."
);
assign_residual_block(
m_cachedResidual, m_layout, surfaceShapeResidual, surfaceShape,
"The surface-shape residual has the wrong size."
);
assign_residual_block(
m_cachedResidual, m_layout, enthalpyResidual, hydrostatic, "The hydrostatic residual has the wrong size."
);
assign_residual_block(
m_cachedResidual, m_layout, massResidual, mass, "The mass-normalization residual has the wrong size."
);
residualView.assign(utils::blocks::gravity_field.gradient_term, gravityGradient);
residualView.assign(utils::blocks::gravity_field.poisson_term, gravityPotential);
residualView.assign(utils::blocks::density_field.mass_term, closure);
residualView.assign(utils::blocks::surface_deformation_field.shape_equilibrium_term, surfaceShape);
residualView.assign(utils::blocks::enthalpy_field.specific_term, hydrostatic);
residualView.assign(utils::blocks::fixed_total_mass_constraint.mass_normalization_term, mass);
++m_statistics.residualAssemblies;
}
@@ -679,39 +631,16 @@ namespace mean_field::operators {
direction, "PreparedStellarEquilibriumOperator received a non-finite Jacobian direction."
);
using Form = utils::blocks::surface_deformed_stellar_equilibrium_form;
const auto rootDirection = m_rootManifest.directionView(direction);
constexpr auto densityValue = utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto surfaceDeformationValue =
utils::blocks::get_value_block<Form>(utils::blocks::surface_deformation_field.parameters_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto bernoulliValue =
utils::blocks::get_value_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
constexpr auto gravityGradientResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto surfaceShapeResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::surface_deformation_field.shape_equilibrium_term);
constexpr auto enthalpyResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
const mfem::Vector reducedDensityDirection = make_value_view(direction, m_layout, densityValue);
const mfem::Vector surfaceDeformationDirection = make_value_view(direction, m_layout, surfaceDeformationValue);
const mfem::Vector gravityGradientDirection = make_value_view(direction, m_layout, gravityGradientValue);
const mfem::Vector gravityPotentialDirection = make_value_view(direction, m_layout, gravityPotentialValue);
const mfem::Vector reducedEnthalpyDirection = make_value_view(direction, m_layout, enthalpyValue);
const mfem::Vector bernoulliDirection = make_value_view(direction, m_layout, bernoulliValue);
const mfem::Vector reducedDensityDirection = rootDirection.block(utils::blocks::density_field.mass_term);
const mfem::Vector surfaceDeformationDirection =
rootDirection.block(utils::blocks::surface_deformation_field.parameters_term);
const mfem::Vector gravityGradientDirection = rootDirection.block(utils::blocks::gravity_field.gradient_term);
const mfem::Vector gravityPotentialDirection = rootDirection.block(utils::blocks::gravity_field.poisson_term);
const mfem::Vector reducedEnthalpyDirection = rootDirection.block(utils::blocks::enthalpy_field.specific_term);
const mfem::Vector bernoulliDirection =
rootDirection.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
m_domainDeformation.applyJacobian(
m_surfaceDeformationParameters, surfaceDeformationDirection, m_volumeDisplacementDirection
@@ -757,41 +686,29 @@ namespace mean_field::operators {
);
action.SetSize(Height());
action = 0.0;
action = 0.0;
const auto actionView = m_rootManifest.residualView(action);
MFEM_VERIFY(
gravityAction.Size() == m_layout.size(gravityGradientResidual) + m_layout.size(gravityPotentialResidual),
gravityAction.Size() == actionView.block(utils::blocks::gravity_field.gradient_term).Size() +
actionView.block(utils::blocks::gravity_field.poisson_term).Size(),
"The gravity Jacobian action has the wrong size."
);
mfem::Vector gravityGradientAction(gravityAction.GetData(), m_layout.size(gravityGradientResidual));
mfem::Vector gravityGradientAction(
gravityAction.GetData(), actionView.block(utils::blocks::gravity_field.gradient_term).Size()
);
mfem::Vector gravityPotentialAction(
gravityAction.GetData() + m_layout.size(gravityGradientResidual), m_layout.size(gravityPotentialResidual)
gravityAction.GetData() + gravityGradientAction.Size(),
actionView.block(utils::blocks::gravity_field.poisson_term).Size()
);
assign_residual_block(
action, m_layout, gravityGradientResidual, gravityGradientAction,
"The gravity-gradient Jacobian action has the wrong size."
);
assign_residual_block(
action, m_layout, gravityPotentialResidual, gravityPotentialAction,
"The gravity-potential Jacobian action has the wrong size."
);
assign_residual_block(
action, m_layout, densityResidual, closureAction, "The closure Jacobian action has the wrong size."
);
assign_residual_block(
action, m_layout, surfaceShapeResidual, m_surfaceShapeAction,
"The surface-shape Jacobian action has the wrong size."
);
assign_residual_block(
action, m_layout, enthalpyResidual, hydrostaticAction, "The hydrostatic Jacobian action has the wrong size."
);
assign_residual_block(
action, m_layout, massResidual, massAction, "The mass-normalization Jacobian action has the wrong size."
);
actionView.assign(utils::blocks::gravity_field.gradient_term, gravityGradientAction);
actionView.assign(utils::blocks::gravity_field.poisson_term, gravityPotentialAction);
actionView.assign(utils::blocks::density_field.mass_term, closureAction);
actionView.assign(utils::blocks::surface_deformation_field.shape_equilibrium_term, m_surfaceShapeAction);
actionView.assign(utils::blocks::enthalpy_field.specific_term, hydrostaticAction);
actionView.assign(utils::blocks::fixed_total_mass_constraint.mass_normalization_term, massAction);
++m_statistics.jacobianApplications;
}
@@ -803,11 +720,30 @@ namespace mean_field::operators {
}
double PreparedStellarEquilibriumOperator::GetTargetMass() const noexcept {
return m_targetMass;
return m_fixedMassConstraint.targetMass().value();
}
const StellarEquilibriumLayout &PreparedStellarEquilibriumOperator::GetLayout() const noexcept {
return m_layout;
return m_rootManifest.layout();
}
const StellarEquilibriumRootManifest &PreparedStellarEquilibriumOperator::GetRootManifest() const noexcept {
return m_rootManifest;
}
RootStateView<utils::blocks::surface_deformed_stellar_equilibrium_form>
PreparedStellarEquilibriumOperator::GetRootStateView(const mfem::Vector &state) const {
return m_rootManifest.stateView(state);
}
ResidualView<utils::blocks::surface_deformed_stellar_equilibrium_form>
PreparedStellarEquilibriumOperator::GetResidualView(mfem::Vector &residual) const {
return m_rootManifest.residualView(residual);
}
RootConstraintReport PreparedStellarEquilibriumOperator::GetFixedMassReport() const {
VerifyPrepared();
return m_rootManifest.fixedMassReport(m_massNormalizationOperator.GetCurrentMass());
}
const StellarEquilibriumDependencies &PreparedStellarEquilibriumOperator::GetDependencies() const {

View File

@@ -102,7 +102,7 @@ namespace mean_field::physics {
GravitySolution solve_gravity_field(
fem::FEM &f,
const utils::Args &args,
const GravitySolveOptions &options,
const mfem::GridFunction &rho,
const mfem::GridFunction &displacement
) {
@@ -132,7 +132,15 @@ namespace mean_field::physics {
"Vec_H1 "
"space."
);
MFEM_VERIFY(args.p.max_iters > 0, "Gravity solve requires a positive MINRES iteration limit.");
MFEM_VERIFY(
std::isfinite(options.relativeTolerance) && options.relativeTolerance >= 0.0,
"Gravity solve requires a finite, nonnegative relative tolerance."
);
MFEM_VERIFY(
std::isfinite(options.absoluteTolerance) && options.absoluteTolerance >= 0.0,
"Gravity solve requires a finite, nonnegative absolute tolerance."
);
MFEM_VERIFY(options.maximumIterations > 0, "Gravity solve requires a positive MINRES iteration limit.");
using form = utils::blocks::gravity_field_form;
@@ -206,9 +214,9 @@ namespace mean_field::physics {
mfem::MINRESSolver minres(f.mesh->GetComm());
minres.SetOperator(reduced_operator);
minres.SetPreconditioner(reduced_preconditioner);
minres.SetRelTol(args.p.rtol);
minres.SetAbsTol(args.p.atol);
minres.SetMaxIter(args.p.max_iters);
minres.SetRelTol(options.relativeTolerance);
minres.SetAbsTol(options.absoluteTolerance);
minres.SetMaxIter(options.maximumIterations);
// minres.SetPrintLevel(args.verbose ? 1 : 0);
minres.SetPrintLevel(0);
minres.Mult(right_hand_side, gravity_state);
@@ -222,4 +230,21 @@ namespace mean_field::physics {
return solution;
}
GravitySolution solve_gravity_field(
fem::FEM &f,
const utils::Args &args,
const mfem::GridFunction &rho,
const mfem::GridFunction &displacement
) {
return solve_gravity_field(
f,
GravitySolveOptions{
.relativeTolerance = args.p.rtol,
.absoluteTolerance = args.p.atol,
.maximumIterations = args.p.max_iters
},
rho, displacement
);
}
} // namespace mean_field::physics

View File

@@ -0,0 +1,248 @@
module;
#include <algorithm>
#include <cmath>
#include <numbers>
#include <optional>
#include <stdexcept>
#include <vector>
#include <mfem.hpp>
module mean_field;
import :seed.lane_emden;
import :utils.misc;
namespace {
struct LaneEmdenPoint final {
double coordinate{0.0};
double value{0.0};
double derivative{0.0};
};
struct LaneEmdenDerivative final {
double value{0.0};
double derivative{0.0};
};
[[nodiscard]] LaneEmdenDerivative evaluate_lane_emden_rhs(
const double coordinate,
const double value,
const double derivative,
const double polytropicIndex
) {
const double nonnegativeValue = std::max(value, 0.0);
return {
.value = derivative,
.derivative = -2.0 * derivative / coordinate - std::pow(nonnegativeValue, polytropicIndex)
};
}
[[nodiscard]] LaneEmdenPoint take_lane_emden_step(
const LaneEmdenPoint &point,
const double step,
const double polytropicIndex
) {
const LaneEmdenDerivative first =
evaluate_lane_emden_rhs(point.coordinate, point.value, point.derivative, polytropicIndex);
const LaneEmdenDerivative second = evaluate_lane_emden_rhs(
point.coordinate + 0.5 * step, point.value + 0.5 * step * first.value,
point.derivative + 0.5 * step * first.derivative, polytropicIndex
);
const LaneEmdenDerivative third = evaluate_lane_emden_rhs(
point.coordinate + 0.5 * step, point.value + 0.5 * step * second.value,
point.derivative + 0.5 * step * second.derivative, polytropicIndex
);
const LaneEmdenDerivative fourth = evaluate_lane_emden_rhs(
point.coordinate + step, point.value + step * third.value, point.derivative + step * third.derivative,
polytropicIndex
);
return {
.coordinate = point.coordinate + step,
.value = point.value + step / 6.0 * (first.value + 2.0 * second.value + 2.0 * third.value + fourth.value),
.derivative =
point.derivative +
step / 6.0 * (first.derivative + 2.0 * second.derivative + 2.0 * third.derivative + fourth.derivative)
};
}
[[nodiscard]] std::vector<LaneEmdenPoint> solve_lane_emden(
const double polytropicIndex,
const double coordinateLimit,
const double integrationStep
) {
if (!std::isfinite(polytropicIndex) || polytropicIndex < 0.0) {
throw std::invalid_argument("Lane-Emden integration requires a finite, nonnegative polytropic index.");
}
if (!std::isfinite(coordinateLimit) || coordinateLimit <= 0.0) {
throw std::invalid_argument("The Lane-Emden coordinate limit must be finite and positive.");
}
if (!std::isfinite(integrationStep) || integrationStep <= 0.0) {
throw std::invalid_argument("The Lane-Emden integration step must be finite and positive.");
}
constexpr int maximumStepCount = 2'000'000;
if (std::ceil(coordinateLimit / integrationStep) > static_cast<double>(maximumStepCount)) {
throw std::invalid_argument("The requested Lane-Emden interval exceeds the integration step limit.");
}
const double initialCoordinate = std::min(1.0e-6, coordinateLimit);
const double coordinateSquared = initialCoordinate * initialCoordinate;
const double coordinateCubed = coordinateSquared * initialCoordinate;
const double coordinateFourth = coordinateSquared * coordinateSquared;
LaneEmdenPoint point{
.coordinate = initialCoordinate,
.value = 1.0 - coordinateSquared / 6.0 + polytropicIndex * coordinateFourth / 120.0,
.derivative = -initialCoordinate / 3.0 + polytropicIndex * coordinateCubed / 30.0
};
std::vector<LaneEmdenPoint> solution;
solution.reserve(8192);
solution.push_back({.coordinate = 0.0, .value = 1.0, .derivative = 0.0});
solution.push_back(point);
for (int stepIndex = 0; stepIndex < maximumStepCount && point.coordinate < coordinateLimit; ++stepIndex) {
const double step = std::min(integrationStep, coordinateLimit - point.coordinate);
LaneEmdenPoint nextPoint = take_lane_emden_step(point, step, polytropicIndex);
if (!std::isfinite(nextPoint.value)) {
throw std::runtime_error(
"The Lane-Emden integration produced a non-finite solution before reaching its termination."
);
}
if (nextPoint.value <= 0.0) {
const double rootFraction = point.value / (point.value - nextPoint.value);
solution.push_back(
{.coordinate = point.coordinate + rootFraction * (nextPoint.coordinate - point.coordinate),
.value = 0.0,
.derivative = point.derivative + rootFraction * (nextPoint.derivative - point.derivative)}
);
return solution;
}
solution.push_back(nextPoint);
point = nextPoint;
}
if (point.coordinate < coordinateLimit) {
throw std::runtime_error("The Lane-Emden integration exceeded its step limit.");
}
return solution;
}
[[nodiscard]] double interpolate_lane_emden_value(
const std::vector<LaneEmdenPoint> &solution,
const double coordinate,
std::size_t &lowerIndex
) {
while (lowerIndex + 1 < solution.size() && solution[lowerIndex + 1].coordinate < coordinate) {
++lowerIndex;
}
if (lowerIndex + 1 >= solution.size()) {
return 0.0;
}
const LaneEmdenPoint &lower = solution[lowerIndex];
const LaneEmdenPoint &upper = solution[lowerIndex + 1];
const double interval = upper.coordinate - lower.coordinate;
if (interval <= 0.0) {
throw std::runtime_error("The Lane-Emden interpolation grid is not strictly increasing.");
}
const double fraction = (coordinate - lower.coordinate) / interval;
return std::clamp(lower.value + fraction * (upper.value - lower.value), 0.0, 1.0);
}
} // namespace
namespace mean_field::seed {
DimensionlessLaneEmdenSolution integrateLaneEmden(
const double polytropicIndex,
const double coordinateLimit,
const double integrationStep
) {
const std::vector<LaneEmdenPoint> points = solve_lane_emden(polytropicIndex, coordinateLimit, integrationStep);
DimensionlessLaneEmdenSolution solution{
.coordinate = mfem::Vector(static_cast<int>(points.size())),
.theta = mfem::Vector(static_cast<int>(points.size())),
.thetaDerivative = mfem::Vector(static_cast<int>(points.size())),
.firstZeroCoordinate = std::nullopt
};
for (int index = 0; index < static_cast<int>(points.size()); ++index) {
solution.coordinate(index) = points[static_cast<std::size_t>(index)].coordinate;
solution.theta(index) = points[static_cast<std::size_t>(index)].value;
solution.thetaDerivative(index) = points[static_cast<std::size_t>(index)].derivative;
}
if (points.back().value == 0.0) {
solution.firstZeroCoordinate = points.back().coordinate;
}
return solution;
}
RadialProfile generateLaneEmdenProfile(
const eos::Polytrope &equationOfState,
const dimensions::DensityValue centralDensity,
const int radialSampleCount
) {
if (!std::isfinite(centralDensity.value()) || centralDensity.value() <= 0.0) {
throw std::invalid_argument("A Lane-Emden seed central density must be finite and positive.");
}
if (radialSampleCount < 2) {
throw std::invalid_argument("A Lane-Emden seed requires at least two radial samples.");
}
const double polytropicIndex = equationOfState.polytropic_index();
if (!std::isfinite(polytropicIndex) || polytropicIndex < 1.0 || polytropicIndex >= 5.0) {
throw std::invalid_argument("Lane-Emden seeds require a finite-radius polytrope with 1 <= n < 5.");
}
constexpr double seedCoordinateLimit = 2'000.0;
constexpr double integrationStep = 1.0e-3;
const std::vector solution = solve_lane_emden(polytropicIndex, seedCoordinateLimit, integrationStep);
if (solution.back().value != 0.0) {
throw std::runtime_error("The Lane-Emden integration did not reach its first zero within the step limit.");
}
const double surfaceCoordinate = solution.back().coordinate;
const dimensions::SpecificEnthalpyValue centralEnthalpy =
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(equationOfState, centralDensity);
const double radialScaleSquared = centralEnthalpy.value() / (4.0 * std::numbers::pi_v<double> *
mean_field::utils::G * centralDensity.value());
if (!std::isfinite(radialScaleSquared) || radialScaleSquared <= 0.0) {
throw std::runtime_error("The polytropic Lane-Emden radial scale is not finite and positive.");
}
const double radialScale = std::sqrt(radialScaleSquared);
RadialProfile profile{
.radius = mfem::Vector(radialSampleCount),
.density = mfem::Vector(radialSampleCount),
.specificEnthalpy = mfem::Vector(radialSampleCount),
.stellarRadius = dimensions::LengthValue{radialScale * surfaceCoordinate},
.centralDensity = centralDensity,
.centralSpecificEnthalpy = centralEnthalpy
};
std::size_t interpolationIndex = 0;
for (int sampleIndex = 0; sampleIndex < radialSampleCount; ++sampleIndex) {
const double fraction = static_cast<double>(sampleIndex) / static_cast<double>(radialSampleCount - 1);
const double dimensionlessRadius = fraction * surfaceCoordinate;
const double laneEmdenValue =
interpolate_lane_emden_value(solution, dimensionlessRadius, interpolationIndex);
const dimensions::DensityValue density{centralDensity.value() * std::pow(laneEmdenValue, polytropicIndex)};
profile.radius(sampleIndex) = radialScale * dimensionlessRadius;
profile.density(sampleIndex) = density.value();
profile.specificEnthalpy(sampleIndex) =
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(equationOfState, density).value();
}
profile.radius(0) = 0.0;
profile.density(0) = centralDensity.value();
profile.specificEnthalpy(0) = centralEnthalpy.value();
const int surfaceIndex = radialSampleCount - 1;
profile.radius(surfaceIndex) = profile.stellarRadius.value();
profile.density(surfaceIndex) = 0.0;
profile.specificEnthalpy(surfaceIndex) = 0.0;
return profile;
}
} // namespace mean_field::seed

View File

@@ -0,0 +1,209 @@
module;
#include <algorithm>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <mfem.hpp>
#include <mpi.h>
module mean_field;
import :field.mfem;
import :seed.stellar_equilibrium_projection;
import :utils.domain;
import :utils.misc;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
void validate_profile(const mean_field::seed::RadialProfile &profile) {
const int sampleCount = profile.radius.Size();
if (sampleCount < 2 || profile.density.Size() != sampleCount ||
profile.specificEnthalpy.Size() != sampleCount) {
throw std::invalid_argument("A radial seed projection requires equally sized profiles with two samples.");
}
if (!std::isfinite(profile.stellarRadius.value()) || profile.stellarRadius.value() <= 0.0 ||
!std::isfinite(profile.centralDensity.value()) || profile.centralDensity.value() <= 0.0 ||
!std::isfinite(profile.centralSpecificEnthalpy.value()) || profile.centralSpecificEnthalpy.value() <= 0.0) {
throw std::invalid_argument("A radial seed projection requires finite, positive physical scales.");
}
for (int index = 0; index < sampleCount; ++index) {
if (!std::isfinite(profile.radius(index)) || !std::isfinite(profile.density(index)) ||
!std::isfinite(profile.specificEnthalpy(index)) || profile.density(index) < 0.0 ||
profile.specificEnthalpy(index) < 0.0) {
throw std::invalid_argument("A radial seed projection received a non-finite or negative profile.");
}
if (index > 0 && profile.radius(index) <= profile.radius(index - 1)) {
throw std::invalid_argument("A radial seed projection requires strictly increasing radii.");
}
}
const int surfaceIndex = sampleCount - 1;
const double radialScale = std::max(profile.stellarRadius.value(), 1.0);
if (std::abs(profile.radius(0)) > 64.0 * std::numeric_limits<double>::epsilon() * radialScale ||
std::abs(profile.radius(surfaceIndex) - profile.stellarRadius.value()) >
64.0 * std::numeric_limits<double>::epsilon() * radialScale ||
profile.density(0) != profile.centralDensity.value() ||
profile.specificEnthalpy(0) != profile.centralSpecificEnthalpy.value() ||
profile.density(surfaceIndex) != 0.0 || profile.specificEnthalpy(surfaceIndex) != 0.0) {
throw std::invalid_argument("A radial seed projection received inconsistent center or surface metadata.");
}
}
[[nodiscard]] double interpolate_profile(
const mfem::Vector &radius,
const mfem::Vector &values,
const double requestedRadius
) {
if (requestedRadius <= radius(0)) {
return values(0);
}
const int finalIndex = radius.Size() - 1;
if (requestedRadius >= radius(finalIndex)) {
return values(finalIndex);
}
int lowerIndex = 0;
int upperIndex = finalIndex;
while (upperIndex - lowerIndex > 1) {
const int middleIndex = lowerIndex + (upperIndex - lowerIndex) / 2;
if (radius(middleIndex) <= requestedRadius) {
lowerIndex = middleIndex;
} else {
upperIndex = middleIndex;
}
}
const double fraction = (requestedRadius - radius(lowerIndex)) / (radius(upperIndex) - radius(lowerIndex));
return (1.0 - fraction) * values(lowerIndex) + fraction * values(upperIndex);
}
struct SurfaceRadiusRange final {
double minimum;
double maximum;
};
[[nodiscard]] SurfaceRadiusRange measure_surface_radius(const mean_field::fem::FEM &finiteElementModel) {
if (finiteElementModel.surfaceDeformationFes == nullptr) {
throw std::invalid_argument("Radial seed projection requires the surface-deformation space.");
}
mfem::ParFiniteElementSpace &surfaceSpace = *finiteElementModel.surfaceDeformationFes;
const mean_field::field::ScalarBoundaryDofMap surfaceMap =
mean_field::field::make_stellar_surface_scalar_dof_map<DomainSchema>(surfaceSpace);
mfem::Vector radiusSquared(surfaceMap.local_size());
radiusSquared = 0.0;
mfem::ParGridFunction coordinateField(&surfaceSpace);
for (int component = 0; component < surfaceSpace.GetMesh()->SpaceDimension(); ++component) {
mfem::FunctionCoefficient coordinateCoefficient([component](const mfem::Vector &position) {
return position(component);
});
coordinateField.ProjectCoefficient(coordinateCoefficient);
mfem::Vector coordinateTrue;
coordinateField.GetTrueDofs(coordinateTrue);
const mfem::Vector surfaceCoordinate = surfaceMap.gather(coordinateTrue);
for (int index = 0; index < radiusSquared.Size(); ++index) {
radiusSquared(index) += surfaceCoordinate(index) * surfaceCoordinate(index);
}
}
double localMinimum = std::numeric_limits<double>::infinity();
double localMaximum = 0.0;
for (int index = 0; index < radiusSquared.Size(); ++index) {
const double radius = std::sqrt(radiusSquared(index));
localMinimum = std::min(localMinimum, radius);
localMaximum = std::max(localMaximum, radius);
}
double globalMinimum = 0.0;
double globalMaximum = 0.0;
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, surfaceSpace.GetComm());
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, surfaceSpace.GetComm());
if (!std::isfinite(globalMinimum) || !std::isfinite(globalMaximum) || globalMinimum <= 0.0 ||
globalMaximum < globalMinimum) {
throw std::runtime_error("The stellar surface has no finite, positive radial extent.");
}
return {.minimum = globalMinimum, .maximum = globalMaximum};
}
} // namespace
namespace mean_field::seed::detail {
ProjectedRadialFields projectRadialFields(
const equilibrium::StellarDiscretization &discretization,
const RadialProfile &profile,
const dimensions::MassValue targetMass,
const dimensions::PressureValue targetSurfacePressure,
const StellarEquilibriumProjectionOptions &options
) {
validate_profile(profile);
if (!std::isfinite(options.surfaceRadiusRelativeTolerance) || options.surfaceRadiusRelativeTolerance < 0.0) {
throw std::invalid_argument("The surface-radius projection tolerance must be finite and nonnegative.");
}
if (targetSurfacePressure.value() != 0.0) {
throw std::invalid_argument("A Lane-Emden radial seed requires a zero-pressure isobaric surface.");
}
fem::FEM &finiteElementModel = discretization.finiteElementModel();
const SurfaceRadiusRange surfaceRadius = measure_surface_radius(finiteElementModel);
const double targetRadius = profile.stellarRadius.value();
const double comparisonScale = std::max({targetRadius, surfaceRadius.maximum, 1.0e-300});
const double relativeMismatch =
std::max(std::abs(surfaceRadius.minimum - targetRadius), std::abs(surfaceRadius.maximum - targetRadius)) /
comparisonScale;
if (relativeMismatch > options.surfaceRadiusRelativeTolerance) {
throw std::invalid_argument(
"The radial seed surface does not coincide with the spherical reference discretization."
);
}
if (finiteElementModel.densityFes == nullptr || finiteElementModel.enthalpyFes == nullptr ||
finiteElementModel.displacementFes == nullptr || finiteElementModel.gravityFluxFes == nullptr ||
finiteElementModel.gravityPotentialFes == nullptr) {
throw std::invalid_argument("Radial seed projection requires the complete equilibrium discretization.");
}
mfem::FunctionCoefficient densityCoefficient([&profile](const mfem::Vector &position) {
return interpolate_profile(profile.radius, profile.density, position.Norml2());
});
mfem::FunctionCoefficient enthalpyCoefficient([&profile](const mfem::Vector &position) {
return interpolate_profile(profile.radius, profile.specificEnthalpy, position.Norml2());
});
mfem::ParGridFunction densityField(finiteElementModel.densityFes.get());
mfem::ParGridFunction enthalpyField(finiteElementModel.enthalpyFes.get());
mfem::ParGridFunction displacementField(finiteElementModel.displacementFes.get());
densityField = 0.0;
enthalpyField = 0.0;
displacementField = 0.0;
densityField.ProjectCoefficient(densityCoefficient);
enthalpyField.ProjectCoefficient(enthalpyCoefficient);
const physics::GravitySolution gravitySolution =
physics::solve_gravity_field(finiteElementModel, options.gravity, densityField, displacementField);
const field::FieldDofGridFunctionAdapter densityAdapter =
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*finiteElementModel.densityFes);
const field::FieldDofGridFunctionAdapter enthalpyAdapter =
field::make_field_dof_grid_function_adapter<field::Enthalpy, DomainSchema>(*finiteElementModel.enthalpyFes);
const field::FieldDofGridFunctionAdapter gravityFluxAdapter =
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(
*finiteElementModel.gravityFluxFes
);
const field::FieldDofGridFunctionAdapter gravityPotentialAdapter =
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(
*finiteElementModel.gravityPotentialFes
);
return {
.density = densityAdapter.gather(densityField),
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius
};
}
} // namespace mean_field::seed::detail

View File

@@ -0,0 +1,638 @@
module;
#include <algorithm>
#include <chrono>
#include <cmath>
#include <complex>
#include <cstdint>
#include <limits>
#include <memory>
#include <ranges>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>
#include <Eigen/SVD>
#include <mfem.hpp>
#include <mpi.h>
module mean_field;
import :solver.preconditioning_diagnostics;
namespace {
using Clock = std::chrono::steady_clock;
[[nodiscard]] double seconds_between(
const Clock::time_point start,
const Clock::time_point finish
) {
return std::chrono::duration<double>(finish - start).count();
}
void verify_finite_vector(
const mfem::Vector &vector,
const char *message
) {
for (int index = 0; index < vector.Size(); ++index) {
if (!std::isfinite(vector(index))) {
throw std::invalid_argument(message);
}
}
}
[[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
if (communicator == MPI_COMM_NULL) {
throw std::invalid_argument("Preconditioning diagnostics require a valid MPI communicator.");
}
if (left.Size() != right.Size()) {
throw std::invalid_argument("A distributed inner product received vectors with different sizes.");
}
const double localValue = left * right;
double globalValue = 0.0;
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, communicator);
return globalValue;
}
[[nodiscard]] double global_norm(
const mfem::Vector &vector,
const MPI_Comm communicator
) {
return std::sqrt(std::max(global_dot(vector, vector, communicator), 0.0));
}
[[nodiscard]] mean_field::solver::OperatorApplicationStatistics maximum_rank_statistics(
const mean_field::solver::OperatorApplicationStatistics &local,
const MPI_Comm communicator
) {
unsigned long long localApplications = static_cast<unsigned long long>(local.applications);
unsigned long long maximumApplications{0};
MPI_Allreduce(&localApplications, &maximumApplications, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
mean_field::solver::OperatorApplicationStatistics result;
result.applications = static_cast<std::uint64_t>(maximumApplications);
MPI_Allreduce(&local.totalSeconds, &result.totalSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&local.maximumSeconds, &result.maximumSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
return result;
}
[[nodiscard]] double maximum_rank_value(
const double localValue,
const MPI_Comm communicator
) {
double result = 0.0;
MPI_Allreduce(&localValue, &result, 1, MPI_DOUBLE, MPI_MAX, communicator);
return result;
}
[[nodiscard]] mean_field::solver::PreconditionerLifecycleStatistics maximum_rank_lifecycle_statistics(
const mean_field::solver::PreconditionerLifecycleStatistics &local,
const MPI_Comm communicator
) {
unsigned long long localSetups = static_cast<unsigned long long>(local.setups);
unsigned long long localRefreshes = static_cast<unsigned long long>(local.refreshes);
unsigned long long maximumSetups{0};
unsigned long long maximumRefreshes{0};
MPI_Allreduce(&localSetups, &maximumSetups, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
MPI_Allreduce(&localRefreshes, &maximumRefreshes, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
mean_field::solver::PreconditionerLifecycleStatistics result;
result.setups = static_cast<std::uint64_t>(maximumSetups);
result.refreshes = static_cast<std::uint64_t>(maximumRefreshes);
MPI_Allreduce(&local.setupSeconds, &result.setupSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&local.refreshSeconds, &result.refreshSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
return result;
}
[[nodiscard]] Eigen::MatrixXd copy_hessenberg(
const Eigen::MatrixXd &source,
const int rowCount,
const int columnCount
) {
return source.topLeftCorner(rowCount, columnCount);
}
} // namespace
namespace mean_field::solver {
InstrumentedOperator::InstrumentedOperator(const mfem::Operator &operation)
: mfem::Operator(
operation.Height(),
operation.Width()
),
m_operation(std::addressof(operation)) {
}
void InstrumentedOperator::Mult(
const mfem::Vector &input,
mfem::Vector &output
) const {
const Clock::time_point start = Clock::now();
m_operation->Mult(input, output);
const double elapsed = seconds_between(start, Clock::now());
++m_statistics.applications;
m_statistics.totalSeconds += elapsed;
m_statistics.maximumSeconds = std::max(m_statistics.maximumSeconds, elapsed);
}
void InstrumentedOperator::ResetStatistics() const noexcept {
m_statistics = {};
}
const OperatorApplicationStatistics &InstrumentedOperator::GetStatistics() const noexcept {
return m_statistics;
}
const mfem::Operator &InstrumentedOperator::GetOperation() const noexcept {
return *m_operation;
}
InstrumentedPreconditioner::InstrumentedPreconditioner(mfem::Solver &preconditioner)
: mfem::Solver(
preconditioner.Height(),
preconditioner.Width(),
preconditioner.iterative_mode
),
m_preconditioner(std::addressof(preconditioner)) {
}
void InstrumentedPreconditioner::SetOperator(const mfem::Operator &operation) {
const Clock::time_point start = Clock::now();
m_preconditioner->SetOperator(operation);
m_lifecycleStatistics.setupSeconds += seconds_between(start, Clock::now());
++m_lifecycleStatistics.setups;
if (m_preconditioner->Height() != Height() || m_preconditioner->Width() != Width()) {
throw std::invalid_argument("An instrumented preconditioner changed dimensions during SetOperator.");
}
}
void InstrumentedPreconditioner::Mult(
const mfem::Vector &input,
mfem::Vector &output
) const {
const Clock::time_point start = Clock::now();
m_preconditioner->Mult(input, output);
const double elapsed = seconds_between(start, Clock::now());
++m_statistics.applications;
m_statistics.totalSeconds += elapsed;
m_statistics.maximumSeconds = std::max(m_statistics.maximumSeconds, elapsed);
}
void InstrumentedPreconditioner::ResetStatistics() const noexcept {
m_statistics = {};
}
const OperatorApplicationStatistics &InstrumentedPreconditioner::GetStatistics() const noexcept {
return m_statistics;
}
const PreconditionerLifecycleStatistics &InstrumentedPreconditioner::GetLifecycleStatistics() const noexcept {
return m_lifecycleStatistics;
}
const mfem::Solver &InstrumentedPreconditioner::GetPreconditioner() const noexcept {
return *m_preconditioner;
}
IdentityPreconditioner::IdentityPreconditioner(const int size) : mfem::Solver(size) {
if (size <= 0) {
throw std::invalid_argument("An identity preconditioner requires a positive dimension.");
}
}
void IdentityPreconditioner::SetOperator(const mfem::Operator &operation) {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument("The identity preconditioner received an incompatible operator.");
}
}
void IdentityPreconditioner::Mult(
const mfem::Vector &input,
mfem::Vector &output
) const {
if (input.Size() != Width()) {
throw std::invalid_argument("The identity preconditioner received an input with the wrong size.");
}
output = input;
}
FixedRightPreconditionedOperator::FixedRightPreconditionedOperator(
const mfem::Operator &jacobian,
const mfem::Solver &inversePreconditioner
)
: mfem::Operator(
jacobian.Height(),
inversePreconditioner.Width()
),
m_jacobian(std::addressof(jacobian)),
m_inversePreconditioner(std::addressof(inversePreconditioner)),
m_preconditionedDirection(inversePreconditioner.Height()) {
if (jacobian.Height() != jacobian.Width()) {
throw std::invalid_argument("A preconditioned stellar Jacobian must be square.");
}
if (inversePreconditioner.Height() != jacobian.Width() || inversePreconditioner.Width() != jacobian.Height()) {
throw std::invalid_argument("The inverse preconditioner does not map residuals into Jacobian states.");
}
if (Height() != Width()) {
throw std::invalid_argument("The fixed right-preconditioned product must be square.");
}
}
void FixedRightPreconditionedOperator::Mult(
const mfem::Vector &input,
mfem::Vector &output
) const {
if (input.Size() != Width()) {
throw std::invalid_argument("The right-preconditioned operator received an input with the wrong size.");
}
m_inversePreconditioner->Mult(input, m_preconditionedDirection);
m_jacobian->Mult(m_preconditionedDirection, output);
}
const mfem::Operator &FixedRightPreconditionedOperator::GetJacobian() const noexcept {
return *m_jacobian;
}
const mfem::Solver &FixedRightPreconditionedOperator::GetInversePreconditioner() const noexcept {
return *m_inversePreconditioner;
}
void ResidualHistoryMonitor::Reset() {
mfem::IterativeSolverMonitor::Reset();
m_history.clear();
}
void ResidualHistoryMonitor::MonitorResidual(
const int iteration,
const double norm,
const mfem::Vector &,
const bool final
) {
m_history.push_back({.iteration = iteration, .reportedNorm = norm, .final = final});
}
const std::vector<IterationResidualMeasurement> &ResidualHistoryMonitor::GetHistory() const noexcept {
return m_history;
}
DirectResidualMeasurement measureDirectResidual(
const mfem::Operator &jacobian,
const mfem::Vector &rightHandSide,
const mfem::Vector &solution,
const std::span<const operators::RootBlockDescriptor> residualBlocks,
const MPI_Comm communicator,
const double denominatorFloor
) {
if (jacobian.Height() != jacobian.Width() || rightHandSide.Size() != jacobian.Height() ||
solution.Size() != jacobian.Width()) {
throw std::invalid_argument("Direct residual measurement received incompatible linear-system dimensions.");
}
if (!std::isfinite(denominatorFloor) || denominatorFloor <= 0.0) {
throw std::invalid_argument("The direct-residual denominator floor must be finite and positive.");
}
verify_finite_vector(rightHandSide, "Direct residual measurement received a non-finite right-hand side.");
verify_finite_vector(solution, "Direct residual measurement received a non-finite solution.");
int expectedOffset = 0;
for (const operators::RootBlockDescriptor &block : residualBlocks) {
if (block.kind != operators::RootBlockKind::residual || block.offset != expectedOffset || block.size < 0 ||
block.offset + block.size > jacobian.Height() || !std::isfinite(block.scale) || block.scale <= 0.0) {
throw std::invalid_argument("Residual block descriptors do not form the canonical equation layout.");
}
expectedOffset += block.size;
}
if (expectedOffset != jacobian.Height()) {
throw std::invalid_argument("Residual block descriptors do not cover the complete equation vector.");
}
mfem::Vector action(jacobian.Height());
jacobian.Mult(solution, action);
if (action.Size() != rightHandSide.Size()) {
throw std::runtime_error("The Jacobian returned an action with the wrong size.");
}
mfem::Vector trueResidual(rightHandSide);
trueResidual -= action;
verify_finite_vector(trueResidual, "Direct residual measurement produced a non-finite residual.");
DirectResidualMeasurement measurement;
measurement.rightHandSideNorm = global_norm(rightHandSide, communicator);
measurement.trueResidualNorm = global_norm(trueResidual, communicator);
const double denominator = std::max(measurement.rightHandSideNorm, denominatorFloor);
measurement.relativeResidual = measurement.trueResidualNorm / denominator;
measurement.blocks.reserve(residualBlocks.size());
for (const operators::RootBlockDescriptor &block : residualBlocks) {
const mfem::Vector blockRightHandSide(
const_cast<mfem::real_t *>(rightHandSide.GetData()) + block.offset, block.size
);
const mfem::Vector blockResidual(trueResidual.GetData() + block.offset, block.size);
const double blockRightHandSideNorm = global_norm(blockRightHandSide, communicator);
const double blockResidualNorm = global_norm(blockResidual, communicator);
const double blockDenominator = std::max(blockRightHandSideNorm, denominatorFloor);
const double globalResidualFraction =
measurement.trueResidualNorm > denominatorFloor
? blockResidualNorm * blockResidualNorm /
(measurement.trueResidualNorm * measurement.trueResidualNorm)
: 0.0;
measurement.blocks.push_back(
{.stableId = std::string(block.stableId),
.size = block.size,
.descriptorScale = block.scale,
.rightHandSideNorm = blockRightHandSideNorm,
.trueResidualNorm = blockResidualNorm,
.blockRelativeResidual = blockResidualNorm / blockDenominator,
.scaledRightHandSideNorm = blockRightHandSideNorm / block.scale,
.scaledTrueResidualNorm = blockResidualNorm / block.scale,
.contributionToGlobalRelativeResidual = blockResidualNorm / denominator,
.fractionOfGlobalSquaredResidualNorm = globalResidualFraction}
);
}
return measurement;
}
LinearSolveMeasurement measureLinearSolve(
const mfem::IterativeSolver &iterativeSolver,
const mfem::Operator &jacobian,
const mfem::Vector &rightHandSide,
const mfem::Vector &solution,
const std::span<const operators::RootBlockDescriptor> residualBlocks,
const OperatorApplicationStatistics &jacobianStatistics,
const OperatorApplicationStatistics &inversePreconditionerStatistics,
const PreconditionerLifecycleStatistics &inversePreconditionerLifecycle,
const ResidualHistoryMonitor &monitor,
const double localSolveSeconds,
const MPI_Comm communicator,
const double denominatorFloor
) {
if (!std::isfinite(localSolveSeconds) || localSolveSeconds < 0.0) {
throw std::invalid_argument("A linear-solve duration must be finite and nonnegative.");
}
const DirectResidualMeasurement directResidual =
measureDirectResidual(jacobian, rightHandSide, solution, residualBlocks, communicator, denominatorFloor);
const double reportedInitial = iterativeSolver.GetInitialNorm();
const double reportedFinal = iterativeSolver.GetFinalNorm();
const double reportedReduction =
std::abs(reportedInitial) > denominatorFloor ? std::abs(reportedFinal) / std::abs(reportedInitial) : 0.0;
double digitsPerJacobianApplication = 0.0;
if (jacobianStatistics.applications > 0 && directResidual.relativeResidual >= 0.0 &&
std::isfinite(directResidual.relativeResidual)) {
digitsPerJacobianApplication = -std::log10(std::max(directResidual.relativeResidual, denominatorFloor)) /
static_cast<double>(jacobianStatistics.applications);
}
return {
.solverConverged = iterativeSolver.GetConverged(),
.outerIterations = iterativeSolver.GetNumIterations(),
.solverReportedInitialNorm = reportedInitial,
.solverReportedFinalNorm = reportedFinal,
.solverReportedResidualReduction = reportedReduction,
.trueResidualDigitsReducedPerJacobianApplication = digitsPerJacobianApplication,
.solveSecondsMaximumRank = maximum_rank_value(localSolveSeconds, communicator),
.jacobian = maximum_rank_statistics(jacobianStatistics, communicator),
.inversePreconditioner = maximum_rank_statistics(inversePreconditionerStatistics, communicator),
.inversePreconditionerLifecycle =
maximum_rank_lifecycle_statistics(inversePreconditionerLifecycle, communicator),
.directResidual = directResidual,
.reportedResidualHistory = monitor.GetHistory()
};
}
ArnoldiSpectralMeasurement measureArnoldiSpectrum(
const mfem::Operator &operation,
const mfem::Vector &initialDirection,
const MPI_Comm communicator,
const ArnoldiOptions &options
) {
if (operation.Height() != operation.Width() || operation.Width() <= 0) {
throw std::invalid_argument("Arnoldi diagnostics require a nonempty square operator.");
}
if (initialDirection.Size() != operation.Width()) {
throw std::invalid_argument("The Arnoldi initial direction has the wrong size.");
}
if (options.krylovDimension <= 0 || !std::isfinite(options.breakdownRelativeTolerance) ||
options.breakdownRelativeTolerance < 0.0 || !std::isfinite(options.ritzConvergenceRelativeTolerance) ||
options.ritzConvergenceRelativeTolerance < 0.0) {
throw std::invalid_argument("Arnoldi diagnostic options are invalid.");
}
verify_finite_vector(initialDirection, "Arnoldi diagnostics received a non-finite initial direction.");
const Clock::time_point measurementStart = Clock::now();
OperatorApplicationStatistics localApplicationStatistics;
const double initialNorm = global_norm(initialDirection, communicator);
if (!std::isfinite(initialNorm) || initialNorm <= 0.0) {
throw std::invalid_argument("Arnoldi diagnostics require a nonzero initial direction.");
}
const int requestedDimension = std::min(options.krylovDimension, operation.Width());
Eigen::MatrixXd hessenberg = Eigen::MatrixXd::Zero(requestedDimension + 1, requestedDimension);
std::vector<mfem::Vector> basis;
basis.reserve(static_cast<std::size_t>(requestedDimension + 1));
basis.emplace_back(initialDirection);
basis.back() /= initialNorm;
int achievedDimension{0};
bool invariantSubspaceFound{false};
for (int column = 0; column < requestedDimension; ++column) {
mfem::Vector candidate(operation.Height());
const Clock::time_point applicationStart = Clock::now();
operation.Mult(basis[static_cast<std::size_t>(column)], candidate);
const double applicationSeconds = seconds_between(applicationStart, Clock::now());
++localApplicationStatistics.applications;
localApplicationStatistics.totalSeconds += applicationSeconds;
localApplicationStatistics.maximumSeconds =
std::max(localApplicationStatistics.maximumSeconds, applicationSeconds);
if (candidate.Size() != operation.Height()) {
throw std::runtime_error("The Arnoldi operator returned a vector with the wrong size.");
}
verify_finite_vector(candidate, "The Arnoldi operator produced a non-finite vector.");
const double unorthogonalizedNorm = global_norm(candidate, communicator);
const int passCount = options.reorthogonalize ? 2 : 1;
for (int pass = 0; pass < passCount; ++pass) {
for (int row = 0; row <= column; ++row) {
const double projection = global_dot(basis[static_cast<std::size_t>(row)], candidate, communicator);
hessenberg(row, column) += projection;
candidate.Add(-projection, basis[static_cast<std::size_t>(row)]);
}
}
const double nextNorm = global_norm(candidate, communicator);
hessenberg(column + 1, column) = nextNorm;
achievedDimension = column + 1;
const double breakdownScale = std::max(unorthogonalizedNorm, 1.0);
if (nextNorm <= options.breakdownRelativeTolerance * breakdownScale) {
invariantSubspaceFound = true;
break;
}
if (column + 1 < requestedDimension) {
candidate /= nextNorm;
basis.push_back(std::move(candidate));
}
}
if (achievedDimension <= 0) {
throw std::runtime_error("Arnoldi diagnostics did not construct a Krylov projection.");
}
const Eigen::MatrixXd projected = copy_hessenberg(hessenberg, achievedDimension, achievedDimension);
const Eigen::MatrixXd projectedRectangular =
copy_hessenberg(hessenberg, achievedDimension + 1, achievedDimension);
Eigen::EigenSolver<Eigen::MatrixXd> eigenSolver(projected, true);
if (eigenSolver.info() != Eigen::Success) {
throw std::runtime_error("The projected Arnoldi eigenproblem did not converge.");
}
Eigen::JacobiSVD<Eigen::MatrixXd> singularValueDecomposition(projectedRectangular);
if (singularValueDecomposition.info() != Eigen::Success) {
throw std::runtime_error("The projected Arnoldi singular-value problem did not converge.");
}
ArnoldiSpectralMeasurement measurement;
measurement.requestedDimension = requestedDimension;
measurement.achievedDimension = achievedDimension;
measurement.invariantSubspaceFound = invariantSubspaceFound;
const OperatorApplicationStatistics globalApplicationStatistics =
maximum_rank_statistics(localApplicationStatistics, communicator);
measurement.operatorApplications = globalApplicationStatistics.applications;
measurement.operatorApplicationSecondsMaximumRank = globalApplicationStatistics.totalSeconds;
measurement.operatorMaximumApplicationSecondsMaximumRank = globalApplicationStatistics.maximumSeconds;
measurement.ritzValues.reserve(static_cast<std::size_t>(achievedDimension));
const Eigen::VectorXd singularValues = singularValueDecomposition.singularValues();
measurement.projectedLargestSingularValue = singularValues(0);
measurement.projectedSmallestSingularValue = singularValues(singularValues.size() - 1);
measurement.projectedConditionProxy =
measurement.projectedSmallestSingularValue > 0.0
? measurement.projectedLargestSingularValue / measurement.projectedSmallestSingularValue
: std::numeric_limits<double>::infinity();
const double finalSubdiagonal = hessenberg(achievedDimension, achievedDimension - 1);
std::complex<double> centroid{0.0, 0.0};
const auto eigenvalues = eigenSolver.eigenvalues();
const auto eigenvectors = eigenSolver.eigenvectors();
for (int index = 0; index < achievedDimension; ++index) {
const std::complex<double> eigenvalue = eigenvalues(index);
const double eigenvectorNorm = eigenvectors.col(index).norm();
const double residualEstimate =
eigenvectorNorm > 0.0
? std::abs(finalSubdiagonal * eigenvectors(achievedDimension - 1, index)) / eigenvectorNorm
: std::numeric_limits<double>::infinity();
const double convergenceScale = std::max(std::abs(eigenvalue), 1.0);
const double relativeResidualEstimate = residualEstimate / convergenceScale;
const bool converged = relativeResidualEstimate <= options.ritzConvergenceRelativeTolerance;
measurement.ritzValues.push_back(
{.realPart = eigenvalue.real(),
.imaginaryPart = eigenvalue.imag(),
.magnitude = std::abs(eigenvalue),
.distanceFromOne = std::abs(eigenvalue - std::complex<double>{1.0, 0.0}),
.residualEstimate = residualEstimate,
.relativeResidualEstimate = relativeResidualEstimate,
.converged = converged}
);
centroid += eigenvalue;
measurement.convergedRitzValueCount += converged ? 1 : 0;
measurement.negativeRealPartCount += eigenvalue.real() < 0.0 ? 1 : 0;
}
centroid /= static_cast<double>(achievedDimension);
measurement.centroidRealPart = centroid.real();
measurement.centroidImaginaryPart = centroid.imag();
measurement.minimumMagnitude = std::numeric_limits<double>::infinity();
measurement.minimumRealPart = std::numeric_limits<double>::infinity();
measurement.maximumRealPart = -std::numeric_limits<double>::infinity();
double squaredDistanceFromOne{0.0};
double squaredClusterRadius{0.0};
for (const RitzValueMeasurement &ritz : measurement.ritzValues) {
const std::complex<double> value{ritz.realPart, ritz.imaginaryPart};
measurement.minimumMagnitude = std::min(measurement.minimumMagnitude, ritz.magnitude);
measurement.maximumMagnitude = std::max(measurement.maximumMagnitude, ritz.magnitude);
measurement.minimumRealPart = std::min(measurement.minimumRealPart, ritz.realPart);
measurement.maximumRealPart = std::max(measurement.maximumRealPart, ritz.realPart);
measurement.maximumAbsoluteImaginaryPart =
std::max(measurement.maximumAbsoluteImaginaryPart, std::abs(ritz.imaginaryPart));
squaredDistanceFromOne += ritz.distanceFromOne * ritz.distanceFromOne;
squaredClusterRadius += std::norm(value - centroid);
double pairDefect = std::numeric_limits<double>::infinity();
for (const RitzValueMeasurement &candidate : measurement.ritzValues) {
pairDefect = std::min(
pairDefect,
std::abs(std::complex<double>{candidate.realPart, candidate.imaginaryPart} - std::conj(value))
);
}
measurement.conjugatePairDefect = std::max(measurement.conjugatePairDefect, pairDefect);
}
measurement.rmsDistanceFromOne = std::sqrt(squaredDistanceFromOne / achievedDimension);
measurement.rmsClusterRadius = std::sqrt(squaredClusterRadius / achievedDimension);
const double projectedFrobeniusSquared = projected.squaredNorm();
if (projectedFrobeniusSquared > 0.0) {
const Eigen::MatrixXd normalityCommutator =
projected.transpose() * projected - projected * projected.transpose();
measurement.projectedDepartureFromNormality = normalityCommutator.norm() / projectedFrobeniusSquared;
}
const Eigen::MatrixXd hermitianPart = 0.5 * (projected + projected.transpose());
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> fieldOfValuesSolver(hermitianPart);
if (fieldOfValuesSolver.info() != Eigen::Success) {
throw std::runtime_error("The projected field-of-values problem did not converge.");
}
measurement.projectedFieldOfValuesMinimumRealPart = fieldOfValuesSolver.eigenvalues().minCoeff();
measurement.projectedFieldOfValuesMaximumRealPart = fieldOfValuesSolver.eigenvalues().maxCoeff();
const double localMeasurementSeconds = seconds_between(measurementStart, Clock::now());
const double localNonApplicationSeconds =
std::max(localMeasurementSeconds - localApplicationStatistics.totalSeconds, 0.0);
measurement.measurementSecondsMaximumRank = maximum_rank_value(localMeasurementSeconds, communicator);
measurement.nonApplicationSecondsMaximumRank = maximum_rank_value(localNonApplicationSeconds, communicator);
return measurement;
}
std::vector<RitzValueMeasurement> selectRitzValues(
const ArnoldiSpectralMeasurement &measurement,
const RitzValueOrdering ordering,
const int count
) {
if (count < 0) {
throw std::invalid_argument("The requested Ritz-value count must be nonnegative.");
}
std::vector<RitzValueMeasurement> selected;
selected.reserve(measurement.ritzValues.size());
for (const RitzValueMeasurement &value : measurement.ritzValues) {
if (value.converged) {
selected.push_back(value);
}
}
std::ranges::sort(selected, [ordering](const RitzValueMeasurement &left, const RitzValueMeasurement &right) {
switch (ordering) {
case RitzValueOrdering::closest_to_zero:
return left.magnitude < right.magnitude;
case RitzValueOrdering::farthest_from_one:
return left.distanceFromOne > right.distanceFromOne;
case RitzValueOrdering::smallest_real_part:
return left.realPart < right.realPart;
case RitzValueOrdering::largest_magnitude:
return left.magnitude > right.magnitude;
}
return false;
});
if (static_cast<int>(selected.size()) > count) {
selected.resize(static_cast<std::size_t>(count));
}
return selected;
}
} // namespace mean_field::solver

View File

@@ -0,0 +1,312 @@
module;
#include <compare>
#include <concepts>
#include <string_view>
#include <type_traits>
export module mean_field:dimensions.quantities;
export namespace mean_field::dimensions {
/*
* QuantityValue provides semantic strong typing for scalar physical
* values expressed in the unit system selected by a model. It does not
* perform dimensional algebra or unit conversion.
*/
struct PhysicalQuantity { };
struct ThermodynamicQuantity : PhysicalQuantity { };
template <typename Candidate>
concept PhysicalQuantityType =
std::same_as<Candidate, std::remove_cv_t<Candidate>> && std::derived_from<Candidate, PhysicalQuantity>;
template <typename Candidate>
concept ThermodynamicQuantityType =
PhysicalQuantityType<Candidate> && std::derived_from<Candidate, ThermodynamicQuantity>;
namespace quantity {
struct Dimensionless final : PhysicalQuantity {
static constexpr std::string_view identifier = "dimensionless";
};
struct Mass final : PhysicalQuantity {
static constexpr std::string_view identifier = "mass";
};
struct Length final : PhysicalQuantity {
static constexpr std::string_view identifier = "length";
};
struct Time final : PhysicalQuantity {
static constexpr std::string_view identifier = "time";
};
struct Area final : PhysicalQuantity {
static constexpr std::string_view identifier = "area";
};
struct Volume final : PhysicalQuantity {
static constexpr std::string_view identifier = "volume";
};
struct Density final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "density";
};
struct SurfaceDensity final : PhysicalQuantity {
static constexpr std::string_view identifier = "surface_density";
};
struct NumberDensity final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "number_density";
};
struct Pressure final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "pressure";
};
struct Temperature final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "temperature";
};
struct Entropy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "entropy";
};
struct SpecificEntropy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "specific_entropy";
};
struct ChemicalPotential final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "chemical_potential";
};
struct Energy final : PhysicalQuantity {
static constexpr std::string_view identifier = "energy";
};
struct InternalEnergy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "internal_energy";
};
struct SpecificEnergy final : PhysicalQuantity {
static constexpr std::string_view identifier = "specific_energy";
};
struct SpecificInternalEnergy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "specific_internal_energy";
};
struct SpecificEnthalpy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "specific_enthalpy";
};
struct EnergyDensity final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "energy_density";
};
struct GravitationalPotential final : PhysicalQuantity {
static constexpr std::string_view identifier = "gravitational_potential";
};
struct Velocity final : PhysicalQuantity {
static constexpr std::string_view identifier = "velocity";
};
struct Acceleration final : PhysicalQuantity {
static constexpr std::string_view identifier = "acceleration";
};
struct Frequency final : PhysicalQuantity {
static constexpr std::string_view identifier = "frequency";
};
struct AngularVelocity final : PhysicalQuantity {
static constexpr std::string_view identifier = "angular_velocity";
};
struct Momentum final : PhysicalQuantity {
static constexpr std::string_view identifier = "momentum";
};
struct AngularMomentum final : PhysicalQuantity {
static constexpr std::string_view identifier = "angular_momentum";
};
struct MomentOfInertia final : PhysicalQuantity {
static constexpr std::string_view identifier = "moment_of_inertia";
};
struct Force final : PhysicalQuantity {
static constexpr std::string_view identifier = "force";
};
struct Torque final : PhysicalQuantity {
static constexpr std::string_view identifier = "torque";
};
struct Power final : PhysicalQuantity {
static constexpr std::string_view identifier = "power";
};
struct Luminosity final : PhysicalQuantity {
static constexpr std::string_view identifier = "luminosity";
};
struct MassFlowRate final : PhysicalQuantity {
static constexpr std::string_view identifier = "mass_flow_rate";
};
struct Opacity final : PhysicalQuantity {
static constexpr std::string_view identifier = "opacity";
};
struct DynamicViscosity final : PhysicalQuantity {
static constexpr std::string_view identifier = "dynamic_viscosity";
};
struct KinematicViscosity final : PhysicalQuantity {
static constexpr std::string_view identifier = "kinematic_viscosity";
};
struct MagneticFluxDensity final : PhysicalQuantity {
static constexpr std::string_view identifier = "magnetic_flux_density";
};
} // namespace quantity
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template <PhysicalQuantityType 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 operator+(
const QuantityValue &lhs,
const QuantityValue &rhs
) noexcept {
return QuantityValue{lhs.m_value + rhs.m_value};
}
friend constexpr QuantityValue operator-(
const QuantityValue &lhs,
const QuantityValue &rhs
) noexcept {
return QuantityValue{lhs.m_value - rhs.m_value};
}
template <Numeric Scalar>
friend constexpr QuantityValue operator*(
const QuantityValue &lhs,
const Scalar rhs
) noexcept {
return QuantityValue{lhs.m_value * static_cast<double>(rhs)};
}
template <Numeric Scalar>
friend constexpr QuantityValue operator*(
const Scalar lhs,
const QuantityValue &rhs
) noexcept {
return QuantityValue{static_cast<double>(lhs) * rhs.m_value};
}
template <Numeric Scalar>
friend constexpr QuantityValue operator/(
const QuantityValue &lhs,
const Scalar rhs
) noexcept {
return QuantityValue{lhs.m_value / static_cast<double>(rhs)};
}
template <Numeric Scalar>
friend constexpr std::partial_ordering operator<=>(
const QuantityValue &lhs,
const Scalar rhs
) noexcept {
return lhs.m_value <=> static_cast<double>(rhs);
}
template <Numeric Scalar>
friend constexpr std::partial_ordering operator<=>(
const Scalar lhs,
const QuantityValue &rhs
) noexcept {
return static_cast<double>(lhs) <=> rhs.m_value;
}
friend constexpr std::partial_ordering operator<=>(
const QuantityValue &lhs,
const QuantityValue &rhs
) noexcept {
return lhs.m_value <=> rhs.m_value;
}
private:
double m_value;
};
template <typename Candidate> struct IsQuantityValue : std::false_type { };
template <PhysicalQuantityType 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 <PhysicalQuantityType Quantity> struct QuantityOf<QuantityValue<Quantity>> {
using Type = Quantity;
};
template <QuantityValueType Value> using QuantityOfT = typename QuantityOf<std::remove_cvref_t<Value>>::Type;
using DimensionlessValue = QuantityValue<quantity::Dimensionless>;
using MassValue = QuantityValue<quantity::Mass>;
using LengthValue = QuantityValue<quantity::Length>;
using TimeValue = QuantityValue<quantity::Time>;
using AreaValue = QuantityValue<quantity::Area>;
using VolumeValue = QuantityValue<quantity::Volume>;
using DensityValue = QuantityValue<quantity::Density>;
using SurfaceDensityValue = QuantityValue<quantity::SurfaceDensity>;
using NumberDensityValue = QuantityValue<quantity::NumberDensity>;
using PressureValue = QuantityValue<quantity::Pressure>;
using TemperatureValue = QuantityValue<quantity::Temperature>;
using EntropyValue = QuantityValue<quantity::Entropy>;
using SpecificEntropyValue = QuantityValue<quantity::SpecificEntropy>;
using ChemicalPotentialValue = QuantityValue<quantity::ChemicalPotential>;
using EnergyValue = QuantityValue<quantity::Energy>;
using InternalEnergyValue = QuantityValue<quantity::InternalEnergy>;
using SpecificEnergyValue = QuantityValue<quantity::SpecificEnergy>;
using SpecificInternalEnergyValue = QuantityValue<quantity::SpecificInternalEnergy>;
using SpecificEnthalpyValue = QuantityValue<quantity::SpecificEnthalpy>;
using EnergyDensityValue = QuantityValue<quantity::EnergyDensity>;
using GravitationalPotentialValue = QuantityValue<quantity::GravitationalPotential>;
using VelocityValue = QuantityValue<quantity::Velocity>;
using AccelerationValue = QuantityValue<quantity::Acceleration>;
using FrequencyValue = QuantityValue<quantity::Frequency>;
using AngularVelocityValue = QuantityValue<quantity::AngularVelocity>;
using MomentumValue = QuantityValue<quantity::Momentum>;
using AngularMomentumValue = QuantityValue<quantity::AngularMomentum>;
using MomentOfInertiaValue = QuantityValue<quantity::MomentOfInertia>;
using ForceValue = QuantityValue<quantity::Force>;
using TorqueValue = QuantityValue<quantity::Torque>;
using PowerValue = QuantityValue<quantity::Power>;
using LuminosityValue = QuantityValue<quantity::Luminosity>;
using MassFlowRateValue = QuantityValue<quantity::MassFlowRate>;
using OpacityValue = QuantityValue<quantity::Opacity>;
using DynamicViscosityValue = QuantityValue<quantity::DynamicViscosity>;
using KinematicViscosityValue = QuantityValue<quantity::KinematicViscosity>;
using MagneticFluxDensityValue = QuantityValue<quantity::MagneticFluxDensity>;
} // namespace mean_field::dimensions

View File

@@ -91,10 +91,10 @@ export namespace mean_field::eos {
template <typename Candidate>
concept BarotropicClosureEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, DensityFromSpecificEnthalpy> &&
SupportsPartialDerivative<Candidate, DensityFromSpecificEnthalpy, quantity::SpecificEnthalpy>;
SupportsPartialDerivative<Candidate, DensityFromSpecificEnthalpy, dimensions::quantity::SpecificEnthalpy>;
template <typename Candidate>
concept PressureForceEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, PressureFromSpecificEnthalpy> &&
SupportsPartialDerivative<Candidate, PressureFromSpecificEnthalpy, quantity::SpecificEnthalpy>;
SupportsPartialDerivative<Candidate, PressureFromSpecificEnthalpy, dimensions::quantity::SpecificEnthalpy>;
} // namespace mean_field::eos

View File

@@ -8,6 +8,11 @@ export import :eos.evaluation;
export namespace mean_field::eos {
class Polytrope final {
public:
struct Parameters final {
double n;
double K;
};
using Relations = RelationCatalog<
PressureFromDensity,
PressureFromSpecificEnthalpy,
@@ -15,6 +20,13 @@ export namespace mean_field::eos {
SpecificEnthalpyFromPressure,
DensityFromSpecificEnthalpy>;
explicit Polytrope(const Parameters parameters)
: Polytrope(
parameters.n,
parameters.K
) {
}
Polytrope(
const double polytropic_index,
const double polytropic_constant
@@ -56,125 +68,131 @@ export namespace mean_field::eos {
return m_enthalpy_scale;
}
[[nodiscard]] PressureValue evaluate(
[[nodiscard]] dimensions::PressureValue evaluate(
PressureFromDensity,
const DensityValue density
const dimensions::DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return PressureValue{0.0};
return dimensions::PressureValue{0.0};
}
return PressureValue{m_polytropic_constant * std::pow(density.value(), 1.0 + 1.0 / m_polytropic_index)};
return dimensions::PressureValue{
m_polytropic_constant * std::pow(density.value(), 1.0 + 1.0 / m_polytropic_index)
};
}
[[nodiscard]] SpecificEnthalpyValue evaluate(
[[nodiscard]] dimensions::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromDensity,
const DensityValue density
const dimensions::DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return SpecificEnthalpyValue{0.0};
return dimensions::SpecificEnthalpyValue{0.0};
}
return SpecificEnthalpyValue{m_enthalpy_scale * std::pow(density.value(), 1.0 / m_polytropic_index)};
return dimensions::SpecificEnthalpyValue{
m_enthalpy_scale * std::pow(density.value(), 1.0 / m_polytropic_index)
};
}
[[nodiscard]] DensityValue evaluate(
[[nodiscard]] dimensions::DensityValue evaluate(
DensityFromSpecificEnthalpy,
const SpecificEnthalpyValue specificEnthalpy
const dimensions::SpecificEnthalpyValue specificEnthalpy
) const {
validate_finite(specificEnthalpy.value(), "specific enthalpy");
if (specificEnthalpy.value() <= 0.0) {
return DensityValue{0.0};
return dimensions::DensityValue{0.0};
}
return DensityValue{std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index)};
return dimensions::DensityValue{std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index)};
}
[[nodiscard]] PressureValue evaluate(
[[nodiscard]] dimensions::PressureValue evaluate(
PressureFromSpecificEnthalpy,
const SpecificEnthalpyValue specificEnthalpy
const dimensions::SpecificEnthalpyValue specificEnthalpy
) const {
const DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
const dimensions::DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
if (specificEnthalpy.value() <= 0.0) {
return PressureValue{0.0};
return dimensions::PressureValue{0.0};
}
return PressureValue{density.value() * specificEnthalpy.value() / (m_polytropic_index + 1.0)};
return dimensions::PressureValue{density.value() * specificEnthalpy.value() / (m_polytropic_index + 1.0)};
}
[[nodiscard]] SpecificEnthalpyValue evaluate(
[[nodiscard]] dimensions::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromPressure,
const PressureValue pressure
const dimensions::PressureValue pressure
) const {
validate_nonnegativity(pressure.value(), "pressure");
if (pressure.value() == 0.0) {
return SpecificEnthalpyValue{0.0};
return dimensions::SpecificEnthalpyValue{0.0};
}
const double indexPlusOne = m_polytropic_index + 1.0;
return SpecificEnthalpyValue{
return dimensions::SpecificEnthalpyValue{
indexPlusOne * std::pow(m_polytropic_constant, m_polytropic_index / indexPlusOne) *
std::pow(pressure.value(), 1.0 / indexPlusOne)
};
}
[[nodiscard]] PartialDerivative<
quantity::Density,
quantity::SpecificEnthalpy>
dimensions::quantity::Density,
dimensions::quantity::SpecificEnthalpy>
partialDerivative(
DensityFromSpecificEnthalpy,
WithRespectTo<quantity::SpecificEnthalpy>,
const SpecificEnthalpyValue specificEnthalpy
WithRespectTo<dimensions::quantity::SpecificEnthalpy>,
const dimensions::SpecificEnthalpyValue specificEnthalpy
) const {
validate_finite(specificEnthalpy.value(), "specific enthalpy");
if (specificEnthalpy.value() < 0.0) {
return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{0.0};
return PartialDerivative<dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy>{0.0};
}
if (specificEnthalpy.value() == 0.0) {
return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{
return PartialDerivative<dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy>{
m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0
};
}
return PartialDerivative<quantity::Density, quantity::SpecificEnthalpy>{
return PartialDerivative<dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy>{
m_polytropic_index / m_enthalpy_scale *
std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index - 1.0)
};
}
[[nodiscard]] PartialDerivative<
quantity::Pressure,
quantity::SpecificEnthalpy>
dimensions::quantity::Pressure,
dimensions::quantity::SpecificEnthalpy>
partialDerivative(
PressureFromSpecificEnthalpy,
WithRespectTo<quantity::SpecificEnthalpy>,
const SpecificEnthalpyValue specificEnthalpy
WithRespectTo<dimensions::quantity::SpecificEnthalpy>,
const dimensions::SpecificEnthalpyValue specificEnthalpy
) const {
const DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
const dimensions::DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
return PartialDerivative<quantity::Pressure, quantity::SpecificEnthalpy>{density.value()};
return PartialDerivative<dimensions::quantity::Pressure, dimensions::quantity::SpecificEnthalpy>{
density.value()
};
}
[[nodiscard]] PartialDerivative<
quantity::Pressure,
quantity::Density>
dimensions::quantity::Pressure,
dimensions::quantity::Density>
partialDerivative(
PressureFromDensity,
WithRespectTo<quantity::Density>,
const DensityValue density
WithRespectTo<dimensions::quantity::Density>,
const dimensions::DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return PartialDerivative<quantity::Pressure, quantity::Density>{0.0};
return PartialDerivative<dimensions::quantity::Pressure, dimensions::quantity::Density>{0.0};
}
return PartialDerivative<quantity::Pressure, quantity::Density>{
return PartialDerivative<dimensions::quantity::Pressure, dimensions::quantity::Density>{
m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) *
std::pow(density.value(), 1.0 / m_polytropic_index)
};

View File

@@ -13,10 +13,10 @@ export namespace mean_field::eos {
ThermodynamicQuantityType InputQuantity,
typename SurfaceState>
[[nodiscard]] constexpr auto pressureSurfaceRelationInput(
const PressureValue targetPressure,
const dimensions::PressureValue targetPressure,
const SurfaceState &state
) {
if constexpr (std::same_as<InputQuantity, quantity::Pressure>) {
if constexpr (std::same_as<InputQuantity, dimensions::quantity::Pressure>) {
return targetPressure;
} else {
return state.value(InputQuantity{});
@@ -30,9 +30,9 @@ export namespace mean_field::eos {
template <
typename EquationOfState,
typename SurfaceState>
[[nodiscard]] static QuantityValue<CarrierQuantity> requiredCarrierValue(
[[nodiscard]] static dimensions::QuantityValue<CarrierQuantity> requiredCarrierValue(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const dimensions::PressureValue targetPressure,
const SurfaceState &state
) {
return evaluate<CarrierQuantity>(
@@ -47,11 +47,11 @@ export namespace mean_field::eos {
typename SurfaceVariation>
[[nodiscard]] static double inputJacobianContribution(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const dimensions::PressureValue targetPressure,
const SurfaceState &state,
const SurfaceVariation &variation
) {
if constexpr (std::same_as<InputQuantity, quantity::Pressure>) {
if constexpr (std::same_as<InputQuantity, dimensions::quantity::Pressure>) {
return 0.0;
} else {
const auto derivative = partialDerivative<CarrierQuantity, InputQuantity>(
@@ -67,7 +67,7 @@ export namespace mean_field::eos {
typename SurfaceVariation>
[[nodiscard]] static double carrierCorrectionJacobianAction(
const EquationOfState &equationOfState,
const PressureValue targetPressure,
const dimensions::PressureValue targetPressure,
const SurfaceState &state,
const SurfaceVariation &variation
) {
@@ -92,18 +92,18 @@ export namespace mean_field::eos {
ResolvedPressureSurfaceRelation(
const EquationOfState &equationOfState,
const PressureValue targetPressure
const dimensions::PressureValue targetPressure
) noexcept
: m_equationOfState(std::addressof(equationOfState)),
m_targetPressure(targetPressure) {
}
[[nodiscard]] PressureValue targetPressure() const noexcept {
[[nodiscard]] dimensions::PressureValue targetPressure() const noexcept {
return m_targetPressure;
}
template <typename SurfaceState>
[[nodiscard]] QuantityValue<CarrierQuantity> requiredCarrierValue(const SurfaceState &state) const {
[[nodiscard]] dimensions::QuantityValue<CarrierQuantity> requiredCarrierValue(const SurfaceState &state) const {
return detail::PressureSurfaceRelationOperations<RelationType>::requiredCarrierValue(
*m_equationOfState, m_targetPressure, state
);
@@ -123,6 +123,6 @@ export namespace mean_field::eos {
private:
const EquationOfState *m_equationOfState;
PressureValue m_targetPressure;
dimensions::PressureValue m_targetPressure;
};
} // namespace mean_field::eos

View File

@@ -2,132 +2,42 @@ module;
#include <compare>
#include <concepts>
#include <string_view>
#include <type_traits>
export module mean_field:eos.quantities;
export import :dimensions.quantities;
export namespace mean_field::eos {
struct ThermodynamicQuantity { };
// Compatibility names for the thermodynamic subset now owned by the
// general dimensions partition.
using ThermodynamicQuantity = dimensions::ThermodynamicQuantity;
template <typename Candidate>
concept ThermodynamicQuantityType =
std::same_as<Candidate, std::remove_cv_t<Candidate>> && std::derived_from<Candidate, ThermodynamicQuantity>;
concept ThermodynamicQuantityType = dimensions::ThermodynamicQuantityType<Candidate>;
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";
};
using Density = dimensions::quantity::Density;
using Pressure = dimensions::quantity::Pressure;
using SpecificEnthalpy = dimensions::quantity::SpecificEnthalpy;
} // namespace quantity
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
concept Numeric = dimensions::Numeric<T>;
template <ThermodynamicQuantityType Quantity> class QuantityValue final {
public:
explicit constexpr QuantityValue(const double value) noexcept : m_value(value) {
}
template <ThermodynamicQuantityType Quantity> using QuantityValue = dimensions::QuantityValue<Quantity>;
[[nodiscard]] constexpr double value() const noexcept {
return m_value;
}
using DensityValue = dimensions::DensityValue;
using PressureValue = dimensions::PressureValue;
using SpecificEnthalpyValue = dimensions::SpecificEnthalpyValue;
[[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> using IsQuantityValue = dimensions::IsQuantityValue<Candidate>;
template <typename Candidate>
concept QuantityValueType = IsQuantityValue<std::remove_cvref_t<Candidate>>::value;
concept QuantityValueType =
dimensions::QuantityValueType<Candidate> && ThermodynamicQuantityType<dimensions::QuantityOfT<Candidate>>;
template <typename Candidate> struct QuantityOf;
template <typename Candidate> using QuantityOf = dimensions::QuantityOf<Candidate>;
template <ThermodynamicQuantityType Quantity> struct QuantityOf<QuantityValue<Quantity>> {
using Type = Quantity;
};
template <QuantityValueType Value> using QuantityOfT = typename QuantityOf<std::remove_cvref_t<Value>>::Type;
template <QuantityValueType Value> using QuantityOfT = dimensions::QuantityOfT<Value>;
template <ThermodynamicQuantityType OutputQuantity, ThermodynamicQuantityType InputQuantity>
class PartialDerivative final {
@@ -163,7 +73,7 @@ export namespace mean_field::eos {
InputQuantity> &rhs
) noexcept;
template <Numeric rhsT>
template <Numeric Scalar>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
@@ -171,21 +81,21 @@ export namespace mean_field::eos {
const PartialDerivative<
OutputQuantity,
InputQuantity> &,
rhsT
Scalar
) noexcept;
template <Numeric lhsT>
template <Numeric Scalar>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator*(
lhsT,
Scalar,
const PartialDerivative<
OutputQuantity,
InputQuantity> &
) noexcept;
template <Numeric rhsT>
template <Numeric Scalar>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
@@ -193,22 +103,22 @@ export namespace mean_field::eos {
const PartialDerivative<
OutputQuantity,
InputQuantity> &,
rhsT
Scalar
) noexcept;
template <Numeric cmpT>
template <Numeric Scalar>
friend constexpr std::partial_ordering operator<=>(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
cmpT rhs
Scalar rhs
) noexcept {
return lhs.m_value <=> static_cast<double>(rhs);
}
template <Numeric cmpT>
template <Numeric Scalar>
friend constexpr std::partial_ordering operator<=>(
cmpT lhs,
Scalar lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs

View File

@@ -85,9 +85,11 @@ export namespace mean_field::eos {
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>;
using PressureFromDensity = Relation<dimensions::quantity::Pressure, dimensions::quantity::Density>;
using PressureFromSpecificEnthalpy =
Relation<dimensions::quantity::Pressure, dimensions::quantity::SpecificEnthalpy>;
using SpecificEnthalpyFromDensity = Relation<dimensions::quantity::SpecificEnthalpy, dimensions::quantity::Density>;
using SpecificEnthalpyFromPressure =
Relation<dimensions::quantity::SpecificEnthalpy, dimensions::quantity::Pressure>;
using DensityFromSpecificEnthalpy = Relation<dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy>;
} // namespace mean_field::eos

View File

@@ -0,0 +1,64 @@
module;
#include <memory>
#include <stdexcept>
export module mean_field:equilibrium.stellar_discretization;
export import :fem;
export import :mapping.domain_mapper;
export namespace mean_field::equilibrium {
/*
* An explicit, non-owning view of the numerical discretization used by a
* stellar equilibrium problem. The referenced FEM and mapper must outlive
* every problem and structure that uses this view.
*
* Ownership cannot move here yet because FEM currently also contains
* mutable field workspaces. Separating those workspaces is a prerequisite
* for shared discretization ownership by solved Structure objects.
*/
class StellarDiscretization final {
public:
explicit StellarDiscretization(fem::FEM &finiteElementModel)
: StellarDiscretization(
finiteElementModel,
RequireDomainMapper(finiteElementModel)
) {
}
StellarDiscretization(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper
)
: m_finiteElementModel(std::addressof(finiteElementModel)),
m_domainMapper(std::addressof(domainMapper)) {
if (!finiteElementModel.okay()) {
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
}
}
[[nodiscard]] fem::FEM &finiteElementModel() const noexcept {
return *m_finiteElementModel;
}
[[nodiscard]] const mapping::DomainMapper &domainMapper() const noexcept {
return *m_domainMapper;
}
[[nodiscard]] bool isCurrent() const noexcept {
return m_finiteElementModel != nullptr && m_domainMapper != nullptr && m_finiteElementModel->okay();
}
private:
[[nodiscard]] static const mapping::DomainMapper &RequireDomainMapper(const fem::FEM &finiteElementModel) {
if (finiteElementModel.domainMapperStateless == nullptr) {
throw std::invalid_argument("A stellar discretization requires a domain mapper.");
}
return *finiteElementModel.domainMapperStateless;
}
fem::FEM *m_finiteElementModel;
const mapping::DomainMapper *m_domainMapper;
};
} // namespace mean_field::equilibrium

View File

@@ -208,6 +208,9 @@ export namespace mean_field::field {
using FormList = TypeList<Form::MeshExtension, Form::GravityForce, Form::CentrifugalForce, Form::ErrorNorm>;
};
// Current realization of MultiplierFor<FixedTotalMass>. This remains a
// barotrope-specific field representation: the specification compiler,
// rather than the universal state registry, decides when it is present.
struct BarotropicConstant {
static constexpr std::string_view name = "barotropic_constant";
@@ -226,6 +229,26 @@ export namespace mean_field::field {
static_assert(constraintsAreValid);
};
// Solver border generated by FixedCentralDensity. This is deliberately a
// non-spatial numerical coordinate rather than a physical stellar field.
struct CentralDensityBorder {
static constexpr std::string_view name = "central_density_border";
using Support = NonSpatialSupport;
struct Scalar final : GlobalScalarQ {
static constexpr std::string_view symbol = "lambda_rho_c";
};
using Quantities = TypeList<Scalar>;
using Constraints = TypeList<>;
using FormList = TypeList<>;
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
static_assert(constraintsAreValid);
};
// =========================================================================
// Specific enthalpy
//

View File

@@ -78,6 +78,7 @@ export namespace mean_field::mapping {
int m_dimension;
mfem::Vector m_shape;
mfem::DenseMatrix m_reference_dshape;
mfem::DenseMatrix m_mesh_dshape;
mfem::Vector m_field_value;
mfem::DenseMatrix m_field_jacobian;
@@ -178,7 +179,8 @@ export namespace mean_field::mapping {
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
mfem::Vector &value,
mfem::DenseMatrix &jacobian
mfem::DenseMatrix &jacobian,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const;
[[nodiscard]] MappingStatus EvaluateCompactificationCoordinate(
@@ -186,7 +188,19 @@ export namespace mean_field::mapping {
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
CompactificationPointData &point_data
CompactificationPointData &point_data,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const;
[[nodiscard]] MappingStatus EvaluatePointVariationImpl(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const MappingPointContext &base_context,
Workspace &workspace,
MappingPointVariation &variation,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const;
[[nodiscard]] static mfem::ElementTransformation &SelectFaceElementTransformation(

View File

@@ -25,6 +25,7 @@ export import :integrators.viscosity;
export import :quadrature.policy;
export import :quadrature.mfem;
export import :solver.fields;
export import :solver.preconditioning_diagnostics;
export import :utils.blocks;
export import :operators.gravity_field;
export import :operators.gravity_field_jacobian;
@@ -51,9 +52,14 @@ export import :operators.context.rotational_displacement_force;
export import :operators.kernels.rotational_displacement_force;
export import :operators.prepared_rotational_displacement_force;
export import :operators.prepared_displacement_residual;
export import :dimensions.quantities;
export import :model.structure_profile;
export import :model.structure.base;
export import :model.structure.polytropic;
export import :model.specifications;
export import :model.typed_stellar;
export import :model.compiled_fixed_mass;
export import :model.compiled_fixed_central_density;
export import :eos.quantities;
export import :eos.relations;
export import :eos.concepts;
@@ -61,6 +67,7 @@ export import :eos.evaluation;
export import :eos.pressure_surface;
export import :eos.runtime;
export import :eos.polytrope;
export import :seed.lane_emden;
export import :surface.constant;
export import :surface.dependencies;
export import :surface.compiled;
@@ -73,7 +80,15 @@ export import :deformation.vacuum_extension;
export import :deformation.radial_extensions;
export import :deformation.domain_deformation;
export import :model.stellar;
export import :operators.root_manifest;
export import :operators.prepared_constraint;
export import :operators.prepared_mass_normalization;
export import :operators.prepared_central_density;
export import :operators.prepared_centering_constraint;
export import :operators.prepared_surface_constraint;
export import :operators.prepared_stellar_equilibrium;
export import :operators.prepared_central_density_stellar_equilibrium;
export import :equilibrium.stellar_discretization;
export import :operators.stellar_equilibrium_problem;
export import :seed.stellar_equilibrium_projection;
export import :operators.stellar_equilibrium_system;

View File

@@ -0,0 +1,95 @@
module;
#include <concepts>
#include <type_traits>
export module mean_field:model.compiled_fixed_central_density;
export import :eos.polytrope;
export import :field.registry;
export import :model.compiled_fixed_mass;
export import :utils.blocks;
export namespace mean_field::models {
struct CentralDensityLayoutRequest final {
using SpecificationType = FixedCentralDensity;
using GeneratedValueType = BorderFor<FixedCentralDensity>;
using GeneratedResidualType = ResidualFor<FixedCentralDensity>;
using ValueBlockType = utils::blocks::fixed_central_density::central_value::value;
using ResidualBlockType = utils::blocks::fixed_central_density::central_value::residual;
using TermType = utils::blocks::fixed_central_density::central_value;
using StateValueBlockTypes = ModelTypeList<utils::blocks::enthalpy::specific::value>;
static constexpr ConstraintRowInjection rowInjection = ConstraintRowInjection::solver_border;
static constexpr std::size_t valueArity = GeneratedValueType::scalarArity;
static constexpr std::size_t residualArity = GeneratedResidualType::scalarArity;
template <typename Form> [[nodiscard]] static consteval auto valueBlock() {
return utils::blocks::get_value_block<Form>(TermType{});
}
template <typename Form> [[nodiscard]] static consteval auto residualBlock() {
return utils::blocks::get_residual_block<Form>(TermType{});
}
};
class CompiledFixedCentralDensity final {
public:
using SpecificationType = FixedCentralDensity;
using LayoutRequest = CentralDensityLayoutRequest;
using BorderType = typename LayoutRequest::GeneratedValueType;
using ResidualType = typename LayoutRequest::GeneratedResidualType;
using CarrierField = field::Enthalpy;
using BorderField = field::CentralDensityBorder;
CompiledFixedCentralDensity(
const FixedCentralDensity specification,
const eos::Polytrope &equationOfState
)
: m_specification(specification),
m_equationOfState(equationOfState),
m_targetEnthalpy(
eos::evaluate<eos::quantity::SpecificEnthalpy>(
m_equationOfState,
m_specification.targetDensity()
)
) {
}
[[nodiscard]] const FixedCentralDensity &specification() const noexcept {
return m_specification;
}
[[nodiscard]] dimensions::DensityValue targetDensity() const noexcept {
return m_specification.targetDensity();
}
[[nodiscard]] dimensions::SpecificEnthalpyValue targetEnthalpy() const noexcept {
return m_targetEnthalpy;
}
[[nodiscard]] dimensions::DensityValue
densityFromEnthalpy(const dimensions::SpecificEnthalpyValue enthalpy) const {
return eos::evaluate<eos::quantity::Density>(m_equationOfState, enthalpy);
}
[[nodiscard]] static consteval LayoutRequest layoutRequest() noexcept {
return {};
}
private:
FixedCentralDensity m_specification;
eos::Polytrope m_equationOfState;
dimensions::SpecificEnthalpyValue m_targetEnthalpy;
};
[[nodiscard]] inline CompiledFixedCentralDensity compileConstraint(
const FixedCentralDensity specification,
const eos::Polytrope &equationOfState
) {
return {specification, equationOfState};
}
static_assert(ConstraintLayoutRequestType<CentralDensityLayoutRequest>);
static_assert(CompiledConstraint<CompiledFixedCentralDensity>);
} // namespace mean_field::models

View File

@@ -0,0 +1,115 @@
module;
#include <concepts>
#include <type_traits>
export module mean_field:model.compiled_fixed_mass;
export import :field.registry;
export import :model.specifications;
export import :utils.blocks;
export namespace mean_field::models {
enum class ConstraintRowInjection { append, solver_border };
template <
ModelSpecification Specification,
typename GeneratedValue,
typename GeneratedResidual,
typename ValueBlock,
typename ResidualBlock,
typename Term,
typename... StateValueBlocks>
struct ConstraintLayoutRequest final {
using SpecificationType = Specification;
using GeneratedValueType = GeneratedValue;
using GeneratedResidualType = GeneratedResidual;
using ValueBlockType = ValueBlock;
using ResidualBlockType = ResidualBlock;
using TermType = Term;
using StateValueBlockTypes = ModelTypeList<StateValueBlocks...>;
static constexpr ConstraintRowInjection rowInjection = ConstraintRowInjection::append;
static constexpr std::size_t valueArity = GeneratedValue::scalarArity;
static constexpr std::size_t residualArity = GeneratedResidual::scalarArity;
template <typename Form> [[nodiscard]] static consteval auto valueBlock() {
return utils::blocks::get_value_block<Form>(Term{});
}
template <typename Form> [[nodiscard]] static consteval auto residualBlock() {
return utils::blocks::get_residual_block<Form>(Term{});
}
};
template <typename Candidate>
concept ConstraintLayoutRequestType = requires {
typename std::remove_cvref_t<Candidate>::SpecificationType;
typename std::remove_cvref_t<Candidate>::GeneratedValueType;
typename std::remove_cvref_t<Candidate>::GeneratedResidualType;
typename std::remove_cvref_t<Candidate>::ValueBlockType;
typename std::remove_cvref_t<Candidate>::ResidualBlockType;
typename std::remove_cvref_t<Candidate>::StateValueBlockTypes;
requires ModelSpecification<typename std::remove_cvref_t<Candidate>::SpecificationType>;
requires std::remove_cvref_t<Candidate>::valueArity == std::remove_cvref_t<Candidate>::residualArity;
};
using FixedMassLayoutRequest = ConstraintLayoutRequest<
FixedTotalMass,
MultiplierFor<FixedTotalMass>,
ResidualFor<FixedTotalMass>,
utils::blocks::fixed_total_mass::mass_normalization::value,
utils::blocks::fixed_total_mass::mass_normalization::residual,
utils::blocks::fixed_total_mass::mass_normalization,
utils::blocks::density::mass::value,
utils::blocks::displacement::geometry::value>;
class CompiledFixedMass final {
public:
using SpecificationType = FixedTotalMass;
using LayoutRequest = FixedMassLayoutRequest;
using MultiplierType = typename LayoutRequest::GeneratedValueType;
using ResidualType = typename LayoutRequest::GeneratedResidualType;
// In the current barotropic formulation, the multiplier generated by
// FixedTotalMass is realized by the historical scalar C field.
using MultiplierField = field::BarotropicConstant;
explicit CompiledFixedMass(const FixedTotalMass specification) noexcept : m_specification(specification) {
}
[[nodiscard]] const FixedTotalMass &specification() const noexcept {
return m_specification;
}
[[nodiscard]] dimensions::MassValue targetMass() const noexcept {
return m_specification.targetMass();
}
[[nodiscard]] static consteval LayoutRequest layoutRequest() noexcept {
return {};
}
private:
FixedTotalMass m_specification;
};
template <typename Candidate>
concept CompiledConstraint = requires(const std::remove_cvref_t<Candidate> &constraint) {
typename std::remove_cvref_t<Candidate>::SpecificationType;
typename std::remove_cvref_t<Candidate>::LayoutRequest;
requires ModelSpecification<typename std::remove_cvref_t<Candidate>::SpecificationType>;
requires ConstraintLayoutRequestType<typename std::remove_cvref_t<Candidate>::LayoutRequest>;
{
constraint.specification()
} noexcept -> std::same_as<const typename std::remove_cvref_t<Candidate>::SpecificationType &>;
{ constraint.layoutRequest() } noexcept -> std::same_as<typename std::remove_cvref_t<Candidate>::LayoutRequest>;
};
[[nodiscard]] inline CompiledFixedMass compileConstraint(const FixedTotalMass specification) noexcept {
return CompiledFixedMass{specification};
}
static_assert(ConstraintLayoutRequestType<FixedMassLayoutRequest>);
static_assert(CompiledConstraint<CompiledFixedMass>);
} // namespace mean_field::models

View File

@@ -0,0 +1,481 @@
module;
#include <array>
#include <cmath>
#include <compare>
#include <concepts>
#include <cstddef>
#include <format>
#include <span>
#include <stdexcept>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
export module mean_field:model.specifications;
export import :eos.polytrope;
export import :surface.constant;
export namespace mean_field::models {
enum class SpecificationRole {
constitutive_law,
boundary_condition,
invariant,
phase_condition,
gauge_choice,
rotation_law
};
struct SpecificationKey final {
SpecificationRole role;
std::size_t ordinal;
constexpr auto operator<=>(const SpecificationKey &) const = default;
};
struct SpecificationDescriptor final {
std::string_view name;
SpecificationRole role;
SpecificationKey key;
std::size_t generatedValueArity;
std::size_t generatedResidualArity;
constexpr bool operator==(const SpecificationDescriptor &) const = default;
};
enum class EquilibriumSystemCompilation {
complete_equilibrium_system,
equation_contributions_only,
// Transitional spellings retained while internal solver code is
// migrated to physics-facing equilibrium-system terminology.
isolated_root = complete_equilibrium_system,
assembly_only = equation_contributions_only
};
using ModelCompilationClass = EquilibriumSystemCompilation;
struct RuntimeSpecificationDescriptor final {
SpecificationDescriptor specification;
std::size_t canonicalIndex;
bool hasRootCompiler;
constexpr bool operator==(const RuntimeSpecificationDescriptor &) const = default;
};
template <typename Candidate> struct SpecificationTraits;
template <typename Candidate>
concept ModelSpecification = requires {
typename std::remove_cvref_t<Candidate>::Parameters;
{ SpecificationTraits<std::remove_cvref_t<Candidate>>::name } -> std::convertible_to<std::string_view>;
{ SpecificationTraits<std::remove_cvref_t<Candidate>>::role } -> std::convertible_to<SpecificationRole>;
{ SpecificationTraits<std::remove_cvref_t<Candidate>>::key } -> std::convertible_to<SpecificationKey>;
} && std::constructible_from<std::remove_cvref_t<Candidate>, typename std::remove_cvref_t<Candidate>::Parameters>;
class FixedTotalMass final {
public:
struct Parameters final {
dimensions::MassValue Mtotal;
};
using TargetValue = dimensions::MassValue;
explicit FixedTotalMass(const Parameters parameters) : FixedTotalMass(parameters.Mtotal) {
}
explicit FixedTotalMass(const TargetValue targetMass) : m_targetMass(targetMass) {
if (!std::isfinite(targetMass.value()) || targetMass.value() <= 0.0) {
throw std::invalid_argument(
std::format(
"The fixed total mass must be finite and positive. Instead M = {} was provided.",
targetMass.value()
)
);
}
}
[[nodiscard]] TargetValue targetMass() const noexcept {
return m_targetMass;
}
private:
TargetValue m_targetMass;
};
class FixedCentralDensity final {
public:
struct Parameters final {
dimensions::DensityValue RhoC;
};
using TargetValue = dimensions::DensityValue;
explicit FixedCentralDensity(const Parameters parameters) : FixedCentralDensity(parameters.RhoC) {
}
explicit FixedCentralDensity(const TargetValue targetDensity) : m_targetDensity(targetDensity) {
if (!std::isfinite(targetDensity.value()) || targetDensity.value() <= 0.0) {
throw std::invalid_argument(
std::format(
"The fixed central density must be finite and positive. Instead rho_c = {} was provided.",
targetDensity.value()
)
);
}
}
[[nodiscard]] TargetValue targetDensity() const noexcept {
return m_targetDensity;
}
private:
TargetValue m_targetDensity;
};
template <> struct SpecificationTraits<eos::Polytrope> {
static constexpr std::string_view name = "Polytrope";
static constexpr SpecificationRole role = SpecificationRole::constitutive_law;
static constexpr SpecificationKey key{role, 0};
};
template <> struct SpecificationTraits<surface::ConstantPressureSurface> {
static constexpr std::string_view name = "IsobaricSurface";
static constexpr SpecificationRole role = SpecificationRole::boundary_condition;
static constexpr SpecificationKey key{role, 0};
};
template <> struct SpecificationTraits<FixedTotalMass> {
static constexpr std::string_view name = "FixedTotalMass";
static constexpr SpecificationRole role = SpecificationRole::invariant;
static constexpr SpecificationKey key{role, 0};
};
template <> struct SpecificationTraits<FixedCentralDensity> {
static constexpr std::string_view name = "FixedCentralDensity";
static constexpr SpecificationRole role = SpecificationRole::phase_condition;
static constexpr SpecificationKey key{role, 0};
};
template <typename... Types> struct ModelTypeList final {
static constexpr std::size_t size = sizeof...(Types);
};
template <typename Query, typename List> struct ModelTypeListContains;
template <typename Query, typename... Types>
struct ModelTypeListContains<Query, ModelTypeList<Types...>>
: std::bool_constant<(std::same_as<Query, Types> || ...)> { };
template <typename Query, typename List>
inline constexpr bool modelTypeListContains = ModelTypeListContains<Query, List>::value;
template <ModelSpecification Specification> struct ResidualFor final {
using SpecificationType = Specification;
static constexpr std::size_t scalarArity = 1;
};
template <ModelSpecification Specification> struct MultiplierFor final {
using SpecificationType = Specification;
static constexpr std::size_t scalarArity = 1;
};
template <ModelSpecification Specification> struct BorderFor final {
using SpecificationType = Specification;
static constexpr std::size_t scalarArity = 1;
};
template <ModelSpecification Specification> struct SpecificationContribution {
using GeneratedValues = ModelTypeList<>;
using GeneratedResiduals = ModelTypeList<>;
static constexpr bool isDefined = false;
static constexpr bool hasRootCompiler = false;
};
template <> struct SpecificationContribution<eos::Polytrope> {
using GeneratedValues = ModelTypeList<>;
using GeneratedResiduals = ModelTypeList<>;
static constexpr bool isDefined = true;
static constexpr bool hasRootCompiler = true;
};
template <> struct SpecificationContribution<surface::ConstantPressureSurface> {
using GeneratedValues = ModelTypeList<>;
using GeneratedResiduals = ModelTypeList<>;
static constexpr bool isDefined = true;
static constexpr bool hasRootCompiler = true;
};
template <> struct SpecificationContribution<FixedTotalMass> {
using GeneratedValues = ModelTypeList<MultiplierFor<FixedTotalMass>>;
using GeneratedResiduals = ModelTypeList<ResidualFor<FixedTotalMass>>;
static constexpr bool isDefined = true;
static constexpr bool hasRootCompiler = true;
};
template <> struct SpecificationContribution<FixedCentralDensity> {
using GeneratedValues = ModelTypeList<BorderFor<FixedCentralDensity>>;
using GeneratedResiduals = ModelTypeList<ResidualFor<FixedCentralDensity>>;
static constexpr bool isDefined = true;
static constexpr bool hasRootCompiler = true;
};
template <typename Candidate>
concept ResolvedModelSpecification =
ModelSpecification<Candidate> && SpecificationContribution<std::remove_cvref_t<Candidate>>::isDefined;
namespace detail {
template <typename... Specifications> struct SpecificationSetStorage final {
static constexpr std::size_t size = sizeof...(Specifications);
};
template <typename... Lists> struct ConcatenateModelTypeLists;
template <> struct ConcatenateModelTypeLists<> {
using Type = ModelTypeList<>;
};
template <typename... Types> struct ConcatenateModelTypeLists<ModelTypeList<Types...>> {
using Type = ModelTypeList<Types...>;
};
template <typename... First, typename... Second, typename... Remaining>
struct ConcatenateModelTypeLists<ModelTypeList<First...>, ModelTypeList<Second...>, Remaining...> {
using Type = typename ConcatenateModelTypeLists<ModelTypeList<First..., Second...>, Remaining...>::Type;
};
template <ModelSpecification Specification, typename Set> struct InsertSpecification;
template <ModelSpecification Specification>
struct InsertSpecification<Specification, SpecificationSetStorage<>> {
using Type = SpecificationSetStorage<Specification>;
};
template <ModelSpecification Specification, ModelSpecification Head, ModelSpecification... Tail>
struct InsertSpecification<Specification, SpecificationSetStorage<Head, Tail...>> {
private:
using InsertedTail = typename InsertSpecification<Specification, SpecificationSetStorage<Tail...>>::Type;
template <typename First, typename Rest> struct PrependSpecification;
template <typename First, ModelSpecification... Rest>
struct PrependSpecification<First, SpecificationSetStorage<Rest...>> {
using Type = SpecificationSetStorage<First, Rest...>;
};
public:
using Type = std::conditional_t<
(SpecificationTraits<Specification>::key < SpecificationTraits<Head>::key),
SpecificationSetStorage<Specification, Head, Tail...>,
typename PrependSpecification<Head, InsertedTail>::Type>;
};
template <typename Set, ModelSpecification... Specifications> struct CanonicalizeSpecifications;
template <typename Set> struct CanonicalizeSpecifications<Set> {
using Type = Set;
};
template <typename Set, ModelSpecification Head, ModelSpecification... Tail>
struct CanonicalizeSpecifications<Set, Head, Tail...> {
using Inserted = typename InsertSpecification<Head, Set>::Type;
using Type = typename CanonicalizeSpecifications<Inserted, Tail...>::Type;
};
template <ModelSpecification... Specifications>
using CanonicalSpecificationSet =
typename CanonicalizeSpecifications<SpecificationSetStorage<>, Specifications...>::Type;
template <
ModelSpecification Head,
ModelSpecification... Tail>
consteval bool specificationKeyIsUnique() {
return ((SpecificationTraits<Head>::key != SpecificationTraits<Tail>::key) && ...);
}
template <ModelSpecification... Specifications> struct SpecificationKeysAreUnique;
template <> struct SpecificationKeysAreUnique<> : std::true_type { };
template <ModelSpecification Head, ModelSpecification... Tail>
struct SpecificationKeysAreUnique<Head, Tail...>
: std::bool_constant<
specificationKeyIsUnique<Head, Tail...>() && SpecificationKeysAreUnique<Tail...>::value> { };
template <SpecificationRole Role, ModelSpecification... Specifications>
inline constexpr std::size_t specificationRoleCount =
(std::size_t{0} + ... + (SpecificationTraits<Specifications>::role == Role ? 1 : 0));
template <typename List> struct ModelTypeListScalarArity;
template <typename... Types>
struct ModelTypeListScalarArity<ModelTypeList<Types...>>
: std::integral_constant<std::size_t, (std::size_t{0} + ... + Types::scalarArity)> { };
template <typename Query, typename... Types>
inline constexpr bool isOneOf = (std::same_as<Query, Types> || ...);
template <typename Query, typename... Types>
inline constexpr std::size_t typeCount =
(std::size_t{0} + ... +
(std::same_as<Query, std::remove_cvref_t<Types>> ? std::size_t{1} : std::size_t{0}));
template <typename CanonicalSet, typename... Arguments> struct ArgumentsMatchCanonicalSpecifications;
template <ModelSpecification... CanonicalSpecifications, typename... Arguments>
struct ArgumentsMatchCanonicalSpecifications<SpecificationSetStorage<CanonicalSpecifications...>, Arguments...>
: std::bool_constant<
sizeof...(CanonicalSpecifications) == sizeof...(Arguments) &&
(isOneOf<std::remove_cvref_t<Arguments>, CanonicalSpecifications...> && ...) &&
((typeCount<CanonicalSpecifications, Arguments...> == 1) && ...)> { };
} // namespace detail
template <ModelSpecification... Specifications>
inline constexpr bool specificationKeysAreUnique = detail::SpecificationKeysAreUnique<Specifications...>::value;
template <typename... Specifications>
concept ValidModelSpecificationPack =
(ResolvedModelSpecification<Specifications> && ...) && specificationKeysAreUnique<Specifications...> &&
detail::specificationRoleCount<SpecificationRole::constitutive_law, Specifications...> == 1;
template <ModelSpecification... Specifications>
requires specificationKeysAreUnique<Specifications...>
using SpecificationSet = detail::CanonicalSpecificationSet<Specifications...>;
template <typename SpecificationSet> struct SpecificationOperatorSignature;
template <ModelSpecification... Specifications>
struct SpecificationOperatorSignature<detail::SpecificationSetStorage<Specifications...>> final {
using GeneratedValues = typename detail::ConcatenateModelTypeLists<
typename SpecificationContribution<Specifications>::GeneratedValues...>::Type;
using GeneratedResiduals = typename detail::ConcatenateModelTypeLists<
typename SpecificationContribution<Specifications>::GeneratedResiduals...>::Type;
static constexpr std::size_t generatedValueArity = detail::ModelTypeListScalarArity<GeneratedValues>::value;
static constexpr std::size_t generatedResidualArity =
detail::ModelTypeListScalarArity<GeneratedResiduals>::value;
static constexpr bool symbolicallySquare = generatedValueArity == generatedResidualArity;
};
template <ResolvedModelSpecification Specification>
[[nodiscard]] consteval SpecificationDescriptor specificationDescriptor() {
using Contribution = SpecificationContribution<Specification>;
return {
.name = SpecificationTraits<Specification>::name,
.role = SpecificationTraits<Specification>::role,
.key = SpecificationTraits<Specification>::key,
.generatedValueArity = detail::ModelTypeListScalarArity<typename Contribution::GeneratedValues>::value,
.generatedResidualArity = detail::ModelTypeListScalarArity<typename Contribution::GeneratedResiduals>::value
};
}
namespace detail {
template <typename Specifications> class SpecifiedModel;
template <ModelSpecification... Specifications>
class SpecifiedModel<SpecificationSetStorage<Specifications...>> final {
public:
using SpecificationTypes = SpecificationSetStorage<Specifications...>;
using OperatorSignature = SpecificationOperatorSignature<SpecificationTypes>;
static constexpr bool symbolicallySquare = OperatorSignature::symbolicallySquare;
static constexpr bool hasCompleteRootCompiler =
(SpecificationContribution<Specifications>::hasRootCompiler && ...);
static constexpr EquilibriumSystemCompilation compilationClass =
symbolicallySquare && hasCompleteRootCompiler
? EquilibriumSystemCompilation::complete_equilibrium_system
: EquilibriumSystemCompilation::equation_contributions_only;
template <typename... Arguments>
requires ArgumentsMatchCanonicalSpecifications<
SpecificationTypes,
Arguments...>::value
explicit SpecifiedModel(Arguments &&...arguments)
: m_specifications(
std::get<Specifications>(
std::tuple<std::remove_cvref_t<Arguments>...>{std::forward<Arguments>(arguments)...}
)...
) {
}
template <ModelSpecification Specification>
requires isOneOf<
Specification,
Specifications...>
[[nodiscard]] const Specification &specification() const noexcept {
return std::get<Specification>(m_specifications);
}
template <ModelSpecification Specification>
static constexpr bool containsSpecification = isOneOf<Specification, Specifications...>;
[[nodiscard]] static constexpr std::span<const RuntimeSpecificationDescriptor>
runtimeSpecificationDescriptors() noexcept {
return runtimeDescriptors;
}
private:
inline static constexpr std::array<RuntimeSpecificationDescriptor, sizeof...(Specifications)>
runtimeDescriptors = [] {
std::array<RuntimeSpecificationDescriptor, sizeof...(Specifications)> descriptors{};
std::size_t index = 0;
((descriptors[index] =
{.specification = specificationDescriptor<Specifications>(),
.canonicalIndex = index,
.hasRootCompiler = SpecificationContribution<Specifications>::hasRootCompiler},
++index),
...);
return descriptors;
}();
std::tuple<Specifications...> m_specifications;
};
} // namespace detail
template <ModelSpecification... Specifications>
requires ValidModelSpecificationPack<Specifications...> &&
SpecificationOperatorSignature<SpecificationSet<Specifications...>>::symbolicallySquare
using Model = detail::SpecifiedModel<SpecificationSet<Specifications...>>;
template <typename Candidate>
concept SpecifiedModelType = requires {
typename std::remove_cvref_t<Candidate>::SpecificationTypes;
typename std::remove_cvref_t<Candidate>::OperatorSignature;
requires std::remove_cvref_t<Candidate>::symbolicallySquare;
{ std::remove_cvref_t<Candidate>::compilationClass } -> std::convertible_to<ModelCompilationClass>;
{
std::remove_cvref_t<Candidate>::runtimeSpecificationDescriptors()
} -> std::same_as<std::span<const RuntimeSpecificationDescriptor>>;
};
static_assert(ModelSpecification<eos::Polytrope>);
static_assert(ModelSpecification<surface::ConstantPressureSurface>);
static_assert(ModelSpecification<FixedTotalMass>);
static_assert(ModelSpecification<FixedCentralDensity>);
static_assert(ResolvedModelSpecification<eos::Polytrope>);
static_assert(ResolvedModelSpecification<surface::ConstantPressureSurface>);
static_assert(ResolvedModelSpecification<FixedTotalMass>);
static_assert(ResolvedModelSpecification<FixedCentralDensity>);
} // namespace mean_field::models
export namespace mean_field::integral {
using FixedTotalMass = models::FixedTotalMass;
}
export namespace mean_field::constraint {
using FixedCentralDensity = models::FixedCentralDensity;
}

View File

@@ -1,13 +1,12 @@
module;
#include <vector>
#include <mfem.hpp>
export module mean_field:model.structure.polytropic;
export import :eos.polytrope;
export import :model.structure.base;
export import :seed.lane_emden;
import :utils.misc;
@@ -28,40 +27,6 @@ export namespace mean_field::models::structure {
void validate() const;
private:
struct LaneEmdenPoint {
double coordinate{0.0};
double value{0.0};
double derivative{0.0};
};
struct LaneEmdenDerivative {
double value{0.0};
double derivative{0.0};
};
static void validateSeedRequest(const StructureSeedRequest &request);
[[nodiscard]] static LaneEmdenDerivative evaluateLaneEmdenRhs(
double coordinate,
double value,
double derivative,
double polytropicIndex
);
[[nodiscard]] static LaneEmdenPoint takeLaneEmdenStep(
const LaneEmdenPoint &point,
double step,
double polytropicIndex
);
[[nodiscard]] static std::vector<LaneEmdenPoint> solveLaneEmden(double polytropicIndex);
[[nodiscard]] static double interpolateLaneEmdenValue(
const std::vector<LaneEmdenPoint> &solution,
double coordinate,
std::size_t &lowerIndex
);
eos::Polytrope m_equationOfState;
double m_targetMass;
};

View File

@@ -0,0 +1,66 @@
module;
#include <concepts>
#include <cstddef>
#include <span>
#include <type_traits>
#include <utility>
export module mean_field:model.typed_stellar;
export import :model.specifications;
export namespace mean_field::model {
template <typename SpecificationSet> class StellarModel;
template <models::ModelSpecification... CanonicalSpecifications>
class StellarModel<models::detail::SpecificationSetStorage<CanonicalSpecifications...>> final {
public:
using SpecificationTypes = models::detail::SpecificationSetStorage<CanonicalSpecifications...>;
using OperatorSignature = models::SpecificationOperatorSignature<SpecificationTypes>;
using Storage = models::Model<CanonicalSpecifications...>;
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
static constexpr bool hasCompleteEquilibriumCompiler = Storage::hasCompleteRootCompiler;
static constexpr models::EquilibriumSystemCompilation compilationClass = Storage::compilationClass;
template <typename... Arguments>
requires std::constructible_from<
Storage,
Arguments...>
explicit StellarModel(Arguments &&...arguments) : m_specifications(std::forward<Arguments>(arguments)...) {
}
template <models::ModelSpecification Specification>
requires Storage::template
containsSpecification<Specification> [[nodiscard]] const Specification &specification() const noexcept {
return m_specifications.template specification<Specification>();
}
template <models::ModelSpecification Specification>
static constexpr bool containsSpecification = Storage::template containsSpecification<Specification>;
[[nodiscard]] static constexpr std::span<const models::RuntimeSpecificationDescriptor>
runtimeSpecificationDescriptors() noexcept {
return Storage::runtimeSpecificationDescriptors();
}
private:
Storage m_specifications;
};
template <models::ResolvedModelSpecification... Specifications>
requires models::ValidModelSpecificationPack<std::remove_cvref_t<Specifications>...>
StellarModel(Specifications &&...)
-> StellarModel<models::SpecificationSet<std::remove_cvref_t<Specifications>...>>;
namespace detail {
template <typename Candidate> struct IsStellarModel : std::false_type { };
template <typename SpecificationSet> struct IsStellarModel<StellarModel<SpecificationSet>> : std::true_type { };
} // namespace detail
template <typename Candidate>
concept StellarModelType = detail::IsStellarModel<std::remove_cvref_t<Candidate>>::value;
} // namespace mean_field::model

View File

@@ -84,15 +84,24 @@ export namespace mean_field::operators {
mfem::Vector &actionTrue
) const;
void ApplyDisplacementActionFull(
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
) const;
struct ElementPAData {
int elementId{-1};
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *enthalpyDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
mfem::DenseMatrix densityBasis;
mfem::DenseMatrix enthalpyBasis;
mfem::DenseMatrix inverseElementJacobians;
mfem::Vector weightedResidual;
mfem::Vector quadratureWeights;
@@ -122,6 +131,14 @@ export namespace mean_field::operators {
mutable mfem::Vector m_fullDisplacementAction;
mutable mfem::Vector m_fullResidual;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localDisplacementAction;
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_quadratureDisplacementAction;
mutable mfem::Vector m_elementDisplacementAction;
mutable mfem::DenseMatrix m_referenceDShape;
mutable mfem::DenseMatrix m_referenceDisplacementJacobian;
std::uint64_t m_preparationCount{0};
bool m_isPrepared{false};
};

View File

@@ -0,0 +1,264 @@
module;
#include <algorithm>
#include <cmath>
#include <compare>
#include <cstdint>
#include <optional>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_central_density;
export import :field.mfem;
export import :model.compiled_fixed_central_density;
export namespace mean_field::operators {
struct CentralDensityDependencyStamp final {
std::uint64_t identity{0};
std::uint64_t revision{0};
constexpr auto operator<=>(const CentralDensityDependencyStamp &) const = default;
};
struct CentralDensityDependencies final {
CentralDensityDependencyStamp enthalpy;
constexpr auto operator<=>(const CentralDensityDependencies &) const = default;
};
struct PreparedCentralDensityReport final {
bool refreshedCentralEnthalpy{false};
bool refreshedBorder{false};
bool assembledResidual{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return refreshedCentralEnthalpy || refreshedBorder || assembledResidual;
}
constexpr auto operator<=>(const PreparedCentralDensityReport &) const = default;
};
struct CentralDensityConstraintReport final {
double targetDensity;
double achievedDensity;
double targetEnthalpy;
double achievedEnthalpy;
double enthalpyResidual;
double scaledResidual;
};
struct CentralDensityJacobianInput final {
const mfem::Vector &enthalpyVariation;
double borderVariation;
};
struct CentralDensityJacobianOutput final {
mfem::Vector &enthalpyAction;
mfem::Vector &phaseAction;
};
struct CentralDensityJacobianTransposeInput final {
const mfem::Vector &enthalpyResidualDual;
double phaseResidualDual;
};
struct CentralDensityJacobianTransposeOutput final {
mfem::Vector &enthalpyDual;
mfem::Vector &borderDual;
};
/*
* Bordered central-density phase condition
*
* R_c(h) = h(0) - h(rho_c,target),
* R_h <- R_h + lambda_c e_c.
*
* The point functional e_c selects the unique scalar H1 vertex at the
* computational origin. Its coordinate transpose supplies the border
* column, so this contribution is algebraically symmetric before any
* independent scaling is applied by a solver.
*/
class PreparedCentralDensityConstraint final {
public:
PreparedCentralDensityConstraint(
field::FieldPointDofMap centerDof,
const MPI_Comm communicator
)
: m_centerDof(std::move(centerDof)),
m_communicator(communicator) {
}
PreparedCentralDensityReport Prepare(
const models::CompiledFixedCentralDensity &constraint,
const mfem::Vector &enthalpy,
const double border,
const CentralDensityDependencies &dependencies
) {
MFEM_VERIFY(
enthalpy.Size() == m_centerDof.field_size(),
"The central-density phase received an enthalpy vector with the wrong size."
);
MFEM_VERIFY(std::isfinite(border), "The central-density phase received a non-finite border value.");
const bool wasPrepared = m_isPrepared;
PreparedCentralDensityReport report;
if (!wasPrepared || dependencies.enthalpy != m_preparedDependencies.enthalpy) {
double localCentralEnthalpy = 0.0;
for (const int reducedDof : m_centerDof.reduced_dofs()) {
const double value = enthalpy(reducedDof);
MFEM_VERIFY(std::isfinite(value), "The central enthalpy is non-finite.");
localCentralEnthalpy += value;
}
m_centralEnthalpy = GlobalSum(localCentralEnthalpy);
report.refreshedCentralEnthalpy = true;
}
if (!wasPrepared || border != m_border) {
m_border = border;
report.refreshedBorder = true;
}
const bool targetChanged =
!m_constraint.has_value() || constraint.targetDensity() != m_constraint->targetDensity();
if (targetChanged) {
m_constraint = constraint;
}
if (report.refreshedCentralEnthalpy || report.refreshedBorder || targetChanged) {
m_cachedPhaseResidual = m_centralEnthalpy - m_constraint->targetEnthalpy().value();
report.assembledResidual = true;
}
m_preparedDependencies = dependencies;
m_isPrepared = true;
++m_preparationCount;
return report;
}
void AddResidual(
mfem::Vector &enthalpyResidual,
mfem::Vector &phaseResidual
) const {
VerifyPrepared();
VerifyOutputSizes(enthalpyResidual, phaseResidual);
for (const int reducedDof : m_centerDof.reduced_dofs()) {
enthalpyResidual(reducedDof) += m_border;
}
phaseResidual(0) = m_cachedPhaseResidual;
}
void ApplyJacobian(
const CentralDensityJacobianInput &input,
CentralDensityJacobianOutput output
) const {
VerifyPrepared();
MFEM_VERIFY(
input.enthalpyVariation.Size() == m_centerDof.field_size(),
"The central-density Jacobian received an enthalpy direction with the wrong size."
);
VerifyOutputSizes(output.enthalpyAction, output.phaseAction);
double localPhaseAction = 0.0;
for (const int reducedDof : m_centerDof.reduced_dofs()) {
output.enthalpyAction(reducedDof) += input.borderVariation;
localPhaseAction += input.enthalpyVariation(reducedDof);
}
output.phaseAction(0) = GlobalSum(localPhaseAction);
++m_jacobianApplicationCount;
}
void ApplyJacobianTranspose(
const CentralDensityJacobianTransposeInput &input,
CentralDensityJacobianTransposeOutput output
) const {
VerifyPrepared();
MFEM_VERIFY(
input.enthalpyResidualDual.Size() == m_centerDof.field_size(),
"The central-density transpose received an enthalpy residual dual with the wrong size."
);
MFEM_VERIFY(
output.enthalpyDual.Size() == m_centerDof.field_size() && output.borderDual.Size() == 1,
"The central-density transpose received output vectors with the wrong size."
);
double localBorderDual = 0.0;
for (const int reducedDof : m_centerDof.reduced_dofs()) {
output.enthalpyDual(reducedDof) += input.phaseResidualDual;
localBorderDual += input.enthalpyResidualDual(reducedDof);
}
output.borderDual(0) += GlobalSum(localBorderDual);
++m_transposeApplicationCount;
}
[[nodiscard]] CentralDensityConstraintReport GetConstraintReport() const {
VerifyPrepared();
const double targetEnthalpy = m_constraint->targetEnthalpy().value();
const double scale = std::max(std::abs(targetEnthalpy), 1.0e-300);
return {
.targetDensity = m_constraint->targetDensity().value(),
.achievedDensity =
m_constraint->densityFromEnthalpy(dimensions::SpecificEnthalpyValue{m_centralEnthalpy}).value(),
.targetEnthalpy = targetEnthalpy,
.achievedEnthalpy = m_centralEnthalpy,
.enthalpyResidual = m_cachedPhaseResidual,
.scaledResidual = m_cachedPhaseResidual / scale
};
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared;
}
[[nodiscard]] const field::FieldPointDofMap &GetCenterDof() const noexcept {
return m_centerDof;
}
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept {
return m_preparationCount;
}
[[nodiscard]] std::uint64_t GetJacobianApplicationCount() const noexcept {
return m_jacobianApplicationCount;
}
[[nodiscard]] std::uint64_t GetTransposeApplicationCount() const noexcept {
return m_transposeApplicationCount;
}
private:
[[nodiscard]] double GlobalSum(const double localValue) const {
double globalValue = 0.0;
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_communicator);
return globalValue;
}
void VerifyOutputSizes(
const mfem::Vector &enthalpyOutput,
const mfem::Vector &phaseOutput
) const {
MFEM_VERIFY(
enthalpyOutput.Size() == m_centerDof.field_size() && phaseOutput.Size() == 1,
"The central-density phase received output vectors with the wrong size."
);
}
void VerifyPrepared() const {
MFEM_VERIFY(m_isPrepared, "The central-density phase must be prepared before application.");
}
field::FieldPointDofMap m_centerDof;
MPI_Comm m_communicator;
std::optional<models::CompiledFixedCentralDensity> m_constraint;
CentralDensityDependencies m_preparedDependencies;
double m_centralEnthalpy{0.0};
double m_border{0.0};
double m_cachedPhaseResidual{0.0};
std::uint64_t m_preparationCount{0};
mutable std::uint64_t m_jacobianApplicationCount{0};
mutable std::uint64_t m_transposeApplicationCount{0};
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,117 @@
module;
#include <concepts>
#include <memory>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_central_density_stellar_equilibrium;
export import :model.compiled_fixed_central_density;
export import :operators.prepared_central_density;
export import :operators.prepared_stellar_equilibrium;
export namespace mean_field::operators {
using CentralDensityStellarEquilibriumSpecificationModel = model::StellarModel<
models::
SpecificationSet<eos::Polytrope, models::FixedTotalMass, surface::Isobaric, models::FixedCentralDensity>>;
using CentralDensityStellarEquilibriumForm = utils::blocks::central_density_bordered_stellar_equilibrium_form;
using CentralDensityStellarEquilibriumJacobianForm =
utils::blocks::central_density_bordered_stellar_equilibrium_jacobian_form;
using CentralDensityStellarEquilibriumLayout = utils::blocks::form_layout<CentralDensityStellarEquilibriumForm>;
using CentralDensityStellarEquilibriumSystemManifest = EquilibriumSystemManifest<
CentralDensityStellarEquilibriumSpecificationModel,
CentralDensityStellarEquilibriumForm,
CentralDensityStellarEquilibriumJacobianForm>;
using CentralDensityStellarEquilibriumRootManifest = CentralDensityStellarEquilibriumSystemManifest;
struct PreparedCentralDensityStellarEquilibriumReport final {
PreparedStellarEquilibriumReport physical;
PreparedCentralDensityReport phase;
bool assembledResidual{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return physical.DidAnyWork() || phase.DidAnyWork() || assembledResidual;
}
};
class PreparedCentralDensityStellarEquilibriumOperator final : public mfem::Operator {
public:
PreparedCentralDensityStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
models::CompiledFixedMass fixedMassConstraint,
PressureSurfaceConstraintView surfaceConstraint,
deformation::PreparedDomainDeformationRuntime domainDeformation,
models::CompiledFixedCentralDensity centralDensity
)
: PreparedCentralDensityStellarEquilibriumOperator(
f,
std::make_unique<PreparedStellarEquilibriumOperator>(
f,
domainMapper,
equationOfState,
std::move(fixedMassConstraint),
surfaceConstraint,
std::move(domainDeformation)
),
std::move(centralDensity),
MakeCenterDofMap(f)
) {
}
PreparedCentralDensityStellarEquilibriumOperator(const PreparedCentralDensityStellarEquilibriumOperator &) =
delete;
PreparedCentralDensityStellarEquilibriumOperator &
operator=(const PreparedCentralDensityStellarEquilibriumOperator &) = delete;
PreparedCentralDensityStellarEquilibriumOperator(PreparedCentralDensityStellarEquilibriumOperator &&) = delete;
PreparedCentralDensityStellarEquilibriumOperator &
operator=(PreparedCentralDensityStellarEquilibriumOperator &&) = delete;
PreparedCentralDensityStellarEquilibriumReport Prepare(
const mfem::Vector &state,
const StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
);
void BuildResidual(mfem::Vector &residual) const;
void Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const override;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] const CentralDensityStellarEquilibriumLayout &GetLayout() const noexcept;
[[nodiscard]] const CentralDensityStellarEquilibriumRootManifest &GetRootManifest() const noexcept;
[[nodiscard]] const PreparedStellarEquilibriumOperator &GetPhysicalOperator() const noexcept;
[[nodiscard]] const PreparedCentralDensityConstraint &GetCentralDensityConstraint() const noexcept;
[[nodiscard]] RootConstraintReport GetFixedMassReport() const;
[[nodiscard]] CentralDensityConstraintReport GetCentralDensityReport() const;
private:
static field::FieldPointDofMap MakeCenterDofMap(const fem::FEM &f);
PreparedCentralDensityStellarEquilibriumOperator(
fem::FEM &f,
std::unique_ptr<PreparedStellarEquilibriumOperator> physicalOperator,
models::CompiledFixedCentralDensity centralDensity,
field::FieldPointDofMap centerDof
);
void AssembleResidual();
void VerifyPrepared() const;
std::unique_ptr<PreparedStellarEquilibriumOperator> m_physicalOperator;
models::CompiledFixedCentralDensity m_centralDensity;
PreparedCentralDensityConstraint m_phaseConstraint;
CentralDensityStellarEquilibriumRootManifest m_rootManifest;
mfem::Vector m_cachedResidual;
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,37 @@
module;
#include <concepts>
#include <type_traits>
#include <mfem.hpp>
export module mean_field:operators.prepared_constraint;
export import :model.compiled_fixed_mass;
export namespace mean_field::operators {
template <typename Candidate>
concept PreparedConstraint = requires(
std::remove_cvref_t<Candidate> &prepared,
const std::remove_cvref_t<Candidate> &constPrepared,
const typename std::remove_cvref_t<Candidate>::CompiledConstraintType &constraint,
const typename std::remove_cvref_t<Candidate>::Dependencies &dependencies,
const typename std::remove_cvref_t<Candidate>::JacobianInput &jacobianInput,
typename std::remove_cvref_t<Candidate>::JacobianTransposeOutput transposeOutput,
const mfem::Vector &residualDual,
mfem::Vector &result
) {
typename std::remove_cvref_t<Candidate>::SpecificationType;
typename std::remove_cvref_t<Candidate>::CompiledConstraintType;
typename std::remove_cvref_t<Candidate>::Dependencies;
typename std::remove_cvref_t<Candidate>::Report;
typename std::remove_cvref_t<Candidate>::JacobianInput;
typename std::remove_cvref_t<Candidate>::JacobianTransposeOutput;
requires models::CompiledConstraint<typename std::remove_cvref_t<Candidate>::CompiledConstraintType>;
{ prepared.Prepare(constraint, dependencies) } -> std::same_as<typename std::remove_cvref_t<Candidate>::Report>;
{ constPrepared.BuildResidual(result) } -> std::same_as<void>;
{ constPrepared.ApplyJacobian(jacobianInput, result) } -> std::same_as<void>;
{ constPrepared.ApplyJacobianTranspose(residualDual, transposeOutput) } -> std::same_as<void>;
{ constPrepared.IsPrepared() } noexcept -> std::same_as<bool>;
};
} // namespace mean_field::operators

View File

@@ -2,6 +2,7 @@ module;
#include <compare>
#include <cstdint>
#include <vector>
#include <mfem.hpp>
@@ -105,6 +106,29 @@ export namespace mean_field::operators {
private:
void VerifyPrepared() const;
void PrepareElementData();
void ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &gravityGradientVariationTrue,
mfem::Vector &actionTrue
) const;
struct ElementPAData {
int elementId{-1};
mfem::Array<int> densityDofs;
mfem::Array<int> gravityGradientDofs;
mfem::Array<int> displacementDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *gravityGradientDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
mfem::DenseMatrix mappingJacobians;
mfem::DenseMatrix inverseMeshJacobians;
mfem::DenseMatrix baseGravityReferenceValues;
mfem::Vector baseDensityValues;
mfem::Vector referenceWeights;
};
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domainMapper;
@@ -112,11 +136,34 @@ export namespace mean_field::operators {
context::gravity_field::GravityFieldRevisions m_preparedRevisions;
mfem::Vector m_cachedResidual;
std::vector<ElementPAData> m_elements;
mutable mfem::Vector m_densityVariationTrue;
mutable mfem::Vector m_gravityGradientVariationTrue;
mutable mfem::Vector m_displacementVariationTrue;
mutable mfem::Vector m_actionTrue;
mutable mfem::Vector m_densityVariationLocal;
mutable mfem::Vector m_gravityGradientVariationLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localAction;
mutable mfem::Vector m_elementDensityVariation;
mutable mfem::Vector m_elementGravityGradientVariation;
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_elementAction;
mutable mfem::Vector m_densityShape;
mutable mfem::Vector m_displacementShape;
mutable mfem::Vector m_baseGravityReferenceValue;
mutable mfem::Vector m_gravityVariationReferenceValue;
mutable mfem::Vector m_mappedBaseGravity;
mutable mfem::Vector m_mappedGravityVariation;
mutable mfem::Vector m_mappedGeometryVariation;
mutable mfem::Vector m_forceValue;
mutable mfem::DenseMatrix m_gravityGradientShape;
mutable mfem::DenseMatrix m_referenceDisplacementDShape;
mutable mfem::DenseMatrix m_referenceDisplacementJacobian;
mutable mfem::DenseMatrix m_displacementJacobianVariation;
mutable mfem::DenseMatrix m_mappingJacobian;
mutable mfem::DenseMatrix m_inverseMeshJacobian;
std::uint64_t m_residualPreparationCount{0};
mutable std::uint64_t m_residualApplicationCount{0};

View File

@@ -22,6 +22,11 @@ export namespace mean_field::operators {
const mfem::Vector &density,
mfem::Vector &action
) const override;
void MultDisplacementVariationTrue(
const mfem::Vector &densityTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
@@ -41,13 +46,18 @@ export namespace mean_field::operators {
mfem::Array<int> density_dofs;
mfem::Array<int> potential_dofs;
mfem::Array<int> displacement_dofs;
mfem::DofTransformation *density_dof_transformation{nullptr};
mfem::DofTransformation *potential_dof_transformation{nullptr};
mfem::DofTransformation *displacement_dof_transformation{nullptr};
const mfem::IntegrationRule *integration_rule{nullptr};
// Rows are quadrature points; columns are element DOFs.
mfem::DenseMatrix density_basis;
mfem::DenseMatrix potential_basis;
mfem::DenseMatrix inverse_element_jacobians;
// Contains quadrature weight, mesh Jacobian, mapped Jacobian,
// and 4*pi*G.
@@ -67,6 +77,15 @@ export namespace mean_field::operators {
mutable mfem::Vector m_density_true;
mutable mfem::Vector m_potential_true;
mutable mfem::Vector m_action_true;
mutable mfem::Vector m_density_local;
mutable mfem::Vector m_displacement_variation_local;
mutable mfem::Vector m_local_variation_action;
mutable mfem::Vector m_element_density;
mutable mfem::Vector m_element_displacement_variation;
mutable mfem::Vector m_quadrature_variation_action;
mutable mfem::Vector m_element_variation_action;
mutable mfem::DenseMatrix m_reference_displacement_dshape;
mutable mfem::DenseMatrix m_reference_displacement_jacobian;
mfem::Vector m_displacement_true;
std::uint64_t m_preparation_count{0};

View File

@@ -2,6 +2,7 @@ module;
#include <cstdint>
#include <memory>
#include <mfem.hpp>
#include <vector>
export module mean_field:operators.prepared_hdiv_mass;
export import :fem;
@@ -21,6 +22,11 @@ export namespace mean_field::operators {
const mfem::Vector &gravity_gradient,
mfem::Vector &action
) const override;
void MultDisplacementVariationTrue(
const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const;
void AssembleDiagonal(mfem::Vector &diagonal) const override;
void AssembleTrueDiagonal(mfem::Vector &diagonal) const;
@@ -31,6 +37,21 @@ export namespace mean_field::operators {
[[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept;
private:
struct ElementVariationData {
int elementId{-1};
mfem::Array<int> gravityGradientDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::DofTransformation *gravityGradientDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
mfem::Vector baseDisplacement;
mfem::Vector compactification;
const mfem::IntegrationRule *integrationRule{nullptr};
mfem::DenseMatrix frozenMappingData;
};
void PrepareVariationData();
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domain_mapper;
@@ -48,6 +69,21 @@ export namespace mean_field::operators {
mutable mfem::Vector m_action_true;
mutable mfem::Vector m_domain_action_true;
mfem::Vector m_displacement_true;
std::vector<ElementVariationData> m_variationElements;
mutable mapping::DomainMapper::Workspace m_variationWorkspace;
mutable mapping::VolumeMappingContext m_baseMappingContext;
mutable mapping::VolumeMappingVariation m_mappingVariation;
mutable mfem::Vector m_gravityGradientLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localVariationAction;
mutable mfem::Vector m_elementGravityGradient;
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_elementVariationAction;
mutable mfem::Vector m_gravityGradientValue;
mutable mfem::Vector m_massTensorVariationAction;
mutable mfem::DenseMatrix m_gravityGradientShape;
mutable mfem::DenseMatrix m_massTensorVariation;
std::uint64_t m_preparation_count{0};
bool m_is_prepared{false};
};

View File

@@ -9,7 +9,9 @@ export module mean_field:operators.prepared_mass_normalization;
export import :fem;
export import :mapping.domain_mapper;
export import :model.compiled_fixed_mass;
export import :operators.context.gravity_field;
export import :operators.prepared_constraint;
export import :utils.blocks;
export namespace mean_field::operators {
@@ -33,6 +35,16 @@ export namespace mean_field::operators {
double targetMass{0.0};
};
struct FixedMassJacobianInput final {
const mfem::Vector &densityVariation;
const mfem::Vector &displacementVariation;
};
struct FixedMassJacobianTransposeOutput final {
mfem::Vector &densityDual;
mfem::Vector &displacementDual;
};
struct PreparedMassNormalizationReport final {
bool rebuiltStaticPlan{false};
bool refreshedGeometry{false};
@@ -43,12 +55,15 @@ export namespace mean_field::operators {
[[nodiscard]] bool DidAnyWork() const noexcept {
return rebuiltStaticPlan || refreshedGeometry || refreshedDensity || updatedTargetMass || assembledResidual;
}
constexpr auto operator<=>(const PreparedMassNormalizationReport &) const = default;
};
struct PreparedMassNormalizationActionStatistics final {
std::uint64_t densityApplications{0};
std::uint64_t displacementApplications{0};
std::uint64_t completeApplications{0};
std::uint64_t transposeApplications{0};
constexpr auto operator<=>(const PreparedMassNormalizationActionStatistics &) const = default;
};
@@ -66,6 +81,13 @@ export namespace mean_field::operators {
*/
class PreparedMassNormalizationOperator final {
public:
using SpecificationType = models::FixedTotalMass;
using CompiledConstraintType = models::CompiledFixedMass;
using Dependencies = MassNormalizationDependencies;
using Report = PreparedMassNormalizationReport;
using JacobianInput = FixedMassJacobianInput;
using JacobianTransposeOutput = FixedMassJacobianTransposeOutput;
PreparedMassNormalizationOperator(
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
@@ -82,6 +104,11 @@ export namespace mean_field::operators {
const MassNormalizationDependencies &dependencies
);
PreparedMassNormalizationReport Prepare(
const models::CompiledFixedMass &constraint,
const MassNormalizationDependencies &dependencies
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -100,6 +127,22 @@ export namespace mean_field::operators {
mfem::Vector &action
) const;
void ApplyJacobian(
const FixedMassJacobianInput &input,
mfem::Vector &action
) const;
void ApplyCompleteJacobianTransposeAction(
double residualDual,
mfem::Vector &densityDual,
mfem::Vector &displacementDual
) const;
void ApplyJacobianTranspose(
const mfem::Vector &residualDual,
FixedMassJacobianTransposeOutput output
) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] double GetCurrentMass() const;
[[nodiscard]] double GetTargetMass() const;
@@ -145,6 +188,16 @@ export namespace mean_field::operators {
[[nodiscard]] double EvaluateDisplacementActionLocal(const mfem::Vector &displacementVariation) const;
void AssembleDensityTransposeAction(
double residualDual,
mfem::Vector &densityDual
) const;
void AssembleDisplacementTransposeAction(
double residualDual,
mfem::Vector &displacementDual
) const;
[[nodiscard]] double GlobalSum(double localValue) const;
const fem::FEM &m_fem;
@@ -167,6 +220,10 @@ export namespace mean_field::operators {
bool m_isPrepared{false};
};
using PreparedFixedMass = PreparedMassNormalizationOperator;
static_assert(PreparedConstraint<PreparedFixedMass>);
using MassNormalizationLayout = utils::blocks::form_layout<utils::blocks::barotropic_equilibrium_form>;
class PreparedMassNormalizationJacobianOperator final : public mfem::Operator {
@@ -181,6 +238,11 @@ export namespace mean_field::operators {
mfem::Vector &action
) const override;
void MultTranspose(
const mfem::Vector &residualDual,
mfem::Vector &stateDual
) const override;
[[nodiscard]] const MassNormalizationLayout &GetLayout() const noexcept;
private:

View File

@@ -3,6 +3,7 @@ module;
#include <compare>
#include <cstdint>
#include <optional>
#include <vector>
#include <mfem.hpp>
@@ -103,6 +104,25 @@ export namespace mean_field::operators {
private:
void VerifyPrepared() const;
void PrepareElementData();
void ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
) const;
struct ElementPAData {
int elementId{-1};
mfem::Array<int> densityDofs;
mfem::Array<int> displacementDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
mfem::DenseMatrix inverseElementJacobians;
mfem::DenseMatrix centrifugalAccelerations;
mfem::Vector baseDensityValues;
mfem::Vector quadratureWeights;
};
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domainMapper;
@@ -111,9 +131,24 @@ export namespace mean_field::operators {
std::optional<physics::RigidRotation> m_rotation;
mfem::Vector m_cachedResidual;
std::vector<ElementPAData> m_elements;
mutable mfem::Vector m_densityVariationTrue;
mutable mfem::Vector m_displacementVariationTrue;
mutable mfem::Vector m_actionTrue;
mutable mfem::Vector m_densityVariationLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localAction;
mutable mfem::Vector m_elementDensityVariation;
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_elementAction;
mutable mfem::Vector m_densityShape;
mutable mfem::Vector m_displacementShape;
mutable mfem::Vector m_physicalPositionVariation;
mutable mfem::Vector m_centrifugalAcceleration;
mutable mfem::Vector m_centrifugalAccelerationVariation;
mutable mfem::Vector m_weightedForce;
mutable mfem::DenseMatrix m_referenceDisplacementDShape;
mutable mfem::DenseMatrix m_referenceDisplacementJacobian;
context::rotational_displacement_force::RotationalDisplacementForceDependencies m_preparedDependencies;

View File

@@ -15,6 +15,7 @@ export import :fem;
export import :field.mfem;
export import :mapping.domain_mapper;
export import :model.stellar;
export import :model.typed_stellar;
export import :operators.context.gravity_field;
export import :operators.gravity_field;
export import :operators.gravity_field_jacobian;
@@ -23,6 +24,7 @@ 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 :operators.root_manifest;
export import :physics.rigid_rotation;
export import :utils.blocks;
@@ -82,6 +84,16 @@ export namespace mean_field::operators {
using StellarEquilibriumLayout =
utils::blocks::form_layout<utils::blocks::surface_deformed_stellar_equilibrium_form>;
using StellarEquilibriumSpecificationModel =
model::StellarModel<models::SpecificationSet<eos::Polytrope, models::FixedTotalMass, surface::Isobaric>>;
using StellarEquilibriumSystemManifest = EquilibriumSystemManifest<
StellarEquilibriumSpecificationModel,
utils::blocks::surface_deformed_stellar_equilibrium_form,
utils::blocks::surface_deformed_stellar_equilibrium_jacobian_form>;
using StellarEquilibriumRootManifest = StellarEquilibriumSystemManifest;
class PreparedStellarEquilibriumOperator final : public mfem::Operator {
public:
template <models::StellarModelType Model>
@@ -101,12 +113,27 @@ export namespace mean_field::operators {
f,
domainMapper,
stellarModel.equationOfState(),
stellarModel.targetMass(),
models::compileConstraint(models::FixedTotalMass{dimensions::MassValue{stellarModel.targetMass()}}),
PressureSurfaceConstraintView{stellarModel.compiledSurfaceConstraint()},
deformation::PreparedDomainDeformationRuntime{stellarModel.compileDomainDeformation(f)}
) {
}
/*
* Authoritative construction path for a compiled equilibrium system.
* The caller owns the EOS and compiled surface constraint for this
* operator's lifetime; the remaining compiled contributions are
* transferred into the operator.
*/
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
models::CompiledFixedMass fixedMassConstraint,
PressureSurfaceConstraintView surfaceConstraint,
deformation::PreparedDomainDeformationRuntime domainDeformation
);
PreparedStellarEquilibriumOperator(const PreparedStellarEquilibriumOperator &) = delete;
PreparedStellarEquilibriumOperator &operator=(const PreparedStellarEquilibriumOperator &) = delete;
PreparedStellarEquilibriumOperator(PreparedStellarEquilibriumOperator &&) = delete;
@@ -128,6 +155,12 @@ export namespace mean_field::operators {
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] double GetTargetMass() const noexcept;
[[nodiscard]] const StellarEquilibriumLayout &GetLayout() const noexcept;
[[nodiscard]] const StellarEquilibriumRootManifest &GetRootManifest() const noexcept;
[[nodiscard]] RootStateView<utils::blocks::surface_deformed_stellar_equilibrium_form>
GetRootStateView(const mfem::Vector &state) const;
[[nodiscard]] ResidualView<utils::blocks::surface_deformed_stellar_equilibrium_form>
GetResidualView(mfem::Vector &residual) const;
[[nodiscard]] RootConstraintReport GetFixedMassReport() const;
[[nodiscard]] const StellarEquilibriumDependencies &GetDependencies() const;
[[nodiscard]] const PreparedStellarEquilibriumStatistics &GetStatistics() const noexcept;
@@ -160,16 +193,7 @@ export namespace mean_field::operators {
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
PressureSurfaceConstraintView surfaceConstraint,
deformation::PreparedDomainDeformationRuntime domainDeformation
);
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
models::CompiledFixedMass fixedMassConstraint,
PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData
);
@@ -177,7 +201,7 @@ export namespace mean_field::operators {
void AssembleResidual();
void VerifyPrepared() const;
StellarEquilibriumLayout m_layout;
StellarEquilibriumRootManifest m_rootManifest;
mfem::Array<int> m_gravityStateOffsets;
context::gravity_field::GravityFieldLinearizationContext m_gravityContext;
@@ -198,7 +222,7 @@ export namespace mean_field::operators {
mfem::Vector m_generatedVolumeDisplacement;
mfem::Vector m_fullMechanicalResidual;
mfem::Vector m_cachedResidual;
double m_targetMass{0.0};
models::CompiledFixedMass m_fixedMassConstraint;
mutable PreparedStellarEquilibriumStatistics m_statistics;
bool m_isPrepared{false};

View File

@@ -14,10 +14,10 @@ export import :field.mfem;
export import :surface.compiled;
namespace mean_field::operators::detail {
template <eos::ThermodynamicQuantityType Quantity> struct SingleQuantitySurfaceState final {
eos::QuantityValue<Quantity> quantityValue;
template <dimensions::ThermodynamicQuantityType Quantity> struct SingleQuantitySurfaceState final {
dimensions::QuantityValue<Quantity> quantityValue;
[[nodiscard]] eos::QuantityValue<Quantity> value(Quantity) const noexcept {
[[nodiscard]] dimensions::QuantityValue<Quantity> value(Quantity) const noexcept {
return quantityValue;
}
};
@@ -37,7 +37,7 @@ export namespace mean_field::operators {
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>::PhysicalQuantity, dimensions::quantity::Pressure> &&
std::same_as<
typename std::remove_cvref_t<Candidate>::SurfaceDependencies::RowField,
typename std::remove_cvref_t<Candidate>::CarrierField> &&
@@ -112,7 +112,7 @@ export namespace mean_field::operators {
for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) {
const detail::SingleQuantitySurfaceState<CarrierQuantity> state{
eos::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
dimensions::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
rowResidual(surfaceRows.reduced_dofs()[surfaceIndex]) =
static_cast<const Constraint *>(constraint)->residual(state);
@@ -132,10 +132,10 @@ export namespace mean_field::operators {
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)}
dimensions::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
const detail::SingleQuantitySurfaceState<CarrierQuantity> variation{
eos::QuantityValue<CarrierQuantity>{stateVariation(reducedDof)}
dimensions::QuantityValue<CarrierQuantity>{stateVariation(reducedDof)}
};
rowAction(reducedDof) = static_cast<const Constraint *>(constraint)->jacobianAction(state, variation);
}

View File

@@ -0,0 +1,601 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <concepts>
#include <cstddef>
#include <optional>
#include <span>
#include <stdexcept>
#include <string_view>
#include <type_traits>
#include <mfem.hpp>
export module mean_field:operators.root_manifest;
export import :model.compiled_fixed_mass;
export import :model.compiled_fixed_central_density;
export import :model.specifications;
export import :utils.blocks;
export namespace mean_field::operators {
enum class RootBlockKind { value, residual };
enum class RootBlockProvenance { physical_operator, model_specification };
enum class RootRowInjection { physical_equation, append_global, replace_carrier_rows };
enum class RootColumnPolicy { physical_state, existing_physical_multiplier, solver_border, no_column };
enum class RootScalePolicy { unscaled, target_relative };
struct RootBlockDescriptor final {
std::string_view stableId;
std::string_view symbol;
RootBlockKind kind;
RootBlockProvenance provenance;
std::string_view source;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
RootScalePolicy scalePolicy;
int canonicalIndex;
int offset;
int size;
double scale;
};
struct RootRowReplacementDescriptor final {
std::string_view stableId;
std::string_view sourceSpecification;
models::SpecificationRole role;
int carrierResidualBlock;
int replacedRowCount;
};
struct RootConstraintDescriptor final {
std::string_view stableId;
models::SpecificationRole role;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
int valueBlock;
int residualBlock;
int rowArity;
int columnArity;
double target;
std::optional<double> carrierTarget;
std::string_view targetUnits;
std::string_view residualUnits;
double residualScale;
};
struct CentralDensityManifestInput final {
double targetDensity;
double targetEnthalpy;
int centerDofCount;
};
struct RootConstraintReport final {
RootConstraintDescriptor descriptor;
double achieved;
double dimensionalResidual;
double scaledResidual;
};
namespace detail {
struct StaticRootBlockDescriptor final {
std::string_view stableId;
std::string_view symbol;
RootBlockProvenance provenance;
std::string_view source;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
RootScalePolicy scalePolicy;
};
template <typename Block> struct RootBlockTraits;
#define MEAN_FIELD_PHYSICAL_VALUE_BLOCK(BlockType, StableId, Symbol) \
template <> struct RootBlockTraits<BlockType> { \
static constexpr StaticRootBlockDescriptor descriptor{ \
StableId, \
Symbol, \
RootBlockProvenance::physical_operator, \
"stellar_equilibrium", \
RootRowInjection::physical_equation, \
RootColumnPolicy::physical_state, \
RootScalePolicy::unscaled \
}; \
}
#define MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(BlockType, StableId, Symbol) \
template <> struct RootBlockTraits<BlockType> { \
static constexpr StaticRootBlockDescriptor descriptor{ \
StableId, \
Symbol, \
RootBlockProvenance::physical_operator, \
"stellar_equilibrium", \
RootRowInjection::physical_equation, \
RootColumnPolicy::no_column, \
RootScalePolicy::unscaled \
}; \
}
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::density::mass::value,
"density",
"rho"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::displacement::geometry::value,
"volume_displacement",
"d"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::surface_deformation::parameters::value,
"surface_deformation",
"q"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::gravity::gradient::value,
"gravity_gradient",
"g"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::gravity::poisson::value,
"gravity_potential",
"Phi"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::enthalpy::specific::value,
"specific_enthalpy",
"h"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::gravity::gradient::residual,
"gravity_gradient_relation",
"R_g"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::gravity::poisson::residual,
"poisson_balance",
"R_Phi"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::density::mass::residual,
"barotropic_closure",
"R_rho"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::displacement::geometry::residual,
"mechanical_balance",
"R_d"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::surface_deformation::shape_equilibrium::residual,
"surface_shape_balance",
"R_q"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::enthalpy::specific::residual,
"hydrostatic_balance",
"R_h"
);
#undef MEAN_FIELD_PHYSICAL_VALUE_BLOCK
#undef MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK
template <> struct RootBlockTraits<utils::blocks::fixed_total_mass::mass_normalization::value> {
static constexpr StaticRootBlockDescriptor descriptor{
"fixed_total_mass.multiplier",
"C",
RootBlockProvenance::model_specification,
"FixedTotalMass",
RootRowInjection::physical_equation,
RootColumnPolicy::existing_physical_multiplier,
RootScalePolicy::unscaled
};
};
template <> struct RootBlockTraits<utils::blocks::fixed_total_mass::mass_normalization::residual> {
static constexpr StaticRootBlockDescriptor descriptor{
"fixed_total_mass.residual",
"R_M",
RootBlockProvenance::model_specification,
"FixedTotalMass",
RootRowInjection::append_global,
RootColumnPolicy::no_column,
RootScalePolicy::target_relative
};
};
template <> struct RootBlockTraits<utils::blocks::fixed_central_density::central_value::value> {
static constexpr StaticRootBlockDescriptor descriptor{
"fixed_central_density.border",
"lambda_rho_c",
RootBlockProvenance::model_specification,
"FixedCentralDensity",
RootRowInjection::physical_equation,
RootColumnPolicy::solver_border,
RootScalePolicy::unscaled
};
};
template <> struct RootBlockTraits<utils::blocks::fixed_central_density::central_value::residual> {
static constexpr StaticRootBlockDescriptor descriptor{
"fixed_central_density.residual", "R_rho_c",
RootBlockProvenance::model_specification, "FixedCentralDensity",
RootRowInjection::append_global, RootColumnPolicy::no_column,
RootScalePolicy::target_relative
};
};
template <typename Block>
[[nodiscard]] constexpr double blockScale(
const double fixedMassScale,
const double centralDensityScale
) noexcept {
if constexpr (std::same_as<Block, utils::blocks::fixed_total_mass::mass_normalization::residual>) {
return fixedMassScale;
} else if constexpr (std::same_as<Block, utils::blocks::fixed_central_density::central_value::residual>) {
return centralDensityScale;
} else {
return 1.0;
}
}
template <
RootBlockKind Kind,
typename... Blocks>
[[nodiscard]] std::array<
RootBlockDescriptor,
sizeof...(Blocks)>
makeBlockDescriptors(
const mfem::Array<int> &offsets,
const double fixedMassScale,
const double centralDensityScale,
utils::blocks::type_list<Blocks...>
) {
std::array<RootBlockDescriptor, sizeof...(Blocks)> descriptors{};
int index = 0;
((descriptors[index] =
{.stableId = RootBlockTraits<Blocks>::descriptor.stableId,
.symbol = RootBlockTraits<Blocks>::descriptor.symbol,
.kind = Kind,
.provenance = RootBlockTraits<Blocks>::descriptor.provenance,
.source = RootBlockTraits<Blocks>::descriptor.source,
.rowInjection = RootBlockTraits<Blocks>::descriptor.rowInjection,
.columnPolicy = RootBlockTraits<Blocks>::descriptor.columnPolicy,
.scalePolicy = RootBlockTraits<Blocks>::descriptor.scalePolicy,
.canonicalIndex = index,
.offset = offsets[index],
.size = offsets[index + 1] - offsets[index],
.scale = blockScale<Blocks>(fixedMassScale, centralDensityScale)},
++index),
...);
return descriptors;
}
template <models::SpecifiedModelType Model>
inline constexpr bool hasCentralDensity = Model::template containsSpecification<models::FixedCentralDensity>;
template <models::SpecifiedModelType Model>
inline constexpr std::size_t rootConstraintCount = 2 + (hasCentralDensity<Model> ? 1 : 0);
template <
models::SpecifiedModelType Model,
typename Form>
[[nodiscard]] std::array<
RootConstraintDescriptor,
rootConstraintCount<Model>>
makeConstraintDescriptors(
const double targetMass,
const double targetSurfacePressure,
const double fixedMassScale,
const std::optional<CentralDensityManifestInput> centralDensity
) {
std::array<RootConstraintDescriptor, rootConstraintCount<Model>> descriptors{};
descriptors[0] = {
.stableId = "FixedTotalMass",
.role = models::SpecificationRole::invariant,
.rowInjection = RootRowInjection::append_global,
.columnPolicy = RootColumnPolicy::existing_physical_multiplier,
.valueBlock = models::FixedMassLayoutRequest::valueBlock<Form>().index,
.residualBlock = models::FixedMassLayoutRequest::residualBlock<Form>().index,
.rowArity = 1,
.columnArity = 1,
.target = targetMass,
.carrierTarget = targetMass,
.targetUnits = "mass",
.residualUnits = "mass",
.residualScale = fixedMassScale
};
descriptors[1] = {
.stableId = "IsobaricSurface",
.role = models::SpecificationRole::boundary_condition,
.rowInjection = RootRowInjection::replace_carrier_rows,
.columnPolicy = RootColumnPolicy::no_column,
.valueBlock = -1,
.residualBlock =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term).index,
.rowArity = 0,
.columnArity = 0,
.target = targetSurfacePressure,
.carrierTarget = std::nullopt,
.targetUnits = "pressure",
.residualUnits = "specific_enthalpy",
.residualScale = 1.0
};
if constexpr (hasCentralDensity<Model>) {
if (!centralDensity.has_value()) {
throw std::invalid_argument(
"A model containing FixedCentralDensity requires central-density manifest metadata."
);
}
descriptors[2] = {
.stableId = "FixedCentralDensity",
.role = models::SpecificationRole::phase_condition,
.rowInjection = RootRowInjection::append_global,
.columnPolicy = RootColumnPolicy::solver_border,
.valueBlock = models::CentralDensityLayoutRequest::valueBlock<Form>().index,
.residualBlock = models::CentralDensityLayoutRequest::residualBlock<Form>().index,
.rowArity = 1,
.columnArity = 1,
.target = centralDensity->targetDensity,
.carrierTarget = centralDensity->targetEnthalpy,
.targetUnits = "density",
.residualUnits = "specific_enthalpy",
.residualScale = std::max(std::abs(centralDensity->targetEnthalpy), 1.0e-300)
};
} else if (centralDensity.has_value()) {
throw std::invalid_argument(
"Central-density manifest metadata was provided to a model without FixedCentralDensity."
);
}
return descriptors;
}
} // namespace detail
template <typename Form> class RootStateView final {
public:
RootStateView(
const mfem::Vector &state,
const utils::blocks::form_layout<Form> &layout
)
: m_state(state),
m_layout(layout) {
if (state.Size() != layout.value_offsets().Last()) {
throw std::invalid_argument("RootStateView received a vector with the wrong size.");
}
}
template <typename Term> [[nodiscard]] mfem::Vector block(const Term &term) const {
constexpr auto valueBlock = utils::blocks::get_value_block<Form>(term);
return mfem::Vector(
const_cast<mfem::real_t *>(m_state.GetData()) + m_layout.offset(valueBlock), m_layout.size(valueBlock)
);
}
[[nodiscard]] const mfem::Vector &vector() const noexcept {
return m_state;
}
private:
const mfem::Vector &m_state;
const utils::blocks::form_layout<Form> &m_layout;
};
template <typename Form> class ResidualView final {
public:
ResidualView(
mfem::Vector &residual,
const utils::blocks::form_layout<Form> &layout
)
: m_residual(residual),
m_layout(layout) {
if (residual.Size() != layout.residual_offsets().Last()) {
throw std::invalid_argument("ResidualView received a vector with the wrong size.");
}
}
template <typename Term> [[nodiscard]] mfem::Vector block(const Term &term) const {
constexpr auto residualBlock = utils::blocks::get_residual_block<Form>(term);
return mfem::Vector(m_residual.GetData() + m_layout.offset(residualBlock), m_layout.size(residualBlock));
}
template <typename Term>
void assign(
const Term &term,
const mfem::Vector &source
) const {
mfem::Vector destination = block(term);
if (destination.Size() != source.Size()) {
throw std::invalid_argument("ResidualView block assignment has the wrong size.");
}
destination = source;
}
[[nodiscard]] mfem::Vector &vector() const noexcept {
return m_residual;
}
private:
mfem::Vector &m_residual;
const utils::blocks::form_layout<Form> &m_layout;
};
template <models::SpecifiedModelType Model, typename Form, typename JacobianForm>
requires utils::blocks::valid_jacobian_form<Form, JacobianForm>
class CompiledRootManifest final {
public:
using ModelType = Model;
using FormType = Form;
using JacobianType = JacobianForm;
using Layout = utils::blocks::form_layout<Form>;
using StateView = RootStateView<Form>;
using DirectionView = RootStateView<Form>;
using RootResidualView = ResidualView<Form>;
static constexpr models::ModelCompilationClass compilationClass = Model::compilationClass;
static constexpr bool symbolicallySquare = Model::symbolicallySquare;
CompiledRootManifest(
const std::array<
int,
Form::value_block_count> &valueSizes,
const std::array<
int,
Form::residual_block_count> &residualSizes,
const double targetMass,
const double targetSurfacePressure,
const int replacedSurfaceRowCount,
const std::optional<CentralDensityManifestInput> centralDensity = std::nullopt
)
: m_layout(
valueSizes,
residualSizes
),
m_fixedMassScale(
std::max(
std::abs(targetMass),
1.0e-300
)
),
m_centralDensityScale(
centralDensity.has_value() ? std::max(
std::abs(centralDensity->targetEnthalpy),
1.0e-300
)
: 1.0
),
m_valueBlocks(
detail::makeBlockDescriptors<RootBlockKind::value>(
m_layout.value_offsets(),
m_fixedMassScale,
m_centralDensityScale,
typename Form::value_blocks{}
)
),
m_residualBlocks(
detail::makeBlockDescriptors<RootBlockKind::residual>(
m_layout.residual_offsets(),
m_fixedMassScale,
m_centralDensityScale,
typename Form::residual_blocks{}
)
),
m_replacements{RootRowReplacementDescriptor{
.stableId = "isobaric_surface.replacement",
.sourceSpecification = "IsobaricSurface",
.role = models::SpecificationRole::boundary_condition,
.carrierResidualBlock =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term).index,
.replacedRowCount = replacedSurfaceRowCount
}},
m_constraints(
detail::makeConstraintDescriptors<
Model,
Form>(
targetMass,
targetSurfacePressure,
m_fixedMassScale,
centralDensity
)
) {
if (replacedSurfaceRowCount < 0) {
throw std::invalid_argument(
"An equilibrium-system manifest cannot contain a negative replacement-row count."
);
}
if (centralDensity.has_value() && centralDensity->centerDofCount < 0) {
throw std::invalid_argument(
"An equilibrium-system manifest cannot contain a negative central-DOF count."
);
}
if constexpr (compilationClass == models::EquilibriumSystemCompilation::complete_equilibrium_system) {
if (m_layout.value_offsets().Last() != m_layout.residual_offsets().Last()) {
throw std::invalid_argument(
"A complete equilibrium system must have equal state and equation dimensions."
);
}
}
}
[[nodiscard]] const Layout &layout() const noexcept {
return m_layout;
}
[[nodiscard]] StateView stateView(const mfem::Vector &state) const {
return {state, m_layout};
}
[[nodiscard]] DirectionView directionView(const mfem::Vector &direction) const {
return {direction, m_layout};
}
[[nodiscard]] RootResidualView residualView(mfem::Vector &residual) const {
return {residual, m_layout};
}
[[nodiscard]] std::span<const RootBlockDescriptor> valueBlocks() const noexcept {
return m_valueBlocks;
}
[[nodiscard]] std::span<const RootBlockDescriptor> residualBlocks() const noexcept {
return m_residualBlocks;
}
[[nodiscard]] std::span<const RootRowReplacementDescriptor> rowReplacements() const noexcept {
return m_replacements;
}
[[nodiscard]] std::span<const RootConstraintDescriptor> constraints() const noexcept {
return m_constraints;
}
[[nodiscard]] static constexpr std::span<const models::RuntimeSpecificationDescriptor>
specificationDescriptors() noexcept {
return Model::runtimeSpecificationDescriptors();
}
[[nodiscard]] RootConstraintReport fixedMassReport(const double achievedMass) const {
const RootConstraintDescriptor &descriptor = m_constraints[0];
const double residual = achievedMass - descriptor.target;
return {
.descriptor = descriptor,
.achieved = achievedMass,
.dimensionalResidual = residual,
.scaledResidual = residual / descriptor.residualScale
};
}
private:
Layout m_layout;
double m_fixedMassScale;
double m_centralDensityScale;
std::array<RootBlockDescriptor, Form::value_block_count> m_valueBlocks;
std::array<RootBlockDescriptor, Form::residual_block_count> m_residualBlocks;
std::array<RootRowReplacementDescriptor, 1> m_replacements;
std::array<RootConstraintDescriptor, detail::rootConstraintCount<Model>> m_constraints;
};
// Physics-facing names for the public equilibrium-system boundary. The
// root-oriented names remain available while existing solver consumers
// migrate, but new APIs should expose these aliases.
using EquilibriumBlockKind = RootBlockKind;
using EquilibriumBlockProvenance = RootBlockProvenance;
using EquilibriumEquationInjection = RootRowInjection;
using EquilibriumGeneratedVariablePolicy = RootColumnPolicy;
using EquilibriumScalePolicy = RootScalePolicy;
using EquilibriumBlockDescriptor = RootBlockDescriptor;
using EquilibriumEquationReplacementDescriptor = RootRowReplacementDescriptor;
using EquilibriumSpecificationDescriptor = RootConstraintDescriptor;
using EquilibriumSpecificationReport = RootConstraintReport;
template <typename Form> using EquilibriumStateView = RootStateView<Form>;
template <typename Form> using EquilibriumResidualView = ResidualView<Form>;
template <models::SpecifiedModelType Model, typename Form, typename JacobianForm>
requires utils::blocks::valid_jacobian_form<Form, JacobianForm>
using EquilibriumSystemManifest = CompiledRootManifest<Model, Form, JacobianForm>;
} // namespace mean_field::operators

View File

@@ -0,0 +1,210 @@
module;
#include <concepts>
#include <cstddef>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.stellar_equilibrium_problem;
export import :deformation.domain_deformation;
export import :equilibrium.stellar_discretization;
export import :model.typed_stellar;
export import :operators.prepared_central_density_stellar_equilibrium;
export import :surface.compiler;
export namespace mean_field::equilibrium {
template <typename Candidate>
concept StellarEquilibriumModel = model::StellarModelType<Candidate> && requires {
requires std::remove_cvref_t<Candidate>::template containsSpecification<eos::Polytrope>;
requires std::remove_cvref_t<Candidate>::template containsSpecification<surface::Isobaric>;
requires std::remove_cvref_t<Candidate>::template containsSpecification<models::FixedTotalMass>;
requires std::remove_cvref_t<Candidate>::specificationCount ==
3 + static_cast<std::size_t>(
std::remove_cvref_t<Candidate>::template containsSpecification<models::FixedCentralDensity>
);
};
template <StellarEquilibriumModel Model> class StellarEquilibriumProblem final {
public:
using ModelType = std::remove_cvref_t<Model>;
static constexpr bool hasFixedCentralDensity =
ModelType::template containsSpecification<models::FixedCentralDensity>;
static constexpr bool symbolicallySquare = ModelType::symbolicallySquare;
using PreparedOperatorType = std::conditional_t<
hasFixedCentralDensity,
operators::PreparedCentralDensityStellarEquilibriumOperator,
operators::PreparedStellarEquilibriumOperator>;
using CompiledSurfaceConstraintType =
surface::CompiledPressureSurfaceConstraintT<surface::BarotropicSurfaceFormulation, eos::Polytrope>;
StellarEquilibriumProblem(
ModelType stellarModel,
const StellarDiscretization discretization
)
requires(!hasFixedCentralDensity)
: m_stellarModel(std::move(stellarModel)),
m_discretization(discretization),
m_compiledSurfaceConstraint(CompileSurfaceConstraint(m_stellarModel)),
m_preparedOperator(
m_discretization.finiteElementModel(),
m_discretization.domainMapper(),
m_stellarModel.template specification<eos::Polytrope>(),
models::compileConstraint(m_stellarModel.template specification<models::FixedTotalMass>()),
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
CompileDefaultDomainDeformation(m_discretization.finiteElementModel())
) {
VerifyProblem();
}
StellarEquilibriumProblem(
ModelType stellarModel,
const StellarDiscretization discretization
)
requires hasFixedCentralDensity
: m_stellarModel(std::move(stellarModel)),
m_discretization(discretization),
m_compiledSurfaceConstraint(CompileSurfaceConstraint(m_stellarModel)),
m_preparedOperator(
m_discretization.finiteElementModel(),
m_discretization.domainMapper(),
m_stellarModel.template specification<eos::Polytrope>(),
models::compileConstraint(m_stellarModel.template specification<models::FixedTotalMass>()),
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
CompileDefaultDomainDeformation(m_discretization.finiteElementModel()),
models::compileConstraint(
m_stellarModel.template specification<models::FixedCentralDensity>(),
m_stellarModel.template specification<eos::Polytrope>()
)
) {
VerifyProblem();
}
StellarEquilibriumProblem(const StellarEquilibriumProblem &) = delete;
StellarEquilibriumProblem &operator=(const StellarEquilibriumProblem &) = delete;
StellarEquilibriumProblem(StellarEquilibriumProblem &&) = delete;
StellarEquilibriumProblem &operator=(StellarEquilibriumProblem &&) = delete;
[[nodiscard]] const ModelType &GetStellarModel() const noexcept {
return m_stellarModel;
}
[[nodiscard]] const StellarDiscretization &GetDiscretization() const noexcept {
return m_discretization;
}
[[nodiscard]] const CompiledSurfaceConstraintType &GetCompiledSurfaceConstraint() const noexcept {
return m_compiledSurfaceConstraint;
}
[[nodiscard]] PreparedOperatorType &GetPreparedOperator() noexcept {
return m_preparedOperator;
}
[[nodiscard]] const PreparedOperatorType &GetPreparedOperator() const noexcept {
return m_preparedOperator;
}
[[nodiscard]] const auto &GetManifest() const noexcept {
return m_preparedOperator.GetRootManifest();
}
[[nodiscard]] const field::FieldBoundaryDofMap &GetPressureSurfaceRows() const noexcept {
if constexpr (hasFixedCentralDensity) {
return m_preparedOperator.GetPhysicalOperator().GetSurfaceConstraintOperator().GetSurfaceRows();
} else {
return m_preparedOperator.GetSurfaceConstraintOperator().GetSurfaceRows();
}
}
[[nodiscard]] int StateSize() const noexcept {
return m_preparedOperator.Width();
}
[[nodiscard]] int EquationSize() const noexcept {
return m_preparedOperator.Height();
}
[[nodiscard]] const mfem::Operator &GetLinearizationOperator() const noexcept {
return m_preparedOperator;
}
[[nodiscard]] auto Prepare(
const mfem::Vector &state,
const operators::StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
return m_preparedOperator.Prepare(state, dependencies, rotation);
}
void BuildResidual(mfem::Vector &residual) const {
m_preparedOperator.BuildResidual(residual);
}
void ApplyLinearization(
const mfem::Vector &direction,
mfem::Vector &action
) const {
m_preparedOperator.Mult(direction, action);
}
private:
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
return surface::compilePressureSurfaceConstraint<surface::BarotropicSurfaceFormulation>(
stellarModel.template specification<surface::Isobaric>(),
stellarModel.template specification<eos::Polytrope>()
);
}
[[nodiscard]] static deformation::PreparedDomainDeformationRuntime
CompileDefaultDomainDeformation(fem::FEM &finiteElementModel) {
MFEM_VERIFY(
finiteElementModel.mesh != nullptr,
"Default stellar domain-deformation compilation requires a physical mesh."
);
mfem::Vector referenceCenter(finiteElementModel.mesh->SpaceDimension());
referenceCenter = 0.0;
return deformation::PreparedDomainDeformationRuntime{deformation::compileDomainDeformation(
deformation::NodalRadialSurface{std::move(referenceCenter)},
deformation::PowerLawRadialInteriorExtension{}, deformation::FixedInfinityRadialVacuumExtension{},
finiteElementModel
)};
}
void VerifyProblem() const {
MFEM_VERIFY(symbolicallySquare, "A stellar equilibrium problem must be symbolically square.");
MFEM_VERIFY(
StateSize() == EquationSize(),
"The discretized stellar equilibrium problem has unequal state and equation dimensions."
);
MFEM_VERIFY(m_discretization.isCurrent(), "The stellar equilibrium problem has a stale discretization.");
}
ModelType m_stellarModel;
StellarDiscretization m_discretization;
CompiledSurfaceConstraintType m_compiledSurfaceConstraint;
PreparedOperatorType m_preparedOperator;
};
template <StellarEquilibriumModel Model>
[[nodiscard]] auto discretize(
Model &&stellarModel,
const StellarDiscretization discretization
) {
using ModelType = std::remove_cvref_t<Model>;
return StellarEquilibriumProblem<ModelType>{std::forward<Model>(stellarModel), discretization};
}
template <StellarEquilibriumModel Model>
[[nodiscard]] auto discretize(
Model &&stellarModel,
fem::FEM &finiteElementModel
) {
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel});
}
} // namespace mean_field::equilibrium

View File

@@ -0,0 +1,26 @@
module;
#include <type_traits>
#include <utility>
export module mean_field:operators.stellar_equilibrium_system;
export import :operators.stellar_equilibrium_problem;
export namespace mean_field::equilibrium {
// Transitional source-compatible names. New code should use
// StellarEquilibriumProblem and equilibrium::discretize.
template <typename Candidate>
concept CurrentlySupportedStellarModel = StellarEquilibriumModel<Candidate>;
template <StellarEquilibriumModel Model> using StellarEquilibriumSystem = StellarEquilibriumProblem<Model>;
template <StellarEquilibriumModel Model>
[[nodiscard]] auto makeStellarEquilibriumSystem(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
Model &&stellarModel
) {
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel, domainMapper});
}
} // namespace mean_field::equilibrium

View File

@@ -6,6 +6,12 @@ export module mean_field:physics.gravity;
export import :fem;
export namespace mean_field::physics {
struct GravitySolveOptions final {
double relativeTolerance{1.0e-12};
double absoluteTolerance{1.0e-15};
int maximumIterations{1000};
};
struct GravitySolution {
mfem::ParGridFunction gradPhi;
mfem::ParGridFunction phi;
@@ -16,6 +22,13 @@ export namespace mean_field::physics {
}
};
GravitySolution solve_gravity_field(
fem::FEM &f,
const GravitySolveOptions &options,
const mfem::GridFunction &rho,
const mfem::GridFunction &displacement
);
GravitySolution solve_gravity_field(
fem::FEM &f,
const utils::Args &args,

View File

@@ -0,0 +1,120 @@
module;
#include <cmath>
#include <concepts>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <vector>
#include <mfem.hpp>
export module mean_field:seed.lane_emden;
export import :dimensions.quantities;
export import :eos.polytrope;
export import :model.typed_stellar;
export namespace mean_field::seed {
struct DimensionlessLaneEmdenSolution final {
mfem::Vector coordinate;
mfem::Vector theta;
mfem::Vector thetaDerivative;
std::optional<double> firstZeroCoordinate;
};
/*
* Integrate the dimensionless Lane-Emden equation from the regular center
* to either the first zero of theta or coordinateLimit, whichever occurs
* first. This numerical kernel also supports the n = 0 and n = 5 analytic
* benchmark cases even though they do not both define admissible seeds for
* the current Polytrope EOS and finite stellar domain.
*/
[[nodiscard]] DimensionlessLaneEmdenSolution integrateLaneEmden(
double polytropicIndex,
double coordinateLimit,
double integrationStep = 1.0e-3
);
struct RadialProfile final {
mfem::Vector radius;
mfem::Vector density;
mfem::Vector specificEnthalpy;
dimensions::LengthValue stellarRadius;
dimensions::DensityValue centralDensity;
dimensions::SpecificEnthalpyValue centralSpecificEnthalpy;
};
class LaneEmden final {
public:
struct Parameters final {
std::optional<dimensions::DensityValue> centralDensity{std::nullopt};
int radialSampleCount{512};
};
LaneEmden()
: m_centralDensity(std::nullopt),
m_radialSampleCount(512) {
}
explicit LaneEmden(const Parameters parameters)
: m_centralDensity(parameters.centralDensity),
m_radialSampleCount(parameters.radialSampleCount) {
if (m_centralDensity.has_value() &&
(!std::isfinite(m_centralDensity->value()) || m_centralDensity->value() <= 0.0)) {
throw std::invalid_argument("A Lane-Emden seed central density must be finite and positive.");
}
if (m_radialSampleCount < 2) {
throw std::invalid_argument("A Lane-Emden seed requires at least two radial samples.");
}
}
[[nodiscard]] const std::optional<dimensions::DensityValue> &centralDensity() const noexcept {
return m_centralDensity;
}
[[nodiscard]] int radialSampleCount() const noexcept {
return m_radialSampleCount;
}
private:
std::optional<dimensions::DensityValue> m_centralDensity;
int m_radialSampleCount;
};
[[nodiscard]] RadialProfile generateLaneEmdenProfile(
const eos::Polytrope &equationOfState,
dimensions::DensityValue centralDensity,
int radialSampleCount
);
template <model::StellarModelType Model>
requires std::remove_cvref_t<Model>::template
containsSpecification<eos::Polytrope> [[nodiscard]] RadialProfile generateRadialProfile(
const Model &stellarModel,
const LaneEmden &strategy
) {
std::optional<dimensions::DensityValue> centralDensity = strategy.centralDensity();
if (!centralDensity.has_value()) {
if constexpr (std::remove_cvref_t<Model>::template containsSpecification<models::FixedCentralDensity>) {
centralDensity = stellarModel.template specification<models::FixedCentralDensity>().targetDensity();
} else {
throw std::invalid_argument(
"Lane-Emden seed generation requires either FixedCentralDensity or an explicit seed-only central "
"density."
);
}
}
return generateLaneEmdenProfile(
stellarModel.template specification<eos::Polytrope>(), *centralDensity, strategy.radialSampleCount()
);
}
template <typename Strategy, typename Model>
concept RadialSeedStrategyFor = requires(const Model &stellarModel, const Strategy &strategy) {
{ generateRadialProfile(stellarModel, strategy) } -> std::same_as<RadialProfile>;
};
} // namespace mean_field::seed

View File

@@ -0,0 +1,136 @@
module;
#include <concepts>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:seed.stellar_equilibrium_projection;
export import :operators.stellar_equilibrium_problem;
export import :physics.gravity;
export import :seed.lane_emden;
export namespace mean_field::seed {
struct StellarEquilibriumProjectionOptions final {
physics::GravitySolveOptions gravity{};
double surfaceRadiusRelativeTolerance{5.0e-4};
};
template <equilibrium::StellarEquilibriumModel Model> struct ProjectedEquilibriumState final {
using ModelType = std::remove_cvref_t<Model>;
mfem::Vector values;
};
namespace detail {
struct ProjectedRadialFields final {
mfem::Vector density;
mfem::Vector gravityGradient;
mfem::Vector gravityPotential;
mfem::Vector specificEnthalpy;
double bernoulliConstant;
};
[[nodiscard]] ProjectedRadialFields projectRadialFields(
const equilibrium::StellarDiscretization &discretization,
const RadialProfile &profile,
dimensions::MassValue targetMass,
dimensions::PressureValue targetSurfacePressure,
const StellarEquilibriumProjectionOptions &options
);
inline void assignProjectedBlock(
mfem::Vector destination,
const mfem::Vector &source,
const char *name
) {
if (destination.Size() != source.Size()) {
throw std::invalid_argument(name);
}
destination = source;
}
} // namespace detail
template <equilibrium::StellarEquilibriumModel Model>
[[nodiscard]] ProjectedEquilibriumState<Model> projectRadialProfile(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const RadialProfile &profile,
const StellarEquilibriumProjectionOptions &options = {}
) {
const detail::ProjectedRadialFields fields = detail::projectRadialFields(
problem.GetDiscretization(), profile,
problem.GetStellarModel().template specification<models::FixedTotalMass>().targetMass(),
problem.GetStellarModel().template specification<surface::Isobaric>().targetPressure(), options
);
mfem::Vector values(problem.StateSize());
values = 0.0;
const auto stateView = problem.GetManifest().stateView(values);
detail::assignProjectedBlock(
stateView.block(utils::blocks::density_field.mass_term), fields.density,
"The projected density does not match the compiled equilibrium-state block."
);
stateView.block(utils::blocks::surface_deformation_field.parameters_term) = 0.0;
detail::assignProjectedBlock(
stateView.block(utils::blocks::gravity_field.gradient_term), fields.gravityGradient,
"The projected gravity gradient does not match the compiled equilibrium-state block."
);
detail::assignProjectedBlock(
stateView.block(utils::blocks::gravity_field.poisson_term), fields.gravityPotential,
"The projected gravity potential does not match the compiled equilibrium-state block."
);
detail::assignProjectedBlock(
stateView.block(utils::blocks::enthalpy_field.specific_term), fields.specificEnthalpy,
"The projected specific enthalpy does not match the compiled equilibrium-state block."
);
/*
* Projection of a continuous spherical profile onto a faceted
* reference mesh generally leaves a small trace error on the physical
* surface. The pressure condition replaces these carrier rows in the
* compiled equilibrium problem, so impose its required carrier value
* exactly after bulk projection instead of treating that geometric
* mismatch as part of the initial residual.
*/
mfem::Vector enthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term);
const dimensions::SpecificEnthalpyValue requiredSurfaceEnthalpy =
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
problem.GetStellarModel().template specification<eos::Polytrope>(),
problem.GetStellarModel().template specification<surface::Isobaric>().targetPressure()
);
for (const int surfaceRow : problem.GetPressureSurfaceRows().reduced_dofs()) {
enthalpy(surfaceRow) = requiredSurfaceEnthalpy.value();
}
mfem::Vector fixedMassCoordinate =
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
if (fixedMassCoordinate.Size() != 1) {
throw std::invalid_argument("FixedTotalMass must generate exactly one equilibrium-state coordinate.");
}
fixedMassCoordinate(0) = fields.bernoulliConstant;
if constexpr (std::remove_cvref_t<Model>::template containsSpecification<models::FixedCentralDensity>) {
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.0;
}
return {.values = std::move(values)};
}
template <
equilibrium::StellarEquilibriumModel Model,
typename Strategy>
requires RadialSeedStrategyFor<
Strategy,
typename equilibrium::StellarEquilibriumProblem<Model>::ModelType>
[[nodiscard]] ProjectedEquilibriumState<Model> makeProjectedEquilibriumState(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const Strategy &strategy,
const StellarEquilibriumProjectionOptions &options = {}
) {
return projectRadialProfile(problem, generateRadialProfile(problem.GetStellarModel(), strategy), options);
}
} // namespace mean_field::seed

View File

@@ -0,0 +1,259 @@
module;
#include <cstdint>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:solver.preconditioning_diagnostics;
export import :operators.root_manifest;
export namespace mean_field::solver {
struct OperatorApplicationStatistics final {
std::uint64_t applications{0};
double totalSeconds{0.0};
double maximumSeconds{0.0};
};
struct PreconditionerLifecycleStatistics final {
std::uint64_t setups{0};
std::uint64_t refreshes{0};
double setupSeconds{0.0};
double refreshSeconds{0.0};
};
/*
* A non-owning measurement wrapper. Statistics are local to an MPI rank;
* cross-rank wall-clock reductions are performed when a solve report is
* assembled. Krylov application is sequential, so counters intentionally
* do not impose atomic overhead.
*/
class InstrumentedOperator final : public mfem::Operator {
public:
explicit InstrumentedOperator(const mfem::Operator &operation);
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override;
void ResetStatistics() const noexcept;
[[nodiscard]] const OperatorApplicationStatistics &GetStatistics() const noexcept;
[[nodiscard]] const mfem::Operator &GetOperation() const noexcept;
private:
const mfem::Operator *m_operation;
mutable OperatorApplicationStatistics m_statistics;
};
class InstrumentedPreconditioner final : public mfem::Solver {
public:
explicit InstrumentedPreconditioner(mfem::Solver &preconditioner);
void SetOperator(const mfem::Operator &operation) override;
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override;
void ResetStatistics() const noexcept;
[[nodiscard]] const OperatorApplicationStatistics &GetStatistics() const noexcept;
[[nodiscard]] const PreconditionerLifecycleStatistics &GetLifecycleStatistics() const noexcept;
[[nodiscard]] const mfem::Solver &GetPreconditioner() const noexcept;
private:
mfem::Solver *m_preconditioner;
mutable OperatorApplicationStatistics m_statistics;
PreconditionerLifecycleStatistics m_lifecycleStatistics;
};
class IdentityPreconditioner final : public mfem::Solver {
public:
explicit IdentityPreconditioner(int size);
void SetOperator(const mfem::Operator &operation) override;
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override;
};
/*
* If the supplied solver applies M^{-1}, this operator represents the
* fixed right-preconditioned product J M^{-1}. It is deliberately
* independent of the Krylov implementation used in production.
*/
class FixedRightPreconditionedOperator final : public mfem::Operator {
public:
FixedRightPreconditionedOperator(
const mfem::Operator &jacobian,
const mfem::Solver &inversePreconditioner
);
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override;
[[nodiscard]] const mfem::Operator &GetJacobian() const noexcept;
[[nodiscard]] const mfem::Solver &GetInversePreconditioner() const noexcept;
private:
const mfem::Operator *m_jacobian;
const mfem::Solver *m_inversePreconditioner;
mutable mfem::Vector m_preconditionedDirection;
};
struct IterationResidualMeasurement final {
int iteration;
double reportedNorm;
bool final;
};
class ResidualHistoryMonitor final : public mfem::IterativeSolverMonitor {
public:
void Reset() override;
void MonitorResidual(
int iteration,
double norm,
const mfem::Vector &residual,
bool final
) override;
[[nodiscard]] const std::vector<IterationResidualMeasurement> &GetHistory() const noexcept;
private:
std::vector<IterationResidualMeasurement> m_history;
};
struct ResidualBlockMeasurement final {
std::string stableId;
int size{0};
double descriptorScale{1.0};
double rightHandSideNorm{0.0};
double trueResidualNorm{0.0};
double blockRelativeResidual{0.0};
double scaledRightHandSideNorm{0.0};
double scaledTrueResidualNorm{0.0};
double contributionToGlobalRelativeResidual{0.0};
double fractionOfGlobalSquaredResidualNorm{0.0};
};
struct DirectResidualMeasurement final {
double rightHandSideNorm{0.0};
double trueResidualNorm{0.0};
double relativeResidual{0.0};
std::vector<ResidualBlockMeasurement> blocks;
};
[[nodiscard]] DirectResidualMeasurement measureDirectResidual(
const mfem::Operator &jacobian,
const mfem::Vector &rightHandSide,
const mfem::Vector &solution,
std::span<const operators::RootBlockDescriptor> residualBlocks,
MPI_Comm communicator,
double denominatorFloor = 1.0e-300
);
struct LinearSolveMeasurement final {
bool solverConverged{false};
int outerIterations{0};
double solverReportedInitialNorm{0.0};
double solverReportedFinalNorm{0.0};
double solverReportedResidualReduction{0.0};
double trueResidualDigitsReducedPerJacobianApplication{0.0};
double solveSecondsMaximumRank{0.0};
OperatorApplicationStatistics jacobian;
OperatorApplicationStatistics inversePreconditioner;
PreconditionerLifecycleStatistics inversePreconditionerLifecycle;
DirectResidualMeasurement directResidual;
std::vector<IterationResidualMeasurement> reportedResidualHistory;
};
[[nodiscard]] LinearSolveMeasurement measureLinearSolve(
const mfem::IterativeSolver &iterativeSolver,
const mfem::Operator &jacobian,
const mfem::Vector &rightHandSide,
const mfem::Vector &solution,
std::span<const operators::RootBlockDescriptor> residualBlocks,
const OperatorApplicationStatistics &jacobianStatistics,
const OperatorApplicationStatistics &inversePreconditionerStatistics,
const PreconditionerLifecycleStatistics &inversePreconditionerLifecycle,
const ResidualHistoryMonitor &monitor,
double localSolveSeconds,
MPI_Comm communicator,
double denominatorFloor = 1.0e-300
);
struct ArnoldiOptions final {
int krylovDimension{40};
double breakdownRelativeTolerance{1.0e-13};
double ritzConvergenceRelativeTolerance{1.0e-8};
bool reorthogonalize{true};
};
struct RitzValueMeasurement final {
double realPart{0.0};
double imaginaryPart{0.0};
double magnitude{0.0};
double distanceFromOne{0.0};
double residualEstimate{0.0};
double relativeResidualEstimate{0.0};
bool converged{false};
};
enum class RitzValueOrdering { closest_to_zero, farthest_from_one, smallest_real_part, largest_magnitude };
struct ArnoldiSpectralMeasurement final {
int requestedDimension{0};
int achievedDimension{0};
bool invariantSubspaceFound{false};
std::uint64_t operatorApplications{0};
double operatorApplicationSecondsMaximumRank{0.0};
double operatorMaximumApplicationSecondsMaximumRank{0.0};
double measurementSecondsMaximumRank{0.0};
double nonApplicationSecondsMaximumRank{0.0};
int convergedRitzValueCount{0};
int negativeRealPartCount{0};
double projectedLargestSingularValue{0.0};
double projectedSmallestSingularValue{0.0};
double projectedConditionProxy{0.0};
double centroidRealPart{0.0};
double centroidImaginaryPart{0.0};
double rmsDistanceFromOne{0.0};
double rmsClusterRadius{0.0};
double minimumMagnitude{0.0};
double maximumMagnitude{0.0};
double minimumRealPart{0.0};
double maximumRealPart{0.0};
double maximumAbsoluteImaginaryPart{0.0};
double conjugatePairDefect{0.0};
double projectedDepartureFromNormality{0.0};
double projectedFieldOfValuesMinimumRealPart{0.0};
double projectedFieldOfValuesMaximumRealPart{0.0};
std::vector<RitzValueMeasurement> ritzValues;
};
[[nodiscard]] ArnoldiSpectralMeasurement measureArnoldiSpectrum(
const mfem::Operator &operation,
const mfem::Vector &initialDirection,
MPI_Comm communicator,
const ArnoldiOptions &options = {}
);
[[nodiscard]] std::vector<RitzValueMeasurement> selectRitzValues(
const ArnoldiSpectralMeasurement &measurement,
RitzValueOrdering ordering,
int count
);
} // namespace mean_field::solver

View File

@@ -15,7 +15,7 @@ export namespace mean_field::surface {
class CompiledPressureSurfaceConstraint final {
public:
using PhysicalCondition = ConstantPressureSurface;
using PhysicalQuantity = eos::quantity::Pressure;
using PhysicalQuantity = dimensions::quantity::Pressure;
using CarrierQuantity = typename Formulation::CarrierQuantity;
using CarrierField = typename Formulation::CarrierField;
using Relation = SelectedRelation;
@@ -32,7 +32,7 @@ export namespace mean_field::surface {
) {
}
[[nodiscard]] eos::PressureValue targetPressure() const noexcept {
[[nodiscard]] dimensions::PressureValue targetPressure() const noexcept {
return m_condition.targetPressure();
}

View File

@@ -7,7 +7,7 @@ module;
export module mean_field:surface.constant;
export import :eos.quantities;
export import :dimensions.quantities;
export namespace mean_field::surface {
struct PressureSurfaceDescriptor final {
@@ -22,8 +22,15 @@ export namespace mean_field::surface {
*/
class ConstantPressureSurface final {
public:
using PhysicalQuantity = eos::quantity::Pressure;
using TargetValue = eos::PressureValue;
struct Parameters final {
dimensions::PressureValue Psurf;
};
using PhysicalQuantity = dimensions::quantity::Pressure;
using TargetValue = dimensions::PressureValue;
explicit ConstantPressureSurface(const Parameters parameters) : ConstantPressureSurface(parameters.Psurf) {
}
explicit ConstantPressureSurface(const TargetValue targetPressure) : m_targetPressure(targetPressure) {
if (!std::isfinite(targetPressure.value())) {

View File

@@ -6,6 +6,8 @@ module;
#include <type_traits>
export module mean_field:utils.blocks;
export import :model.specifications;
export namespace mean_field::utils::blocks {
inline constexpr int dynamic_block_size = -1;
@@ -19,6 +21,18 @@ export namespace mean_field::utils::blocks {
static constexpr int static_block_size = dynamic_block_size;
};
template <typename GeneratedValue> struct generated_value_block final : value_block_base {
using GeneratedType = GeneratedValue;
static constexpr int static_block_size = static_cast<int>(GeneratedValue::scalarArity);
};
template <typename GeneratedResidual> struct generated_residual_block final : residual_block_base {
using GeneratedType = GeneratedResidual;
static constexpr int static_block_size = static_cast<int>(GeneratedResidual::scalarArity);
};
struct term { };
struct field { };
@@ -96,25 +110,44 @@ export namespace mean_field::utils::blocks {
static inline constexpr specific specific_term{};
};
struct barotropic_constant final : field {
struct mass_normalization final : term {
struct value final : value_block_base {
static constexpr int static_block_size = 1;
};
struct fixed_total_mass final : field {
using SpecificationType = models::FixedTotalMass;
using MultiplierType = models::MultiplierFor<SpecificationType>;
using ResidualType = models::ResidualFor<SpecificationType>;
struct residual final : residual_block_base {
static constexpr int static_block_size = 1;
};
struct mass_normalization final : term {
using value = generated_value_block<MultiplierType>;
using residual = generated_residual_block<ResidualType>;
};
static inline constexpr mass_normalization mass_normalization_term{};
};
struct fixed_central_density final : field {
using SpecificationType = models::FixedCentralDensity;
using BorderType = models::BorderFor<SpecificationType>;
using ResidualType = models::ResidualFor<SpecificationType>;
struct central_value final : term {
using value = generated_value_block<BorderType>;
using residual = generated_residual_block<ResidualType>;
};
static inline constexpr central_value central_value_term{};
};
// Compatibility name for the current barotropic formulation. The scalar
// is generated by FixedTotalMass; its realization in this formulation is
// the historical C coordinate.
using barotropic_constant = fixed_total_mass;
inline constexpr density density_field{};
inline constexpr displacement displacement_field{};
inline constexpr surface_deformation surface_deformation_field{};
inline constexpr gravity gravity_field{};
inline constexpr enthalpy enthalpy_field{};
inline constexpr fixed_total_mass fixed_total_mass_constraint{};
inline constexpr fixed_central_density fixed_central_density_phase{};
inline constexpr barotropic_constant barotropic_constant_field{};
template <typename... Types> struct type_list {
@@ -489,6 +522,61 @@ export namespace mean_field::utils::blocks {
density::mass::value,
surface_deformation::parameters::value>>;
// Bordered n=3 family closure. The original stellar coordinates remain a
// contiguous prefix and the phase border and row are appended last.
using central_density_bordered_stellar_equilibrium_form = block_form<
type_list<
density::mass::value,
surface_deformation::parameters::value,
gravity::gradient::value,
gravity::poisson::value,
enthalpy::specific::value,
barotropic_constant::mass_normalization::value,
fixed_central_density::central_value::value>,
type_list<
gravity::gradient::residual,
gravity::poisson::residual,
density::mass::residual,
surface_deformation::shape_equilibrium::residual,
enthalpy::specific::residual,
barotropic_constant::mass_normalization::residual,
fixed_central_density::central_value::residual>>;
using central_density_bordered_stellar_equilibrium_jacobian_form = type_list<
block_row<
gravity::gradient::residual,
gravity::gradient::value,
gravity::poisson::value,
surface_deformation::parameters::value>,
block_row<
gravity::poisson::residual,
gravity::gradient::value,
density::mass::value,
surface_deformation::parameters::value>,
block_row<
density::mass::residual,
density::mass::value,
enthalpy::specific::value,
surface_deformation::parameters::value>,
block_row<
surface_deformation::shape_equilibrium::residual,
density::mass::value,
surface_deformation::parameters::value,
gravity::gradient::value,
enthalpy::specific::value>,
block_row<
enthalpy::specific::residual,
enthalpy::specific::value,
gravity::poisson::value,
surface_deformation::parameters::value,
barotropic_constant::mass_normalization::value,
fixed_central_density::central_value::value>,
block_row<
barotropic_constant::mass_normalization::residual,
density::mass::value,
surface_deformation::parameters::value>,
block_row<fixed_central_density::central_value::residual, enthalpy::specific::value>>;
// Columns: [d, h]
// Rows: [R_d]
using pressure_force_form = block_form<
@@ -509,4 +597,8 @@ export namespace mean_field::utils::blocks {
static_assert(valid_jacobian_form<
surface_deformed_stellar_equilibrium_form,
surface_deformed_stellar_equilibrium_jacobian_form>);
static_assert(valid_jacobian_form<
central_density_bordered_stellar_equilibrium_form,
central_density_bordered_stellar_equilibrium_jacobian_form>);
} // namespace mean_field::utils::blocks