perf(jacobian-action): major updates to jacobian action application by removing redudant quadrature work. ~5x increase in speed
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
248
libmeanfield/impl/seed/lane_emden.cpp
Normal file
248
libmeanfield/impl/seed/lane_emden.cpp
Normal 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
|
||||
209
libmeanfield/impl/seed/stellar_equilibrium_projection.cpp
Normal file
209
libmeanfield/impl/seed/stellar_equilibrium_projection.cpp
Normal 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
|
||||
638
libmeanfield/impl/solver/preconditioning_diagnostics.cpp
Normal file
638
libmeanfield/impl/solver/preconditioning_diagnostics.cpp
Normal 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
|
||||
Reference in New Issue
Block a user