Add native spherical harmonic surface coordinates to coupled equilibrium
This commit is contained in:
@@ -172,6 +172,51 @@ export namespace mean_field::deformation {
|
||||
return m_surface.parameterCount();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasNodalRadialParameters() const noexcept {
|
||||
if constexpr (requires { PreparedSurface::hasNodalRadialParameters; }) {
|
||||
return PreparedSurface::hasNodalRadialParameters;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool supportsSurfaceParameterMetric() const noexcept {
|
||||
if constexpr (requires { PreparedSurface::hasCanonicalNodalVectorTrace; }) {
|
||||
return PreparedSurface::hasCanonicalNodalVectorTrace;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reference surface metrics need the prescription tangent before any
|
||||
// physical state has been prepared. Exposing that tangent avoids
|
||||
// making normalization depend on the arbitrary interior mesh lift.
|
||||
void applySurfaceJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &surfaceDirection
|
||||
) const {
|
||||
requireCurrentDiscretization();
|
||||
requireParameterSize(parameters);
|
||||
requireParameterSize(parameterDirection);
|
||||
if (surfaceDirection.Size() != surfaceDisplacementSize()) {
|
||||
throw std::invalid_argument("The surface tangent output has an incompatible trace size.");
|
||||
}
|
||||
m_surface.applyJacobian(parameters, parameterDirection, surfaceDirection);
|
||||
}
|
||||
|
||||
void applySurfaceJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &surfaceDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const {
|
||||
requireCurrentDiscretization();
|
||||
requireParameterSize(parameters);
|
||||
requireParameterSize(parameterDual);
|
||||
if (surfaceDual.Size() != surfaceDisplacementSize()) {
|
||||
throw std::invalid_argument("The surface transpose input has an incompatible trace size.");
|
||||
}
|
||||
m_surface.applyJacobianTranspose(parameters, surfaceDual, parameterDual);
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return m_surface.surfaceDisplacementSize();
|
||||
}
|
||||
@@ -580,6 +625,30 @@ export namespace mean_field::deformation {
|
||||
return m_implementation->parameterCount();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasNodalRadialParameters() const noexcept {
|
||||
return m_implementation->hasNodalRadialParameters();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool supportsSurfaceParameterMetric() const noexcept {
|
||||
return m_implementation->supportsSurfaceParameterMetric();
|
||||
}
|
||||
|
||||
void applySurfaceJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &surfaceDirection
|
||||
) const {
|
||||
m_implementation->applySurfaceJacobian(parameters, parameterDirection, surfaceDirection);
|
||||
}
|
||||
|
||||
void applySurfaceJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &surfaceDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const {
|
||||
m_implementation->applySurfaceJacobianTranspose(parameters, surfaceDual, parameterDual);
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return m_implementation->surfaceDisplacementSize();
|
||||
}
|
||||
@@ -654,10 +723,22 @@ export namespace mean_field::deformation {
|
||||
private:
|
||||
class Interface {
|
||||
public:
|
||||
virtual ~Interface() = default;
|
||||
virtual ~Interface() = default;
|
||||
|
||||
[[nodiscard]] virtual DomainDeformationDescriptor descriptor() const noexcept = 0;
|
||||
[[nodiscard]] virtual int parameterCount() const noexcept = 0;
|
||||
[[nodiscard]] virtual DomainDeformationDescriptor descriptor() const noexcept = 0;
|
||||
[[nodiscard]] virtual int parameterCount() const noexcept = 0;
|
||||
[[nodiscard]] virtual bool hasNodalRadialParameters() const noexcept = 0;
|
||||
[[nodiscard]] virtual bool supportsSurfaceParameterMetric() const noexcept = 0;
|
||||
virtual void applySurfaceJacobian(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const = 0;
|
||||
virtual void applySurfaceJacobianTranspose(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const = 0;
|
||||
[[nodiscard]] virtual int surfaceDisplacementSize() const noexcept = 0;
|
||||
[[nodiscard]] virtual int volumeDisplacementSize() const noexcept = 0;
|
||||
[[nodiscard]] virtual bool matchesCurrentDiscretization() const noexcept = 0;
|
||||
@@ -705,6 +786,46 @@ export namespace mean_field::deformation {
|
||||
[[nodiscard]] int parameterCount() const noexcept override {
|
||||
return m_preparedDeformation.parameterCount();
|
||||
}
|
||||
[[nodiscard]] bool hasNodalRadialParameters() const noexcept override {
|
||||
if constexpr (requires { m_preparedDeformation.hasNodalRadialParameters(); }) {
|
||||
return m_preparedDeformation.hasNodalRadialParameters();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool supportsSurfaceParameterMetric() const noexcept override {
|
||||
if constexpr (requires(const PreparedDeformation &prepared, const mfem::Vector &x, mfem::Vector &y) {
|
||||
{ prepared.supportsSurfaceParameterMetric() } noexcept -> std::same_as<bool>;
|
||||
{ prepared.applySurfaceJacobian(x, x, y) } -> std::same_as<void>;
|
||||
{ prepared.applySurfaceJacobianTranspose(x, x, y) } -> std::same_as<void>;
|
||||
}) {
|
||||
return m_preparedDeformation.supportsSurfaceParameterMetric();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void applySurfaceJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &trace
|
||||
) const override {
|
||||
if constexpr (requires { m_preparedDeformation.applySurfaceJacobian(parameters, direction, trace); }) {
|
||||
m_preparedDeformation.applySurfaceJacobian(parameters, direction, trace);
|
||||
} else {
|
||||
throw std::logic_error("This domain deformation does not expose its surface tangent.");
|
||||
}
|
||||
}
|
||||
void applySurfaceJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &trace,
|
||||
mfem::Vector &dual
|
||||
) const override {
|
||||
if constexpr (requires {
|
||||
m_preparedDeformation.applySurfaceJacobianTranspose(parameters, trace, dual);
|
||||
}) {
|
||||
m_preparedDeformation.applySurfaceJacobianTranspose(parameters, trace, dual);
|
||||
} else {
|
||||
throw std::logic_error("This domain deformation does not expose its surface transpose.");
|
||||
}
|
||||
}
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept override {
|
||||
return m_preparedDeformation.surfaceDisplacementSize();
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ export namespace mean_field::deformation {
|
||||
|
||||
class PreparedNodalRadialSurface final {
|
||||
public:
|
||||
static constexpr bool hasNodalRadialParameters = true;
|
||||
// Trace entries follow dimension * canonical boundary-node index + component.
|
||||
static constexpr bool hasCanonicalNodalVectorTrace = true;
|
||||
[[nodiscard]] SurfaceDeformationDescriptor descriptor() const noexcept;
|
||||
|
||||
[[nodiscard]] int parameterCount() const noexcept;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
module;
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include <mfem/linalg/densemat.hpp>
|
||||
|
||||
export module mean_field:deformation.spherical_harmonic_surface;
|
||||
|
||||
export import :deformation.nodal_radial_surface;
|
||||
|
||||
export namespace mean_field::deformation {
|
||||
struct SphericalHarmonicSurfaceOptions final {
|
||||
int maximumDegree{4};
|
||||
// A cutoff retains both real sine/cosine partners. Zero restricts the
|
||||
// shape to axisymmetry; an omitted cutoff retains every order.
|
||||
std::optional<int> maximumAzimuthalOrder{};
|
||||
|
||||
void validate() const;
|
||||
};
|
||||
|
||||
struct SphericalHarmonicMode final {
|
||||
int degree;
|
||||
int order;
|
||||
|
||||
constexpr bool operator==(const SphericalHarmonicMode &) const = default;
|
||||
};
|
||||
|
||||
class PreparedSphericalHarmonicSurface;
|
||||
|
||||
class SphericalHarmonicSurface final {
|
||||
public:
|
||||
using PreparedType = PreparedSphericalHarmonicSurface;
|
||||
|
||||
explicit SphericalHarmonicSurface(SphericalHarmonicSurfaceOptions options = {});
|
||||
explicit SphericalHarmonicSurface(
|
||||
mfem::Vector referenceCenter,
|
||||
SphericalHarmonicSurfaceOptions options = {}
|
||||
);
|
||||
|
||||
[[nodiscard]] const mfem::Vector &referenceCenter() const noexcept;
|
||||
[[nodiscard]] const SphericalHarmonicSurfaceOptions &options() const noexcept;
|
||||
[[nodiscard]] int modeCount() const noexcept;
|
||||
[[nodiscard]] SurfaceDeformationDescriptor descriptor() const noexcept;
|
||||
void validate() const;
|
||||
|
||||
private:
|
||||
mfem::Vector m_referenceCenter;
|
||||
SphericalHarmonicSurfaceOptions m_options;
|
||||
};
|
||||
|
||||
// Coefficients have units of radial length. The fixed reference functions
|
||||
// satisfy integral(Y_lm^2 dOmega)=1, with Condon--Shortley phase, negative
|
||||
// orders denoting sine and positive orders cosine. FE interpolation of
|
||||
// their nodal samples need not retain continuous spherical orthogonality.
|
||||
class PreparedSphericalHarmonicSurface final {
|
||||
public:
|
||||
static constexpr bool hasNodalRadialParameters = false;
|
||||
// Trace entries follow dimension * canonical boundary-node index + component.
|
||||
static constexpr bool hasCanonicalNodalVectorTrace = true;
|
||||
|
||||
[[nodiscard]] SurfaceDeformationDescriptor descriptor() const noexcept;
|
||||
[[nodiscard]] int parameterCount() const noexcept;
|
||||
[[nodiscard]] long long globalParameterCount() const noexcept;
|
||||
[[nodiscard]] long long globalParameterOffset() const noexcept;
|
||||
[[nodiscard]] int spatialDimension() const noexcept;
|
||||
[[nodiscard]] int surfaceNodeCount() const noexcept;
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept;
|
||||
[[nodiscard]] long long globalSurfaceDisplacementSize() const noexcept;
|
||||
[[nodiscard]] long long globalSurfaceDisplacementOffset() const noexcept;
|
||||
[[nodiscard]] int surfaceDisplacementDof(
|
||||
int surfaceNode,
|
||||
int component
|
||||
) const;
|
||||
[[nodiscard]] double radialDirection(
|
||||
int surfaceNode,
|
||||
int component
|
||||
) const;
|
||||
[[nodiscard]] double referenceRadius(int surfaceNode) const;
|
||||
[[nodiscard]] const mfem::Vector &referenceCenter() const noexcept;
|
||||
[[nodiscard]] const field::ScalarBoundaryDofMap &surfaceDofMap() const noexcept;
|
||||
[[nodiscard]] std::span<const SphericalHarmonicMode> modes() const noexcept;
|
||||
[[nodiscard]] const mfem::DenseMatrix &nodalBasis() const noexcept;
|
||||
|
||||
void buildSurfaceDisplacement(
|
||||
const mfem::Vector ¶meters,
|
||||
mfem::Vector &surfaceDisplacement
|
||||
) const;
|
||||
void applyJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &surfaceDisplacementDirection
|
||||
) const;
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const;
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDualAction
|
||||
) const;
|
||||
|
||||
private:
|
||||
friend PreparedSphericalHarmonicSurface compileSurfaceDeformationPrescription(
|
||||
const SphericalHarmonicSurface &,
|
||||
const SurfaceDeformationCompilationContext &
|
||||
);
|
||||
|
||||
PreparedSphericalHarmonicSurface(
|
||||
PreparedNodalRadialSurface radialSurface,
|
||||
std::vector<SphericalHarmonicMode> modes,
|
||||
mfem::DenseMatrix nodalBasis
|
||||
);
|
||||
void requireParameters(const mfem::Vector ¶meters) const;
|
||||
void requireSurfaceSize(const mfem::Vector &surfaceDisplacement) const;
|
||||
void applyEmbedding(
|
||||
const mfem::Vector ¶meters,
|
||||
mfem::Vector &surfaceDisplacement
|
||||
) const;
|
||||
|
||||
PreparedNodalRadialSurface m_radialSurface;
|
||||
std::vector<SphericalHarmonicMode> m_modes;
|
||||
mfem::DenseMatrix m_nodalBasis;
|
||||
};
|
||||
|
||||
[[nodiscard]] PreparedSphericalHarmonicSurface compileSurfaceDeformationPrescription(
|
||||
const SphericalHarmonicSurface &prescription,
|
||||
const SurfaceDeformationCompilationContext &context
|
||||
);
|
||||
|
||||
static_assert(SurfaceDeformationPrescription<SphericalHarmonicSurface>);
|
||||
static_assert(PreparedSurfaceDeformationPrescription<PreparedSphericalHarmonicSurface>);
|
||||
static_assert(SurfaceDeformationCompilable<
|
||||
SphericalHarmonicSurface,
|
||||
SurfaceDeformationCompilationContext>);
|
||||
} // namespace mean_field::deformation
|
||||
18
libmeanfield/interface/deformation/surface_metric.cppm
Normal file
18
libmeanfield/interface/deformation/surface_metric.cppm
Normal file
@@ -0,0 +1,18 @@
|
||||
module;
|
||||
|
||||
#include <mfem/linalg/vector.hpp>
|
||||
|
||||
export module mean_field:deformation.surface_metric;
|
||||
|
||||
export import :deformation.domain_deformation;
|
||||
export import :fem;
|
||||
|
||||
export namespace mean_field::deformation {
|
||||
// A reference parameter metric depends on the surface tangent, not the
|
||||
// interior mesh lift. Assembly is out of line to keep MFEM quadrature
|
||||
// implementation headers out of every normalization/preconditioner user.
|
||||
[[nodiscard]] mfem::Vector assembleSurfaceParameterMassDiagonal(
|
||||
const fem::FEM &finiteElements,
|
||||
const PreparedDomainDeformationRuntime &deformation
|
||||
);
|
||||
} // namespace mean_field::deformation
|
||||
@@ -6,10 +6,12 @@ module;
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem/linalg/vector.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
export module mean_field:equilibrium.stellar_discretization;
|
||||
|
||||
export import :deformation.domain_deformation;
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :normalization.physical_riesz;
|
||||
@@ -26,14 +28,40 @@ export namespace mean_field::equilibrium {
|
||||
* references retained by prepared operators. The mapper is part of that
|
||||
* owned FEM and therefore has the same lifetime.
|
||||
*/
|
||||
template <normalization::NormalizationPrescription Normalization> class StellarDiscretizationFor final {
|
||||
template <
|
||||
normalization::NormalizationPrescription Normalization,
|
||||
deformation::SurfaceDeformationPrescription SurfaceDeformation = deformation::NodalRadialSurface,
|
||||
deformation::InteriorDeformationExtension InteriorDeformation = deformation::PowerLawRadialInteriorExtension,
|
||||
deformation::VacuumDeformationExtension VacuumDeformation = deformation::FixedInfinityRadialVacuumExtension>
|
||||
requires deformation::SurfaceDeformationCompilable<
|
||||
SurfaceDeformation,
|
||||
deformation::SurfaceDeformationCompilationContext> &&
|
||||
deformation::InteriorDeformationExtensionCompilable<
|
||||
InteriorDeformation,
|
||||
deformation::RadialDeformationExtensionCompilationContext> &&
|
||||
deformation::VacuumDeformationExtensionCompilable<
|
||||
VacuumDeformation,
|
||||
deformation::RadialDeformationExtensionCompilationContext>
|
||||
class StellarDiscretizationFor final {
|
||||
public:
|
||||
using NormalizationPrescriptionType = std::remove_cvref_t<Normalization>;
|
||||
using NormalizationPrescriptionType = std::remove_cvref_t<Normalization>;
|
||||
using SurfaceDeformationPrescriptionType = SurfaceDeformation;
|
||||
using InteriorDeformationExtensionType = InteriorDeformation;
|
||||
using VacuumDeformationExtensionType = VacuumDeformation;
|
||||
|
||||
explicit StellarDiscretizationFor(fem::FEM &&finiteElementModel)
|
||||
requires std::same_as<
|
||||
NormalizationPrescriptionType,
|
||||
normalization::Unnormalized>
|
||||
NormalizationPrescriptionType,
|
||||
normalization::Unnormalized> &&
|
||||
std::same_as<
|
||||
SurfaceDeformation,
|
||||
deformation::NodalRadialSurface> &&
|
||||
std::same_as<
|
||||
InteriorDeformation,
|
||||
deformation::PowerLawRadialInteriorExtension> &&
|
||||
std::same_as<
|
||||
VacuumDeformation,
|
||||
deformation::FixedInfinityRadialVacuumExtension>
|
||||
: StellarDiscretizationFor(
|
||||
std::move(finiteElementModel),
|
||||
normalization::Unnormalized{}
|
||||
@@ -49,9 +77,55 @@ export namespace mean_field::equilibrium {
|
||||
StellarDiscretizationFor(
|
||||
fem::FEM &&finiteElementModel,
|
||||
NormalizationPrescriptionType normalizationPrescription
|
||||
)
|
||||
requires std::same_as<
|
||||
SurfaceDeformation,
|
||||
deformation::NodalRadialSurface> &&
|
||||
std::same_as<
|
||||
InteriorDeformation,
|
||||
deformation::PowerLawRadialInteriorExtension> &&
|
||||
std::same_as<
|
||||
VacuumDeformation,
|
||||
deformation::FixedInfinityRadialVacuumExtension>
|
||||
: StellarDiscretizationFor(
|
||||
std::move(finiteElementModel),
|
||||
std::move(normalizationPrescription),
|
||||
MakeDefaultSurface(finiteElementModel),
|
||||
InteriorDeformation{},
|
||||
VacuumDeformation{}
|
||||
) {
|
||||
}
|
||||
|
||||
StellarDiscretizationFor(
|
||||
fem::FEM &&finiteElementModel,
|
||||
NormalizationPrescriptionType normalizationPrescription,
|
||||
SurfaceDeformation surfaceDeformation
|
||||
)
|
||||
requires std::default_initializable<InteriorDeformation> && std::default_initializable<VacuumDeformation>
|
||||
: StellarDiscretizationFor(
|
||||
std::move(finiteElementModel),
|
||||
std::move(normalizationPrescription),
|
||||
std::move(surfaceDeformation),
|
||||
InteriorDeformation{},
|
||||
VacuumDeformation{}
|
||||
) {
|
||||
}
|
||||
|
||||
StellarDiscretizationFor(
|
||||
fem::FEM &&finiteElementModel,
|
||||
NormalizationPrescriptionType normalizationPrescription,
|
||||
SurfaceDeformation surfaceDeformation,
|
||||
InteriorDeformation interiorDeformation,
|
||||
VacuumDeformation vacuumDeformation
|
||||
)
|
||||
: m_finiteElementModel(TakeOwnership(std::move(finiteElementModel))),
|
||||
m_normalizationPrescription(std::move(normalizationPrescription)) {
|
||||
m_normalizationPrescription(std::move(normalizationPrescription)),
|
||||
m_surfaceDeformation(std::move(surfaceDeformation)),
|
||||
m_interiorDeformation(std::move(interiorDeformation)),
|
||||
m_vacuumDeformation(std::move(vacuumDeformation)) {
|
||||
m_surfaceDeformation.validate();
|
||||
m_interiorDeformation.validate();
|
||||
m_vacuumDeformation.validate();
|
||||
}
|
||||
|
||||
StellarDiscretizationFor(
|
||||
@@ -90,6 +164,21 @@ export namespace mean_field::equilibrium {
|
||||
|
||||
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const && = delete;
|
||||
|
||||
[[nodiscard]] const SurfaceDeformation &surfaceDeformationPrescription() const & noexcept {
|
||||
return m_surfaceDeformation;
|
||||
}
|
||||
[[nodiscard]] const SurfaceDeformation &surfaceDeformationPrescription() const && = delete;
|
||||
|
||||
[[nodiscard]] const InteriorDeformation &interiorDeformationExtension() const & noexcept {
|
||||
return m_interiorDeformation;
|
||||
}
|
||||
[[nodiscard]] const InteriorDeformation &interiorDeformationExtension() const && = delete;
|
||||
|
||||
[[nodiscard]] const VacuumDeformation &vacuumDeformationExtension() const & noexcept {
|
||||
return m_vacuumDeformation;
|
||||
}
|
||||
[[nodiscard]] const VacuumDeformation &vacuumDeformationExtension() const && = delete;
|
||||
|
||||
[[nodiscard]] bool isCurrent() const noexcept {
|
||||
return m_finiteElementModel != nullptr && m_finiteElementModel->okay();
|
||||
}
|
||||
@@ -108,6 +197,15 @@ export namespace mean_field::equilibrium {
|
||||
return *m_finiteElementModel;
|
||||
}
|
||||
|
||||
[[nodiscard]] static deformation::NodalRadialSurface MakeDefaultSurface(const fem::FEM &finiteElementModel) {
|
||||
if (!finiteElementModel.okay()) {
|
||||
throw std::invalid_argument("A default surface requires a complete finite-element model.");
|
||||
}
|
||||
mfem::Vector center(finiteElementModel.mesh->SpaceDimension());
|
||||
center = 0.0;
|
||||
return deformation::NodalRadialSurface{std::move(center)};
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<fem::FEM> TakeOwnership(fem::FEM &&finiteElementModel) {
|
||||
if (!finiteElementModel.okay()) {
|
||||
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
|
||||
@@ -117,6 +215,9 @@ export namespace mean_field::equilibrium {
|
||||
|
||||
std::unique_ptr<fem::FEM> m_finiteElementModel;
|
||||
NormalizationPrescriptionType m_normalizationPrescription;
|
||||
SurfaceDeformation m_surfaceDeformation;
|
||||
InteriorDeformation m_interiorDeformation;
|
||||
VacuumDeformation m_vacuumDeformation;
|
||||
};
|
||||
|
||||
template <normalization::NormalizationPrescription Normalization>
|
||||
@@ -125,12 +226,43 @@ export namespace mean_field::equilibrium {
|
||||
Normalization
|
||||
) -> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
|
||||
|
||||
template <
|
||||
typename Normalization,
|
||||
typename Surface>
|
||||
StellarDiscretizationFor(
|
||||
fem::FEM &&,
|
||||
Normalization,
|
||||
Surface
|
||||
)
|
||||
-> StellarDiscretizationFor<
|
||||
std::remove_cvref_t<Normalization>,
|
||||
std::remove_cvref_t<Surface>>;
|
||||
|
||||
template <
|
||||
typename Normalization,
|
||||
typename Surface,
|
||||
typename Interior,
|
||||
typename Vacuum>
|
||||
StellarDiscretizationFor(
|
||||
fem::FEM &&,
|
||||
Normalization,
|
||||
Surface,
|
||||
Interior,
|
||||
Vacuum
|
||||
)
|
||||
-> StellarDiscretizationFor<
|
||||
std::remove_cvref_t<Normalization>,
|
||||
std::remove_cvref_t<Surface>,
|
||||
std::remove_cvref_t<Interior>,
|
||||
std::remove_cvref_t<Vacuum>>;
|
||||
|
||||
using StellarDiscretization = StellarDiscretizationFor<normalization::Unnormalized>;
|
||||
|
||||
template <typename Candidate> struct IsStellarDiscretization : std::false_type { };
|
||||
|
||||
template <normalization::NormalizationPrescription Normalization>
|
||||
struct IsStellarDiscretization<StellarDiscretizationFor<Normalization>> : std::true_type { };
|
||||
template <typename Normalization, typename Surface, typename Interior, typename Vacuum>
|
||||
struct IsStellarDiscretization<StellarDiscretizationFor<Normalization, Surface, Interior, Vacuum>>
|
||||
: std::true_type { };
|
||||
|
||||
template <typename Candidate>
|
||||
concept StellarDiscretizationType = IsStellarDiscretization<std::remove_cvref_t<Candidate>>::value;
|
||||
@@ -145,6 +277,51 @@ export namespace mean_field::equilibrium {
|
||||
};
|
||||
}
|
||||
|
||||
// Geometry is a numerical prescription. Keeping it on the discretization
|
||||
// leaves the model's EOS, surface pressure and integral constraints intact.
|
||||
template <
|
||||
normalization::NormalizationPrescription Normalization,
|
||||
deformation::SurfaceDeformationPrescription Surface>
|
||||
requires deformation::SurfaceDeformationCompilable<
|
||||
Surface,
|
||||
deformation::SurfaceDeformationCompilationContext>
|
||||
[[nodiscard]] auto makeStellarDiscretization(
|
||||
fem::FEM &&finiteElementModel,
|
||||
Normalization normalizationPrescription,
|
||||
Surface surface
|
||||
) {
|
||||
return StellarDiscretizationFor<Normalization, Surface>{
|
||||
std::move(finiteElementModel), std::move(normalizationPrescription), std::move(surface)
|
||||
};
|
||||
}
|
||||
|
||||
template <
|
||||
normalization::NormalizationPrescription Normalization,
|
||||
deformation::SurfaceDeformationPrescription Surface,
|
||||
deformation::InteriorDeformationExtension Interior,
|
||||
deformation::VacuumDeformationExtension Vacuum>
|
||||
requires deformation::SurfaceDeformationCompilable<
|
||||
Surface,
|
||||
deformation::SurfaceDeformationCompilationContext> &&
|
||||
deformation::InteriorDeformationExtensionCompilable<
|
||||
Interior,
|
||||
deformation::RadialDeformationExtensionCompilationContext> &&
|
||||
deformation::VacuumDeformationExtensionCompilable<
|
||||
Vacuum,
|
||||
deformation::RadialDeformationExtensionCompilationContext>
|
||||
[[nodiscard]] auto makeStellarDiscretization(
|
||||
fem::FEM &&finiteElementModel,
|
||||
Normalization normalizationPrescription,
|
||||
Surface surface,
|
||||
Interior interior,
|
||||
Vacuum vacuum
|
||||
) {
|
||||
return StellarDiscretizationFor<Normalization, Surface, Interior, Vacuum>{
|
||||
std::move(finiteElementModel), std::move(normalizationPrescription), std::move(surface),
|
||||
std::move(interior), std::move(vacuum)
|
||||
};
|
||||
}
|
||||
|
||||
template <normalization::NormalizationPrescription Normalization>
|
||||
StellarDiscretizationFor<std::remove_cvref_t<Normalization>> makeStellarDiscretization(
|
||||
fem::FEM &,
|
||||
|
||||
@@ -83,10 +83,12 @@ export import :material.thermodynamic_equations;
|
||||
export import :deformation.descriptors;
|
||||
export import :deformation.surface_prescription;
|
||||
export import :deformation.nodal_radial_surface;
|
||||
export import :deformation.spherical_harmonic_surface;
|
||||
export import :deformation.interior_extension;
|
||||
export import :deformation.vacuum_extension;
|
||||
export import :deformation.radial_extensions;
|
||||
export import :deformation.domain_deformation;
|
||||
export import :deformation.surface_metric;
|
||||
export import :deformation.safe_newton_step;
|
||||
export import :model.stellar;
|
||||
export import :operators.root_manifest;
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -154,7 +154,10 @@ export namespace mean_field::equilibrium {
|
||||
m_discretization.domainMapper(),
|
||||
m_stellarModel,
|
||||
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
|
||||
CompileDefaultDomainDeformation(finiteElementModel)
|
||||
CompileDomainDeformation(
|
||||
m_discretization,
|
||||
finiteElementModel
|
||||
)
|
||||
) {
|
||||
VerifyProblem();
|
||||
}
|
||||
@@ -313,19 +316,13 @@ export namespace mean_field::equilibrium {
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] static deformation::PreparedDomainDeformationRuntime
|
||||
CompileDefaultDomainDeformation(fem::FEM &finiteElementModel) {
|
||||
MFEM_VERIFY(
|
||||
finiteElementModel.mesh != nullptr,
|
||||
"Default stellar domain-deformation compilation requires a physical mesh."
|
||||
);
|
||||
mfem::Vector referenceCenter(finiteElementModel.mesh->SpaceDimension());
|
||||
referenceCenter = 0.0;
|
||||
|
||||
[[nodiscard]] static deformation::PreparedDomainDeformationRuntime CompileDomainDeformation(
|
||||
const DiscretizationType &discretization,
|
||||
fem::FEM &finiteElementModel
|
||||
) {
|
||||
return deformation::PreparedDomainDeformationRuntime{deformation::compileDomainDeformation(
|
||||
deformation::NodalRadialSurface{std::move(referenceCenter)},
|
||||
deformation::PowerLawRadialInteriorExtension{}, deformation::FixedInfinityRadialVacuumExtension{},
|
||||
finiteElementModel
|
||||
discretization.surfaceDeformationPrescription(), discretization.interiorDeformationExtension(),
|
||||
discretization.vacuumDeformationExtension(), finiteElementModel
|
||||
)};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ module;
|
||||
|
||||
export module mean_field:preconditioning.material_surface;
|
||||
|
||||
import :deformation.surface_metric;
|
||||
|
||||
export import :operators.prepared_stellar_equilibrium;
|
||||
export import :operators.stellar_equilibrium_problem;
|
||||
export import :preconditioning.backend_implementations;
|
||||
@@ -1440,6 +1442,14 @@ export namespace mean_field::preconditioning {
|
||||
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
const fem::FEM &finiteElements = operation.GetHydrostaticOperator().GetFEM();
|
||||
if (!operation.GetDomainDeformation().hasNodalRadialParameters()) {
|
||||
// The inverse acts on the parameter dual, not an ambient
|
||||
// nodal trace. Keep the existing diagonal approximation but
|
||||
// form it in the prescription's actual coordinate space.
|
||||
return deformation::assembleSurfaceParameterMassDiagonal(
|
||||
finiteElements, operation.GetDomainDeformation()
|
||||
);
|
||||
}
|
||||
MFEM_VERIFY(
|
||||
finiteElements.mesh != nullptr && finiteElements.surfaceDeformationFes != nullptr,
|
||||
"The material/free-surface Riesz surrogate requires the surface finite-element space."
|
||||
@@ -1492,10 +1502,26 @@ export namespace mean_field::preconditioning {
|
||||
|
||||
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
const fem::FEM &finiteElements = m_operation->GetHydrostaticOperator().GetFEM();
|
||||
const field::ScalarBoundaryDofMap surfaceMap =
|
||||
field::make_stellar_surface_scalar_dof_map<DomainSchema>(*finiteElements.surfaceDeformationFes);
|
||||
if (surfaceMap.local_size() != m_surfaceDiagonal.Size()) {
|
||||
throw std::logic_error("Surface Riesz calibration received an incompatible boundary coordinate map.");
|
||||
const bool nodal = m_operation->GetDomainDeformation().hasNodalRadialParameters();
|
||||
field::ScalarBoundaryDofMap surfaceMap;
|
||||
if (nodal) {
|
||||
surfaceMap =
|
||||
field::make_stellar_surface_scalar_dof_map<DomainSchema>(*finiteElements.surfaceDeformationFes);
|
||||
if (surfaceMap.local_size() != m_surfaceDiagonal.Size()) {
|
||||
throw std::logic_error(
|
||||
"Surface Riesz calibration received an incompatible boundary coordinate map."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
int ranks = 0;
|
||||
if (MPI_Comm_size(Communicator(*m_operation), &ranks) != MPI_SUCCESS || ranks != 1) {
|
||||
throw std::invalid_argument(
|
||||
"Surface parameter calibration currently requires one MPI rank for nonnodal coordinates."
|
||||
);
|
||||
}
|
||||
if (m_surfaceDiagonal.Size() != m_operation->GetDomainDeformation().parameterCount()) {
|
||||
throw std::logic_error("Surface Riesz calibration received an incompatible parameter metric.");
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector probe(m_surfaceDiagonal.Size());
|
||||
@@ -1513,7 +1539,11 @@ export namespace mean_field::preconditioning {
|
||||
double localDenominator = 0.0;
|
||||
for (int sample = 0; sample < calibration.probeCount; ++sample) {
|
||||
for (int index = 0; index < probe.Size(); ++index) {
|
||||
std::uint64_t value = static_cast<std::uint64_t>(surfaceMap.global_boundary_dof(index));
|
||||
// Preserve nodal probes exactly. Nonnodal serial probes
|
||||
// use the immutable ordered parameter index instead of
|
||||
// pretending that each coefficient owns a boundary node.
|
||||
std::uint64_t value =
|
||||
static_cast<std::uint64_t>(nodal ? surfaceMap.global_boundary_dof(index) : index);
|
||||
value += 0x9e3779b97f4a7c15ULL * static_cast<std::uint64_t>(sample + 1);
|
||||
value = (value ^ (value >> 30U)) * 0xbf58476d1ce4e5b9ULL;
|
||||
value = (value ^ (value >> 27U)) * 0x94d049bb133111ebULL;
|
||||
@@ -1871,6 +1901,12 @@ export namespace mean_field::preconditioning {
|
||||
|
||||
[[nodiscard]] static field::ScalarBoundaryDofMap
|
||||
SurfaceMap(const operators::PreparedStellarEquilibriumOperator &operation) {
|
||||
if (!operation.GetDomainDeformation().hasNodalRadialParameters()) {
|
||||
throw std::invalid_argument(
|
||||
"SurfaceH1MassStiffness requires nodal radial parameters; a parameter-space H1 surrogate "
|
||||
"has not been implemented for this surface prescription."
|
||||
);
|
||||
}
|
||||
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
const fem::FEM &finiteElements = operation.GetHydrostaticOperator().GetFEM();
|
||||
return field::make_stellar_surface_scalar_dof_map<DomainSchema>(*finiteElements.surfaceDeformationFes);
|
||||
|
||||
Reference in New Issue
Block a user