feat(libmeanfield): centrifugal + pressure

This commit is contained in:
2026-08-04 14:24:55 -04:00
parent 9bc4f2758a
commit dc912fd15e
115 changed files with 260058 additions and 163261 deletions

View File

@@ -1,21 +1,55 @@
module;
#include "mfem.hpp"
#include <source_location>
#include <array>
#include <cmath>
#include <format>
#include <source_location>
#include <string_view>
#include <unordered_map>
#include <format>
module mean_field;
import :mapping.coefficients;
import :analysis.integral;
namespace {
double centrifugal_potential(const mfem::Vector &phys_x, const double omega) {
double centrifugal_potential(
const mfem::Vector &phys_x,
const double omega
) {
const double s2 = std::pow(phys_x(0), 2) + std::pow(phys_x(1), 2);
return -0.5 * s2 * std::pow(omega, 2);
}
}
void grid_function_to_true_dofs(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::GridFunction &grid_function,
mfem::Vector &true_dofs
) {
MFEM_VERIFY(
grid_function.Size() == finite_element_space.GetVSize(),
"The grid function does not match the requested finite-element "
"space."
);
true_dofs.SetSize(finite_element_space.GetTrueVSize());
const mfem::Operator *restriction =
finite_element_space.GetRestrictionMatrix();
if (restriction != nullptr) {
restriction->Mult(grid_function, true_dofs);
} else {
MFEM_VERIFY(
grid_function.Size() == true_dofs.Size(),
"A finite-element space without a restriction operator must "
"have "
"matching local and true sizes."
);
true_dofs = grid_function;
}
}
} // namespace
namespace mean_field::physics {
GravitySolution grav_potential(
@@ -24,68 +58,106 @@ namespace mean_field::physics {
const mfem::GridFunction &rho,
const bool phi_warm
) {
MFEM_VERIFY(
f.densityFes != nullptr && rho.FESpace() == f.densityFes.get(),
"Gravity solve requires rho to use the registered density space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"Gravity solve requires the registered gravity-potential space."
);
mfem::Array<int> outer_bdr_marker(f.mesh->bdr_attributes.Max());
outer_bdr_marker = 0;
outer_bdr_marker = 0;
outer_bdr_marker[1] = 1;
mfem::ParLinearForm g_rhs(f.RT_fes.get());
mfem::ParLinearForm g_rhs(f.gravityFluxFes.get());
// ReSharper disable once CppTooWideScope
std::unique_ptr<mfem::Coefficient> boundary_potential_coeff;
if (!f.has_mapping()) { // We only need to explicitly add a boundary integrator if a mapping is not being used. In the case where the outer domain has been compactified the φ=0 boundary condition is the natural condition and MFEM automatically handles this
auto boundary_potential = [&f](const mfem::Vector& x_physical) {
if (!f.has_mapping()) { // We only need to explicitly add a boundary
// integrator if a mapping is not being used. In
// the case where the outer domain has been
// compactified the φ=0 boundary condition is
// the natural condition and MFEM automatically
// handles this
auto boundary_potential = [&f](const mfem::Vector &x_physical) {
return l2_multipole_potential(f, utils::MASS, x_physical);
};
boundary_potential_coeff = std::make_unique<mfem::FunctionCoefficient>(boundary_potential);
auto boundary_integrator = std::make_unique<mfem::VectorFEBoundaryFluxLFIntegrator>(*boundary_potential_coeff);
const mfem::FiniteElement& boundary_element = *f.RT_fes->GetTypicalTraceElement();
boundary_potential_coeff =
std::make_unique<mfem::FunctionCoefficient>(boundary_potential);
auto boundary_integrator =
std::make_unique<mfem::VectorFEBoundaryFluxLFIntegrator>(
*boundary_potential_coeff
);
const mfem::FiniteElement &boundary_element =
*f.gravityFluxFes->GetTypicalTraceElement();
f.quadrature_factory->configure_gravity_boundary(*boundary_integrator, quadrature::QuadratureRole::discretization, boundary_element, utils::DOMAINS::VACUUM, quadrature::MappingKind::none);
g_rhs.AddBoundaryIntegrator(boundary_integrator.release(), outer_bdr_marker);
f.quadratureFactory->configure_gravity_boundary(
*boundary_integrator,
quadrature::QuadratureRole::discretization, boundary_element,
utils::DOMAINS::VACUUM, quadrature::MappingKind::none
);
g_rhs.AddBoundaryIntegrator(
boundary_integrator.release(), outer_bdr_marker
);
}
g_rhs.Assemble();
mfem::GridFunctionCoefficient rho_coeff(&rho);
mfem::ConstantCoefficient G4pi(4.0 * M_PI * utils::G);
mfem::ProductCoefficient source_coeff(G4pi, rho_coeff);
mfem::ParLinearForm f_rhs(f.L2_fes.get());
mfem::ParLinearForm f_rhs(f.gravityPotentialFes.get());
std::unique_ptr<mfem::Coefficient> mapped_source_coeff;
mfem::Coefficient* active_source_coeff = &source_coeff;
quadrature::MappingKind source_mapping_kind = quadrature::MappingKind::none;
mfem::Coefficient *active_source_coeff = &source_coeff;
quadrature::MappingKind source_mapping_kind =
quadrature::MappingKind::none;
if (f.has_mapping()) {
mapped_source_coeff = std::make_unique<mapping::MappedScalarCoefficient>(*f.mapping, source_coeff);
mapped_source_coeff =
std::make_unique<mapping::MappedScalarCoefficient>(
*f.mapping, source_coeff
);
active_source_coeff = mapped_source_coeff.get();
source_mapping_kind = quadrature::MappingKind::general;
}
auto source_integrator = std::make_unique<mfem::DomainLFIntegrator>(*active_source_coeff);
const mfem::FiniteElement& source_test_element = *f.L2_fes->GetTypicalFE();
const mfem::ElementTransformation& source_transformation = *f.mesh->GetElementTransformation(0);
const int source_coefficient_order = f.L2_fes->GetMaxElementOrder();
auto source_integrator =
std::make_unique<mfem::DomainLFIntegrator>(*active_source_coeff);
const mfem::FiniteElement &source_test_element =
*f.gravityPotentialFes->GetTypicalFE();
const mfem::ElementTransformation &source_transformation =
*f.mesh->GetElementTransformation(0);
const int source_coefficient_order = f.densityFes->GetMaxElementOrder();
f.quadrature_factory->configure_gravity_source(*source_integrator, quadrature::QuadratureRole::discretization, source_test_element, source_transformation, source_coefficient_order, utils::DOMAINS::STELLAR, source_mapping_kind);
f_rhs.AddDomainIntegrator(source_integrator.release(), f.gravity_context.stellar_mask);
f.quadratureFactory->configure_gravity_source(
*source_integrator, quadrature::QuadratureRole::discretization,
source_test_element, source_transformation,
source_coefficient_order, utils::DOMAINS::STELLAR,
source_mapping_kind
);
f_rhs.AddDomainIntegrator(
source_integrator.release(), f.gravityContext.stellar_mask
);
f_rhs.Assemble();
mfem::BlockVector RHS(f.gravity_block_true_offsets);
mfem::BlockVector RHS(f.gravityBlockTrueOffsets);
RHS.GetBlock(0) = *g_rhs.ParallelAssemble();
RHS.GetBlock(1) = *f_rhs.ParallelAssemble();
mfem::BlockVector X(f.gravity_block_true_offsets);
mfem::BlockVector X(f.gravityBlockTrueOffsets);
X = 0.0;
f.gravity_context.minres->SetOperator(*f.gravity_context.block_A);
f.gravity_context.minres->Mult(RHS, X);
f.gravityContext.minres->SetOperator(*f.gravityContext.block_A);
f.gravityContext.minres->Mult(RHS, X);
GravitySolution solution(f);
solution.gradPhi.SetFromTrueDofs(X.GetBlock(0));
solution.phi.SetFromTrueDofs(X.GetBlock(1));
return solution;
}
mfem::GridFunction get_potential(
@@ -105,18 +177,21 @@ namespace mean_field::physics {
std::unique_ptr<mfem::Coefficient> centrifugal_coeff;
if (fem.has_mapping()) {
centrifugal_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, rot);
centrifugal_coeff = std::make_unique<
mapping::PhysicalPositionFunctionCoefficient>(
*fem.mapping, rot
);
} else {
centrifugal_coeff = std::make_unique<mfem::FunctionCoefficient>(rot);
centrifugal_coeff =
std::make_unique<mfem::FunctionCoefficient>(rot);
}
mfem::GridFunction centrifugal_gf(fem.H1_fes.get());
mfem::GridFunction centrifugal_gf(fem.gravityPotentialFes.get());
centrifugal_gf.ProjectCoefficient(*centrifugal_coeff);
phi.phi += centrifugal_gf;
}
return phi.phi;
}
mfem::DenseMatrix compute_quadrupole_moment_tensor(
@@ -128,11 +203,23 @@ namespace mean_field::physics {
mfem::DenseMatrix local_Q(dim, dim);
local_Q = 0.0;
for (int i = 0; i < fem.H1_fes->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3) continue;
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3)
continue;
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = *fem.int_rule;
mfem::ElementTransformation *trans =
fem.mesh->GetElementTransformation(i);
using DensityField = field::Field<field::Density>;
const quadrature::Query query =
DensityField::make_query<field::Density::Form::Quadrupole>(
quadrature::QuadratureRole::diagnostic, trans->OrderW(),
std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
fem.has_mapping() ? quadrature::MappingKind::general
: quadrature::MappingKind::none
);
const mfem::IntegrationRule &ir =
*fem.quadratureFactory->get(query, trans->GetGeometryType())
.integration_rule;
for (int j = 0; j < ir.GetNPoints(); ++j) {
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
@@ -164,7 +251,8 @@ namespace mean_field::physics {
for (int m = 0; m < dim; ++m) {
for (int n = 0; n < dim; ++n) {
const double delta = (m == n) ? 1.0 : 0.0;
const double contrib = 3.0 * x_prime(m) * x_prime(n) - delta * r_sq;
const double contrib =
3.0 * x_prime(m) * x_prime(n) - delta * r_sq;
local_Q(m, n) += rho_val * contrib * weight;
}
}
@@ -172,7 +260,10 @@ namespace mean_field::physics {
}
mfem::DenseMatrix global_Q(dim, dim);
MPI_Allreduce(local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
MPI_Allreduce(
local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE,
MPI_SUM, fem.mesh->GetComm()
);
return global_Q;
}
@@ -183,7 +274,8 @@ namespace mean_field::physics {
const mfem::Vector &phys_x
) {
const double r = phys_x.Norml2();
if (r < 1e-12) return 0.0;
if (r < 1e-12)
return 0.0;
const int dim = fem.mesh->Dimension();
@@ -197,7 +289,8 @@ namespace mean_field::physics {
}
}
const double l2_contrib = -(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor;
const double l2_contrib =
-(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor;
const double l0_contrib = -utils::G * total_mass / r;
@@ -211,87 +304,331 @@ namespace mean_field::physics {
// ==========================================
// 1. Partially Assemble the High-Order Mass Block
// ==========================================
f.gravity_context.m_form = std::make_unique<mfem::ParBilinearForm>(f.RT_fes.get());
f.gravity_context.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
f.gravityContext.m_form =
std::make_unique<mfem::ParBilinearForm>(f.gravityFluxFes.get());
f.gravityContext.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
std::unique_ptr<mfem::VectorFEMassIntegrator> hdiv_mass_integrator;
if (f.has_mapping()) {
f.gravity_context.mapped_hdiv_mass_coeff = std::make_unique<mapping::MappedHDivMassCoefficient>(*f.mapping, f.mesh->Dimension());
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*f.gravity_context.mapped_hdiv_mass_coeff);
f.gravityContext.mapped_hdiv_mass_coeff =
std::make_unique<mapping::MappedHDivMassCoefficient>(
*f.mapping, f.mesh->Dimension()
);
hdiv_mass_integrator =
std::make_unique<mfem::VectorFEMassIntegrator>(
*f.gravityContext.mapped_hdiv_mass_coeff
);
} else {
f.gravity_context.mapped_hdiv_mass_coeff.reset();
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>();
f.gravityContext.mapped_hdiv_mass_coeff.reset();
hdiv_mass_integrator =
std::make_unique<mfem::VectorFEMassIntegrator>();
}
const mfem::FiniteElement& hdiv_element = *f.RT_fes->GetTypicalFE();
const mfem::ElementTransformation& hdiv_transformation = *f.mesh->GetElementTransformation(0);
const quadrature::MappingKind mapping_kind = f.has_mapping() ? quadrature::MappingKind::general : quadrature::MappingKind::none;
const mfem::FiniteElement &hdiv_element =
*f.gravityFluxFes->GetTypicalFE();
const mfem::ElementTransformation &hdiv_transformation =
*f.mesh->GetElementTransformation(0);
const quadrature::MappingKind mapping_kind =
f.has_mapping() ? quadrature::MappingKind::general
: quadrature::MappingKind::none;
f.quadrature_factory->configure_gravity_hdiv_mass(*hdiv_mass_integrator, quadrature::QuadratureRole::discretization, hdiv_element, hdiv_transformation, utils::DOMAINS::ALL, mapping_kind);
f.gravity_context.m_form->AddDomainIntegrator(hdiv_mass_integrator.release());
f.gravity_context.m_form->Assemble();
f.quadratureFactory->configure_gravity_hdiv_mass(
*hdiv_mass_integrator, quadrature::QuadratureRole::discretization,
hdiv_element, hdiv_transformation, utils::DOMAINS::ALL, mapping_kind
);
f.gravityContext.m_form->AddDomainIntegrator(
hdiv_mass_integrator.release()
);
f.gravityContext.m_form->Assemble();
// ==========================================
// 2. Partially Assemble the High-Order Divergence Block
// ==========================================
f.gravity_context.b_form = std::make_unique<mfem::ParMixedBilinearForm>(f.RT_fes.get(), f.L2_fes.get());
f.gravity_context.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
f.gravityContext.b_form = std::make_unique<mfem::ParMixedBilinearForm>(
f.gravityFluxFes.get(), f.gravityPotentialFes.get()
);
f.gravityContext.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto divergence_discretization_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement& divergence_discretization_test_element = *f.L2_fes->GetTypicalFE();
auto divergence_discretization_integrator =
std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement &divergence_discretization_test_element =
*f.gravityPotentialFes->GetTypicalFE();
f.quadrature_factory->configure_gravity_divergence(*divergence_discretization_integrator, quadrature::QuadratureRole::discretization, hdiv_element, divergence_discretization_test_element, hdiv_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none);
f.gravity_context.b_form->AddDomainIntegrator(divergence_discretization_integrator.release());
f.gravity_context.b_form->Assemble();
f.quadratureFactory->configure_gravity_divergence(
*divergence_discretization_integrator,
quadrature::QuadratureRole::discretization, hdiv_element,
divergence_discretization_test_element, hdiv_transformation,
utils::DOMAINS::ALL, quadrature::MappingKind::none
);
f.gravityContext.b_form->AddDomainIntegrator(
divergence_discretization_integrator.release()
);
f.gravityContext.b_form->Assemble();
MFEM_VERIFY(
f.domainMapperStateless != nullptr,
"Gravity source partial assembly requires the stateless domain "
"mapper."
);
mfem::Vector displacement_true(f.displacementFes->GetTrueVSize());
displacement_true = 0.0;
const mfem::GridFunction *active_displacement =
f.mapping->GetDisplacement();
if (active_displacement != nullptr) {
grid_function_to_true_dofs(
*f.displacementFes, *active_displacement, displacement_true
);
}
auto source_form =
std::make_unique<operators::PreparedMappedGravitySourceOperator>(
f, *f.domainMapperStateless
);
source_form->Prepare(displacement_true);
f.gravityContext.source_form = std::move(source_form);
// ==========================================
// 3. Assemble Global Block Operator
// ==========================================
f.gravity_context.BT = std::make_unique<mfem::TransposeOperator>(f.gravity_context.b_form.get());
f.gravityContext.BT = std::make_unique<mfem::TransposeOperator>(
f.gravityContext.b_form.get()
);
f.gravity_context.block_A = std::make_unique<mfem::BlockOperator>(f.gravity_block_true_offsets);
f.gravity_context.block_A->SetBlock(0, 0, f.gravity_context.m_form.get());
f.gravity_context.block_A->SetBlock(0, 1, f.gravity_context.BT.get());
f.gravity_context.block_A->SetBlock(1, 0, f.gravity_context.b_form.get());
f.gravityContext.block_A =
std::make_unique<mfem::BlockOperator>(f.gravityBlockTrueOffsets);
f.gravityContext.block_A->SetBlock(0, 0, f.gravityContext.m_form.get());
f.gravityContext.block_A->SetBlock(0, 1, f.gravityContext.BT.get());
f.gravityContext.block_A->SetBlock(1, 0, f.gravityContext.b_form.get());
// ==========================================
// 4. Construct a mapped Schur preconditioner
// ==========================================
mfem::Vector mass_diagonal(f.RT_fes->GetTrueVSize());
f.gravity_context.m_form->AssembleDiagonal(mass_diagonal);
mfem::Vector mass_diagonal(f.gravityFluxFes->GetTrueVSize());
f.gravityContext.m_form->AssembleDiagonal(mass_diagonal);
mfem::Vector inverse_mass_diagonal(mass_diagonal);
for (int i = 0; i < inverse_mass_diagonal.Size(); ++i) {
MFEM_VERIFY(std::isfinite(inverse_mass_diagonal(i)) && inverse_mass_diagonal(i) > 0.0, "Mapped RT mass matrix has a non-positive or non-finite diagonal entry.");
MFEM_VERIFY(
std::isfinite(inverse_mass_diagonal(i)) &&
inverse_mass_diagonal(i) > 0.0,
"Mapped RT mass matrix has a non-positive or non-finite "
"diagonal "
"entry."
);
inverse_mass_diagonal(i) = 1.0 / inverse_mass_diagonal(i);
}
mfem::ParMixedBilinearForm b_preconditioner(f.RT_fes.get(), f.L2_fes.get());
auto divergence_preconditioner_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
mfem::ParMixedBilinearForm b_preconditioner(
f.gravityFluxFes.get(), f.gravityPotentialFes.get()
);
auto divergence_preconditioner_integrator =
std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement& divergence_trial_element = *f.RT_fes->GetTypicalFE();
const mfem::FiniteElement& divergence_test_element = *f.L2_fes->GetTypicalFE();
const mfem::ElementTransformation& divergence_transformation = *f.mesh->GetElementTransformation(0);
const mfem::FiniteElement &divergence_trial_element =
*f.gravityFluxFes->GetTypicalFE();
const mfem::FiniteElement &divergence_test_element =
*f.gravityPotentialFes->GetTypicalFE();
const mfem::ElementTransformation &divergence_transformation =
*f.mesh->GetElementTransformation(0);
f.quadrature_factory->configure_gravity_divergence(*divergence_preconditioner_integrator, quadrature::QuadratureRole::preconditioner, divergence_trial_element, divergence_test_element, divergence_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none);
b_preconditioner.AddDomainIntegrator(divergence_preconditioner_integrator.release());
f.quadratureFactory->configure_gravity_divergence(
*divergence_preconditioner_integrator,
quadrature::QuadratureRole::preconditioner,
divergence_trial_element, divergence_test_element,
divergence_transformation, utils::DOMAINS::ALL,
quadrature::MappingKind::none
);
b_preconditioner.AddDomainIntegrator(
divergence_preconditioner_integrator.release()
);
b_preconditioner.Assemble();
b_preconditioner.Finalize();
std::unique_ptr<mfem::HypreParMatrix> b_matrix(b_preconditioner.ParallelAssemble());
std::unique_ptr<mfem::HypreParMatrix> inverse_mass_b_transpose(b_matrix->Transpose());
std::unique_ptr<mfem::HypreParMatrix> b_matrix(
b_preconditioner.ParallelAssemble()
);
std::unique_ptr<mfem::HypreParMatrix> inverse_mass_b_transpose(
b_matrix->Transpose()
);
inverse_mass_b_transpose->ScaleRows(inverse_mass_diagonal);
f.gravity_context.Schur.reset(mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get()));
f.gravityContext.Schur.reset(
mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get())
);
// ==========================================
// 5. Wire Up the preconditioners
// ==========================================
f.gravity_context.prec_M = std::make_unique<mfem::OperatorJacobiSmoother>(mass_diagonal, empty_tdofs);
f.gravity_context.prec_Phi->SetOperator(*f.gravity_context.Schur);
f.gravity_context.block_prec->SetDiagonalBlock(0, f.gravity_context.prec_M.get());
f.gravity_context.block_prec->SetDiagonalBlock(1, f.gravity_context.prec_Phi.get());
f.gravityContext.prec_M =
std::make_unique<mfem::OperatorJacobiSmoother>(
mass_diagonal, empty_tdofs
);
f.gravityContext.prec_Phi->SetOperator(*f.gravityContext.Schur);
f.gravityContext.block_prec->SetDiagonalBlock(
0, f.gravityContext.prec_M.get()
);
f.gravityContext.block_prec->SetDiagonalBlock(
1, f.gravityContext.prec_Phi.get()
);
}
}
GravitySolution grav_potential_new(
fem::FEM &f,
const utils::Args &args,
const mfem::GridFunction &rho,
const mfem::GridFunction &displacement
) {
MFEM_VERIFY(
f.mesh != nullptr,
"Gravity initialization requires a parallel mesh."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"Gravity initialization requires the density finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"Gravity initialization requires the gravity-potential "
"finite-element "
"space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"Gravity initialization requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "Gravity initialization requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.domainMapperStateless != nullptr,
"Gravity initialization requires the stateless domain mapper."
);
MFEM_VERIFY(
f.gravityContext.b_form != nullptr,
"Gravity initialization requires the divergence operator."
);
MFEM_VERIFY(
f.gravityContext.BT != nullptr,
"Gravity initialization requires the transpose divergence operator."
);
MFEM_VERIFY(
f.gravityContext.block_prec != nullptr,
"Gravity initialization requires the gravity block preconditioner."
);
MFEM_VERIFY(
rho.FESpace() == f.densityFes.get(),
"Gravity initialization requires density to use the FEM density "
"space."
);
MFEM_VERIFY(
displacement.FESpace() == f.displacementFes.get(),
"Gravity initialization requires displacement to use the FEM "
"Vec_H1 "
"space."
);
using form = utils::blocks::gravity_field_form;
constexpr auto gravity_gradient_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.gradient_term
);
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
);
const std::array<int, form::value_block_count> value_sizes{
f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(),
f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize()
};
const std::array<int, form::residual_block_count> residual_sizes{
f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize()
};
const utils::blocks::form_layout<form> layout(
value_sizes, residual_sizes
);
mfem::Vector density_true;
mfem::Vector displacement_true;
grid_function_to_true_dofs(*f.densityFes, rho, density_true);
grid_function_to_true_dofs(
*f.displacementFes, displacement, displacement_true
);
operators::context::gravity_field::GravityFieldLinearizationContext
linearization_context(f, *f.domainMapperStateless);
operators::GravityFieldJacobianOperator gravity_jacobian(
f, *f.domainMapperStateless, linearization_context,
layout.value_offsets(), layout.residual_offsets()
);
operators::GravityFieldOperator gravity_operator(
f, *f.domainMapperStateless, linearization_context,
layout.value_offsets(), gravity_jacobian
);
operators::context::gravity_field::GravityFieldGeometryContext
reduced_geometry_context(f, *f.domainMapperStateless);
operators::ReducedGravityFieldOperator reduced_operator(
gravity_operator, reduced_geometry_context, displacement_true
);
mfem::Vector right_hand_side;
reduced_operator.BuildRightHandSide(density_true, right_hand_side);
MFEM_VERIFY(
right_hand_side.Size() == reduced_operator.Height(),
"The reduced gravity right-hand side has the wrong size."
);
mfem::BlockVector gravity_state(
reduced_operator.GetGravityTrueOffsets()
);
gravity_state = 0.0;
mfem::MINRESSolver minres(f.mesh->GetComm());
minres.SetOperator(reduced_operator);
minres.SetPreconditioner(*f.gravityContext.block_prec);
minres.SetRelTol(args.p.rtol);
minres.SetAbsTol(args.p.atol);
minres.SetMaxIter(args.p.max_iters);
minres.SetPrintLevel(1);
minres.Mult(right_hand_side, gravity_state);
MFEM_VERIFY(
minres.GetConverged(),
"The reduced gravity solve failed to converge."
);
GravitySolution solution(f);
solution.gradPhi.SetFromTrueDofs(
gravity_state.GetBlock(gravity_gradient_residual_block)
);
solution.phi.SetFromTrueDofs(
gravity_state.GetBlock(gravity_poisson_residual_block)
);
return solution;
}
} // namespace mean_field::physics