Add native spherical harmonic surface coordinates to coupled equilibrium
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <mpi.h>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <mpi.h>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -15,6 +15,8 @@ module;
|
||||
|
||||
export module mean_field:normalization.stellar_equilibrium;
|
||||
|
||||
import :deformation.surface_metric;
|
||||
|
||||
export import :normalization.operators;
|
||||
export import :operators.stellar_equilibrium_compiler;
|
||||
export import :operators.stellar_equilibrium_problem;
|
||||
@@ -23,15 +25,18 @@ export import :utils.domain;
|
||||
namespace mean_field::normalization::detail {
|
||||
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] inline bool CanAssembleExactPartialDiagonal(mfem::ParFiniteElementSpace &space, bool hdiv) {
|
||||
[[nodiscard]] inline bool CanAssembleExactPartialDiagonal(
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
bool hdiv
|
||||
) {
|
||||
// For hanging nodes, diag(P^T A P) contains cross terms. MFEM's PA
|
||||
// diagonal is approximate there, so retain assembled exact diagonals.
|
||||
int eligible = space.GetMesh()->Conforming() && !space.IsVariableOrder() && space.GetVDim() == 1;
|
||||
for (int e = 0; eligible && e < space.GetNE(); ++e) {
|
||||
const auto *fe = space.GetFE(e);
|
||||
eligible = fe->GetGeomType() == mfem::Geometry::CUBE &&
|
||||
(hdiv ? dynamic_cast<const mfem::RT_HexahedronElement *>(fe) != nullptr :
|
||||
dynamic_cast<const mfem::NodalTensorFiniteElement *>(fe) != nullptr);
|
||||
eligible = fe->GetGeomType() == mfem::Geometry::CUBE &&
|
||||
(hdiv ? dynamic_cast<const mfem::RT_HexahedronElement *>(fe) != nullptr
|
||||
: dynamic_cast<const mfem::NodalTensorFiniteElement *>(fe) != nullptr);
|
||||
}
|
||||
int allEligible = 0;
|
||||
if (MPI_Allreduce(&eligible, &allEligible, 1, MPI_INT, MPI_MIN, space.GetComm()) != MPI_SUCCESS)
|
||||
@@ -40,7 +45,9 @@ namespace mean_field::normalization::detail {
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleMassDiagonal(
|
||||
mfem::ParBilinearForm &mass, mfem::ParFiniteElementSpace &space, bool partial
|
||||
mfem::ParBilinearForm &mass,
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
bool partial
|
||||
) {
|
||||
if (partial) {
|
||||
mass.SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
@@ -52,7 +59,8 @@ namespace mean_field::normalization::detail {
|
||||
mass.Assemble();
|
||||
mass.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||
if (!matrix) throw std::runtime_error("Reference Riesz mass assembly failed.");
|
||||
if (!matrix)
|
||||
throw std::runtime_error("Reference Riesz mass assembly failed.");
|
||||
mfem::Vector diagonal;
|
||||
matrix->GetDiag(diagonal);
|
||||
return diagonal;
|
||||
@@ -61,7 +69,7 @@ namespace mean_field::normalization::detail {
|
||||
[[nodiscard]] inline mfem::Vector AssembleScalarMassDiagonal(
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
mfem::Array<int> *domainMarker = nullptr,
|
||||
bool reference = false
|
||||
bool reference = false
|
||||
) {
|
||||
mfem::ParBilinearForm mass(&space);
|
||||
if (domainMarker == nullptr) {
|
||||
@@ -72,20 +80,25 @@ namespace mean_field::normalization::detail {
|
||||
return AssembleMassDiagonal(mass, space, !reference && CanAssembleExactPartialDiagonal(space, false));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleHDivMassDiagonal(mfem::ParFiniteElementSpace &space, bool reference = false) {
|
||||
[[nodiscard]] inline mfem::Vector AssembleHDivMassDiagonal(
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
bool reference = false
|
||||
) {
|
||||
mfem::ParBilinearForm mass(&space);
|
||||
mass.AddDomainIntegrator(new mfem::VectorFEMassIntegrator());
|
||||
return AssembleMassDiagonal(mass, space, !reference && CanAssembleExactPartialDiagonal(space, true));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool CanAssembleDirectBoundaryDiagonal(
|
||||
mfem::ParFiniteElementSpace &space, int attribute
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
int attribute
|
||||
) {
|
||||
int eligible = !space.Nonconforming() && !space.IsVariableOrder() && space.GetVDim() == 1 &&
|
||||
dynamic_cast<const mfem::H1_FECollection *>(space.FEColl()) != nullptr;
|
||||
dynamic_cast<const mfem::H1_FECollection *>(space.FEColl()) != nullptr;
|
||||
mfem::Array<int> dofs;
|
||||
for (int b = 0; eligible && b < space.GetNBE(); ++b) {
|
||||
if (space.GetBdrAttribute(b) != attribute) continue;
|
||||
if (space.GetBdrAttribute(b) != attribute)
|
||||
continue;
|
||||
// A general DOF transformation would require transforming the
|
||||
// full element matrix before extracting its diagonal.
|
||||
eligible = space.GetBdrElementVDofs(b, dofs) == nullptr;
|
||||
@@ -97,7 +110,8 @@ namespace mean_field::normalization::detail {
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleDirectBoundaryDiagonal(
|
||||
mfem::ParFiniteElementSpace &space, int attribute
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
int attribute
|
||||
) {
|
||||
mfem::Vector localDiagonal(space.GetVSize());
|
||||
localDiagonal = 0.0;
|
||||
@@ -106,13 +120,14 @@ namespace mean_field::normalization::detail {
|
||||
// GetNBE includes tagged internal interfaces. MFEM's boundary PA face
|
||||
// restriction uses FaceType::Boundary and omits the star/vacuum interface.
|
||||
for (int b = 0; b < space.GetNBE(); ++b) {
|
||||
if (space.GetBdrAttribute(b) != attribute) continue;
|
||||
const auto &element = *space.GetBE(b);
|
||||
if (space.GetBdrAttribute(b) != attribute)
|
||||
continue;
|
||||
const auto &element = *space.GetBE(b);
|
||||
auto &transformation = *space.GetBdrElementTransformation(b);
|
||||
space.GetBdrElementVDofs(b, dofs);
|
||||
shape.SetSize(element.GetDof());
|
||||
elementDiagonal.SetSize(element.GetDof());
|
||||
elementDiagonal = 0.0;
|
||||
elementDiagonal = 0.0;
|
||||
// Identical rule and physical shapes to MassIntegrator's default
|
||||
// element assembly; only the diagonal of w * shape * shape^T is formed.
|
||||
const auto &rule = mfem::MassIntegrator::GetRule(element, element, transformation);
|
||||
@@ -183,14 +198,25 @@ export namespace mean_field::normalization {
|
||||
// full assembled true-DOF diagonal; production uses exact PA for supported
|
||||
// volume spaces and direct marked-boundary quadrature for supported surfaces.
|
||||
[[nodiscard]] inline mfem::Vector AssembleReferenceScalarMassDiagonal(
|
||||
mfem::ParFiniteElementSpace &space, mfem::Array<int> *domainMarker = nullptr, bool reference = false
|
||||
) { return detail::AssembleScalarMassDiagonal(space, domainMarker, reference); }
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
mfem::Array<int> *domainMarker = nullptr,
|
||||
bool reference = false
|
||||
) {
|
||||
return detail::AssembleScalarMassDiagonal(space, domainMarker, reference);
|
||||
}
|
||||
[[nodiscard]] inline mfem::Vector AssembleReferenceHDivMassDiagonal(
|
||||
mfem::ParFiniteElementSpace &space, bool reference = false
|
||||
) { return detail::AssembleHDivMassDiagonal(space, reference); }
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
bool reference = false
|
||||
) {
|
||||
return detail::AssembleHDivMassDiagonal(space, reference);
|
||||
}
|
||||
[[nodiscard]] inline mfem::Vector AssembleReferenceSurfaceMassDiagonal(
|
||||
const fem::FEM &finiteElements, const field::ScalarBoundaryDofMap &surfaceMap, bool reference = false
|
||||
) { return detail::AssembleSurfaceMassDiagonal(finiteElements, surfaceMap, reference); }
|
||||
const fem::FEM &finiteElements,
|
||||
const field::ScalarBoundaryDofMap &surfaceMap,
|
||||
bool reference = false
|
||||
) {
|
||||
return detail::AssembleSurfaceMassDiagonal(finiteElements, surfaceMap, reference);
|
||||
}
|
||||
|
||||
/*
|
||||
* Runtime preparation paired with the compile-time normalization plan.
|
||||
@@ -544,9 +570,20 @@ export namespace mean_field::normalization {
|
||||
detail::AssembleScalarMassDiagonal(*finiteElements.gravityPotentialFes),
|
||||
gravityContext.GetGravityPotentialMap(), "gravity-potential"
|
||||
);
|
||||
const field::ScalarBoundaryDofMap surfaceMap =
|
||||
field::make_stellar_surface_scalar_dof_map<detail::DomainSchema>(*finiteElements.surfaceDeformationFes);
|
||||
const mfem::Vector surfaceDiagonal = detail::AssembleSurfaceMassDiagonal(finiteElements, surfaceMap);
|
||||
const mfem::Vector surfaceDiagonal = [&]() {
|
||||
const auto &domain = physical.GetDomainDeformation();
|
||||
if (domain.hasNodalRadialParameters()) {
|
||||
const field::ScalarBoundaryDofMap surfaceMap =
|
||||
field::make_stellar_surface_scalar_dof_map<detail::DomainSchema>(
|
||||
*finiteElements.surfaceDeformationFes
|
||||
);
|
||||
return detail::AssembleSurfaceMassDiagonal(finiteElements, surfaceMap);
|
||||
}
|
||||
// Modal coefficients are not surface nodal values. Pulling back
|
||||
// the reference trace metric gives one primal Gram entry per
|
||||
// actual parameter; the builder supplies its dual residual scale.
|
||||
return deformation::assembleSurfaceParameterMassDiagonal(finiteElements, domain);
|
||||
}();
|
||||
if (surfaceDiagonal.Size() != physical.GetDomainDeformation().parameterCount()) {
|
||||
throw std::logic_error("The reference surface Gram diagonal does not match the root surface block.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user