2179 lines
110 KiB
C++
2179 lines
110 KiB
C++
module;
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <concepts>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include <mfem.hpp>
|
|
#include <mpi.h>
|
|
|
|
export module mean_field:preconditioning.material_surface;
|
|
|
|
export import :operators.prepared_stellar_equilibrium;
|
|
export import :operators.stellar_equilibrium_problem;
|
|
export import :preconditioning.backend_implementations;
|
|
export import :preconditioning.plan;
|
|
|
|
export namespace mean_field::preconditioning {
|
|
struct MaterialSurfaceBlockDiagonal final { };
|
|
struct CoupledMaterialIndependentSurface final { };
|
|
struct MaterialThenSurfaceTriangular final { };
|
|
struct SurfaceThenMaterialTriangular final { };
|
|
struct ApproximateMaterialSurfaceLDU final { };
|
|
|
|
template <typename Candidate> struct IsMaterialSurfaceFactorizationPolicy : std::false_type { };
|
|
template <> struct IsMaterialSurfaceFactorizationPolicy<MaterialSurfaceBlockDiagonal> : std::true_type { };
|
|
template <> struct IsMaterialSurfaceFactorizationPolicy<CoupledMaterialIndependentSurface> : std::true_type { };
|
|
template <> struct IsMaterialSurfaceFactorizationPolicy<MaterialThenSurfaceTriangular> : std::true_type { };
|
|
template <> struct IsMaterialSurfaceFactorizationPolicy<SurfaceThenMaterialTriangular> : std::true_type { };
|
|
template <> struct IsMaterialSurfaceFactorizationPolicy<ApproximateMaterialSurfaceLDU> : std::true_type { };
|
|
|
|
template <typename Candidate>
|
|
concept MaterialSurfaceFactorizationPolicy =
|
|
IsMaterialSurfaceFactorizationPolicy<std::remove_cvref_t<Candidate>>::value;
|
|
|
|
namespace detail {
|
|
template <typename... Lists> struct MaterialSurfaceConcatenate;
|
|
|
|
template <> struct MaterialSurfaceConcatenate<> {
|
|
using Type = utils::blocks::type_list<>;
|
|
};
|
|
|
|
template <typename... Types> struct MaterialSurfaceConcatenate<utils::blocks::type_list<Types...>> {
|
|
using Type = utils::blocks::type_list<Types...>;
|
|
};
|
|
|
|
template <typename... Left, typename... Right, typename... Remaining>
|
|
struct MaterialSurfaceConcatenate<
|
|
utils::blocks::type_list<Left...>,
|
|
utils::blocks::type_list<Right...>,
|
|
Remaining...> {
|
|
using Type =
|
|
typename MaterialSurfaceConcatenate<utils::blocks::type_list<Left..., Right...>, Remaining...>::Type;
|
|
};
|
|
|
|
template <typename... Lists>
|
|
using MaterialSurfaceConcatenateT = typename MaterialSurfaceConcatenate<Lists...>::Type;
|
|
|
|
template <typename Equations, typename CarrierField> struct NonCarrierEquations;
|
|
|
|
template <typename CarrierField>
|
|
struct NonCarrierEquations<material::ThermodynamicEquationCatalog<>, CarrierField> {
|
|
using Type = material::ThermodynamicEquationCatalog<>;
|
|
};
|
|
|
|
template <typename First, typename... Remaining, typename CarrierField>
|
|
struct NonCarrierEquations<material::ThermodynamicEquationCatalog<First, Remaining...>, CarrierField> {
|
|
private:
|
|
using Tail =
|
|
typename NonCarrierEquations<material::ThermodynamicEquationCatalog<Remaining...>, CarrierField>::Type;
|
|
|
|
template <typename Head, typename List> struct Prepend;
|
|
|
|
template <typename Head, typename... TailEquations>
|
|
struct Prepend<Head, material::ThermodynamicEquationCatalog<TailEquations...>> {
|
|
using Type = material::ThermodynamicEquationCatalog<Head, TailEquations...>;
|
|
};
|
|
|
|
public:
|
|
using Type = std::conditional_t<
|
|
std::same_as<typename First::FieldType, CarrierField>,
|
|
Tail,
|
|
typename Prepend<First, Tail>::Type>;
|
|
};
|
|
|
|
template <typename Equations> struct EquationCorrectionBlocks;
|
|
|
|
template <typename... Equations>
|
|
struct EquationCorrectionBlocks<material::ThermodynamicEquationCatalog<Equations...>> {
|
|
using Type = utils::blocks::type_list<typename Equations::Correction...>;
|
|
};
|
|
|
|
template <typename Equations> struct EquationResidualBlocks;
|
|
|
|
template <typename... Equations>
|
|
struct EquationResidualBlocks<material::ThermodynamicEquationCatalog<Equations...>> {
|
|
using Type = utils::blocks::type_list<typename Equations::Residual...>;
|
|
};
|
|
|
|
template <typename Residual, typename Corrections, typename JacobianForm> struct CouplingsForResidual;
|
|
|
|
template <typename Residual, typename JacobianForm>
|
|
struct CouplingsForResidual<Residual, utils::blocks::type_list<>, JacobianForm> {
|
|
using Type = utils::blocks::type_list<>;
|
|
};
|
|
|
|
template <typename Residual, typename First, typename... Remaining, typename JacobianForm>
|
|
struct CouplingsForResidual<Residual, utils::blocks::type_list<First, Remaining...>, JacobianForm> {
|
|
private:
|
|
using Tail =
|
|
typename CouplingsForResidual<Residual, utils::blocks::type_list<Remaining...>, JacobianForm>::Type;
|
|
|
|
public:
|
|
using Type = std::conditional_t<
|
|
utils::blocks::has_jacobian_coupling_v<Residual, First, JacobianForm>,
|
|
MaterialSurfaceConcatenateT<utils::blocks::type_list<Coupling<Residual, First>>, Tail>,
|
|
Tail>;
|
|
};
|
|
|
|
template <typename Residuals, typename Corrections, typename JacobianForm> struct InducedCouplings;
|
|
|
|
template <typename Corrections, typename JacobianForm>
|
|
struct InducedCouplings<utils::blocks::type_list<>, Corrections, JacobianForm> {
|
|
using Type = utils::blocks::type_list<>;
|
|
};
|
|
|
|
template <typename First, typename... Remaining, typename Corrections, typename JacobianForm>
|
|
struct InducedCouplings<utils::blocks::type_list<First, Remaining...>, Corrections, JacobianForm> {
|
|
using Type = MaterialSurfaceConcatenateT<
|
|
typename CouplingsForResidual<First, Corrections, JacobianForm>::Type,
|
|
typename InducedCouplings<utils::blocks::type_list<Remaining...>, Corrections, JacobianForm>::Type>;
|
|
};
|
|
|
|
template <typename Candidate, typename = void> struct IsMaterialSurfaceDescriptor : std::false_type { };
|
|
|
|
template <typename Candidate, typename Universe> struct MaterialSurfaceListIsSubset;
|
|
|
|
template <typename... Candidates, typename Universe>
|
|
struct MaterialSurfaceListIsSubset<utils::blocks::type_list<Candidates...>, Universe>
|
|
: std::bool_constant<(utils::blocks::contains_type_v<Candidates, Universe> && ...)> { };
|
|
} // namespace detail
|
|
|
|
template <
|
|
material::CompiledThermodynamicEquations ThermodynamicEquationsT,
|
|
typename SurfaceConstraintT,
|
|
typename FormT,
|
|
typename JacobianFormT>
|
|
requires requires {
|
|
typename SurfaceConstraintT::CarrierField;
|
|
typename SurfaceConstraintT::SurfaceDependencies;
|
|
typename SurfaceConstraintT::SurfaceDependencies::StateFieldTypes;
|
|
} && utils::blocks::valid_jacobian_form<FormT, JacobianFormT>
|
|
struct CompiledMaterialSurfaceDescriptor final {
|
|
using ThermodynamicEquations = ThermodynamicEquationsT;
|
|
using SurfaceConstraint = SurfaceConstraintT;
|
|
using Form = FormT;
|
|
using JacobianForm = JacobianFormT;
|
|
using CarrierField = typename SurfaceConstraint::CarrierField;
|
|
using CarrierEquation =
|
|
material::ThermodynamicEquationForFieldT<typename ThermodynamicEquations::Equations, CarrierField>;
|
|
using NonCarrierMaterialEquations =
|
|
typename detail::NonCarrierEquations<typename ThermodynamicEquations::Equations, CarrierField>::Type;
|
|
using NonCarrierCorrectionBlocks = typename detail::EquationCorrectionBlocks<NonCarrierMaterialEquations>::Type;
|
|
using NonCarrierResidualBlocks = typename detail::EquationResidualBlocks<NonCarrierMaterialEquations>::Type;
|
|
using SurfaceStateFields = typename SurfaceConstraint::SurfaceDependencies::StateFieldTypes;
|
|
using CorrectionBlocks = detail::MaterialSurfaceConcatenateT<
|
|
NonCarrierCorrectionBlocks,
|
|
utils::blocks::type_list<utils::blocks::surface_deformation::parameters::value>,
|
|
utils::blocks::type_list<typename CarrierEquation::Correction>>;
|
|
using ResidualBlocks = detail::MaterialSurfaceConcatenateT<
|
|
NonCarrierResidualBlocks,
|
|
utils::blocks::type_list<utils::blocks::surface_deformation::shape_equilibrium::residual>,
|
|
utils::blocks::type_list<typename CarrierEquation::Residual>>;
|
|
using RequiredCouplings =
|
|
typename detail::InducedCouplings<ResidualBlocks, CorrectionBlocks, JacobianForm>::Type;
|
|
|
|
static constexpr bool symbolicallySquare = CorrectionBlocks::size == ResidualBlocks::size;
|
|
static constexpr bool surfaceDependenciesBelongToMaterial = material::
|
|
fieldsBelongToThermodynamicEquations<SurfaceStateFields, typename ThermodynamicEquations::Equations>;
|
|
static constexpr bool correctionBlocksBelongToForm =
|
|
detail::MaterialSurfaceListIsSubset<CorrectionBlocks, typename Form::value_blocks>::value;
|
|
static constexpr bool residualBlocksBelongToForm =
|
|
detail::MaterialSurfaceListIsSubset<ResidualBlocks, typename Form::residual_blocks>::value;
|
|
};
|
|
|
|
template <typename Problem>
|
|
requires equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>>
|
|
using MaterialSurfaceDescriptorFor = CompiledMaterialSurfaceDescriptor<
|
|
typename std::remove_cvref_t<Problem>::ThermodynamicEquationsType,
|
|
typename std::remove_cvref_t<Problem>::CompiledSurfaceConstraintType,
|
|
typename std::remove_cvref_t<Problem>::FormType,
|
|
typename std::remove_cvref_t<Problem>::JacobianFormType>;
|
|
|
|
namespace detail {
|
|
template <typename ThermodynamicEquations, typename SurfaceConstraint, typename Form, typename JacobianForm>
|
|
struct IsMaterialSurfaceDescriptor<
|
|
CompiledMaterialSurfaceDescriptor<ThermodynamicEquations, SurfaceConstraint, Form, JacobianForm>>
|
|
: std::bool_constant<
|
|
material::CompiledThermodynamicEquations<typename CompiledMaterialSurfaceDescriptor<
|
|
ThermodynamicEquations,
|
|
SurfaceConstraint,
|
|
Form,
|
|
JacobianForm>::ThermodynamicEquations> &&
|
|
CompiledMaterialSurfaceDescriptor<ThermodynamicEquations, SurfaceConstraint, Form, JacobianForm>::
|
|
symbolicallySquare &&
|
|
CompiledMaterialSurfaceDescriptor<ThermodynamicEquations, SurfaceConstraint, Form, JacobianForm>::
|
|
surfaceDependenciesBelongToMaterial &&
|
|
CompiledMaterialSurfaceDescriptor<ThermodynamicEquations, SurfaceConstraint, Form, JacobianForm>::
|
|
correctionBlocksBelongToForm &&
|
|
CompiledMaterialSurfaceDescriptor<ThermodynamicEquations, SurfaceConstraint, Form, JacobianForm>::
|
|
residualBlocksBelongToForm &&
|
|
utils::blocks::types_are_unique_v<typename CompiledMaterialSurfaceDescriptor<
|
|
ThermodynamicEquations,
|
|
SurfaceConstraint,
|
|
Form,
|
|
JacobianForm>::CorrectionBlocks> &&
|
|
utils::blocks::types_are_unique_v<typename CompiledMaterialSurfaceDescriptor<
|
|
ThermodynamicEquations,
|
|
SurfaceConstraint,
|
|
Form,
|
|
JacobianForm>::ResidualBlocks>> { };
|
|
} // namespace detail
|
|
|
|
template <typename Candidate>
|
|
concept MaterialSurfaceDescriptor = detail::IsMaterialSurfaceDescriptor<std::remove_cvref_t<Candidate>>::value;
|
|
|
|
/*
|
|
* Capability boundary for EOS-specific material/surface surrogate
|
|
* assembly. The current kernels remain polytropic, but selection no
|
|
* longer embeds that closed-world type test in the descriptor concept.
|
|
*/
|
|
template <typename EquationOfState>
|
|
struct MaterialSurfaceEquationOfStateBackend {
|
|
static constexpr bool registered = false;
|
|
};
|
|
|
|
template <>
|
|
struct MaterialSurfaceEquationOfStateBackend<eos::Polytrope> {
|
|
static constexpr bool registered = true;
|
|
using CoreType = operators::PreparedStellarEquilibriumOperator;
|
|
};
|
|
|
|
template <typename EquationOfState>
|
|
concept ImplementedMaterialSurfaceEquationOfState = requires {
|
|
{
|
|
MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::registered
|
|
} -> std::convertible_to<bool>;
|
|
requires MaterialSurfaceEquationOfStateBackend<
|
|
std::remove_cvref_t<EquationOfState>>::registered;
|
|
typename MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::CoreType;
|
|
};
|
|
|
|
/*
|
|
* Registering an EOS-to-core association is intentionally not enough to
|
|
* claim that the material/surface preconditioner can execute it. Every
|
|
* implementation listed here must have matching prepared operators and
|
|
* prepare(...) overloads below. A future backend should add its pair only
|
|
* after those executable pieces exist; this keeps capability queries
|
|
* truthful while the current kernels still consume the legacy physical
|
|
* core directly.
|
|
*/
|
|
template <typename EquationOfState, typename PhysicalCore>
|
|
struct MaterialSurfaceExecutableRuntime {
|
|
static constexpr bool available = false;
|
|
};
|
|
|
|
template <>
|
|
struct MaterialSurfaceExecutableRuntime<eos::Polytrope, operators::PreparedStellarEquilibriumOperator> {
|
|
static constexpr bool available = true;
|
|
};
|
|
|
|
template <typename EquationOfState, typename PhysicalCore>
|
|
concept ExecutableMaterialSurfaceRuntimeFor = requires {
|
|
{
|
|
MaterialSurfaceExecutableRuntime<
|
|
std::remove_cvref_t<EquationOfState>,
|
|
std::remove_cvref_t<PhysicalCore>>::available
|
|
} -> std::convertible_to<bool>;
|
|
requires MaterialSurfaceExecutableRuntime<
|
|
std::remove_cvref_t<EquationOfState>,
|
|
std::remove_cvref_t<PhysicalCore>>::available;
|
|
};
|
|
|
|
template <typename Descriptor>
|
|
concept ImplementedMaterialSurfaceDescriptor =
|
|
MaterialSurfaceDescriptor<Descriptor> &&
|
|
ImplementedMaterialSurfaceEquationOfState<
|
|
typename Descriptor::ThermodynamicEquations::EquationOfStateType>;
|
|
|
|
template <typename Descriptor, typename PhysicalCore>
|
|
concept MaterialSurfaceRuntimeFor =
|
|
ImplementedMaterialSurfaceDescriptor<Descriptor> && requires {
|
|
typename MaterialSurfaceEquationOfStateBackend<
|
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType;
|
|
requires std::same_as<
|
|
std::remove_cvref_t<PhysicalCore>,
|
|
typename MaterialSurfaceEquationOfStateBackend<
|
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType>;
|
|
requires ExecutableMaterialSurfaceRuntimeFor<
|
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType,
|
|
PhysicalCore>;
|
|
};
|
|
|
|
template <typename Candidate>
|
|
concept MaterialSurfacePreconditionerProblem =
|
|
equilibrium::DiscretizedStellarEquilibriumProblem<Candidate> && requires {
|
|
requires MaterialSurfaceRuntimeFor<
|
|
MaterialSurfaceDescriptorFor<std::remove_cvref_t<Candidate>>,
|
|
typename std::remove_cvref_t<Candidate>::PhysicalCoreType>;
|
|
};
|
|
|
|
using DensityMassDiagonalCharacteristics = OperatorCharacteristics<
|
|
OperatorCategory::mass_like,
|
|
OperatorValueStructure::scalar,
|
|
OperatorSymmetry::symmetric,
|
|
OperatorDefiniteness::positive_definite,
|
|
OperatorRepresentation::diagonal,
|
|
OperatorDistribution::distributed_true_dof,
|
|
OperatorFESpace::l2>;
|
|
|
|
using EnthalpyMassDiagonalCharacteristics = OperatorCharacteristics<
|
|
OperatorCategory::mass_like,
|
|
OperatorValueStructure::scalar,
|
|
OperatorSymmetry::symmetric,
|
|
OperatorDefiniteness::positive_definite,
|
|
OperatorRepresentation::diagonal,
|
|
OperatorDistribution::distributed_true_dof,
|
|
OperatorFESpace::h1>;
|
|
|
|
// This describes the assembled diagonal surrogate, not the generally
|
|
// nonsymmetric pulled-back q-to-R_q operator that it approximates. A
|
|
// calibrated scalar multiple may carry either sign, so definiteness is not
|
|
// claimed by this legacy path.
|
|
using SurfaceDiagonalCharacteristics = OperatorCharacteristics<
|
|
OperatorCategory::surface_like,
|
|
OperatorValueStructure::scalar,
|
|
OperatorSymmetry::symmetric,
|
|
OperatorDefiniteness::unspecified,
|
|
OperatorRepresentation::diagonal,
|
|
OperatorDistribution::distributed_true_dof,
|
|
OperatorFESpace::h1>;
|
|
|
|
// The frequency-aware surface surrogate is an elliptic scalar operator on
|
|
// the ambient H1 space whose trace supplies the deformation parameters.
|
|
// A positive mass coefficient removes the constant-mode nullspace of the
|
|
// tangential stiffness contribution.
|
|
using SurfaceH1MassStiffnessCharacteristics = OperatorCharacteristics<
|
|
OperatorCategory::elliptic_like,
|
|
OperatorValueStructure::scalar,
|
|
OperatorSymmetry::symmetric,
|
|
OperatorDefiniteness::positive_definite,
|
|
OperatorRepresentation::assembled_sparse,
|
|
OperatorDistribution::distributed_true_dof,
|
|
OperatorFESpace::h1>;
|
|
|
|
enum class SurfaceRieszCalibrationTarget { none, surface_jacobian, approximate_material_schur };
|
|
|
|
// operator_action fits the surrogate s M directly to the requested target
|
|
// T. right_preconditioned_action instead fits T (alpha M^{-1}) to the
|
|
// identity and stores s = 1 / alpha. Both produce one fixed linear
|
|
// diagonal operator after setup; the distinction is solely the calibration
|
|
// objective.
|
|
enum class SurfaceRieszCalibrationObjective { operator_action, right_preconditioned_action };
|
|
|
|
struct SurfaceRieszCalibrationOptions final {
|
|
SurfaceRieszCalibrationTarget target{SurfaceRieszCalibrationTarget::none};
|
|
int probeCount{0};
|
|
SurfaceRieszCalibrationObjective objective{SurfaceRieszCalibrationObjective::operator_action};
|
|
};
|
|
|
|
struct MaterialSurfaceDiagonalOptions final {
|
|
double relativeFloor{1.0e-12};
|
|
double absoluteFloor{1.0e-14};
|
|
SurfaceRieszCalibrationOptions surfaceCalibration{};
|
|
};
|
|
|
|
struct SurfaceMassDiagonal final { };
|
|
|
|
struct SurfaceH1MassStiffness final {
|
|
SurfaceRieszCalibrationOptions calibration{
|
|
.target = SurfaceRieszCalibrationTarget::approximate_material_schur,
|
|
.probeCount = 6
|
|
};
|
|
double relativeMassCoefficientFloor{1.0e-10};
|
|
double gramRelativeTolerance{1.0e-12};
|
|
};
|
|
|
|
template <typename Candidate> struct SurfaceSurrogateTraits {
|
|
static constexpr bool registered = false;
|
|
};
|
|
|
|
template <> struct SurfaceSurrogateTraits<SurfaceMassDiagonal> {
|
|
static constexpr bool registered = true;
|
|
using OperatorDescription = SurfaceDiagonalCharacteristics;
|
|
};
|
|
|
|
template <> struct SurfaceSurrogateTraits<SurfaceH1MassStiffness> {
|
|
static constexpr bool registered = true;
|
|
using OperatorDescription = SurfaceH1MassStiffnessCharacteristics;
|
|
};
|
|
|
|
template <typename Candidate>
|
|
concept MaterialSurfaceSurrogate = SurfaceSurrogateTraits<std::remove_cvref_t<Candidate>>::registered;
|
|
|
|
struct SurfaceH1NormalEquations final {
|
|
double massMass{0.0};
|
|
double massStiffness{0.0};
|
|
double stiffnessStiffness{0.0};
|
|
double massTarget{0.0};
|
|
double stiffnessTarget{0.0};
|
|
double targetTarget{0.0};
|
|
};
|
|
|
|
struct SurfaceH1FitReport final {
|
|
SurfaceRieszCalibrationTarget target{SurfaceRieszCalibrationTarget::none};
|
|
int probeCount{0};
|
|
double sign{1.0};
|
|
double massCoefficient{1.0};
|
|
double stiffnessCoefficient{0.0};
|
|
double relativeResidual{0.0};
|
|
double relativeGramDeterminant{0.0};
|
|
SurfaceH1NormalEquations normalEquations{};
|
|
|
|
[[nodiscard]] bool WasCalibrated() const noexcept {
|
|
return target != SurfaceRieszCalibrationTarget::none;
|
|
}
|
|
};
|
|
|
|
namespace detail {
|
|
[[nodiscard]] inline SurfaceH1FitReport fitSurfaceH1Coefficients(
|
|
const SurfaceH1NormalEquations &equations,
|
|
const SurfaceH1MassStiffness &configuration
|
|
) {
|
|
const std::array values{
|
|
equations.massMass,
|
|
equations.massStiffness,
|
|
equations.stiffnessStiffness,
|
|
equations.massTarget,
|
|
equations.stiffnessTarget,
|
|
equations.targetTarget,
|
|
configuration.relativeMassCoefficientFloor,
|
|
configuration.gramRelativeTolerance
|
|
};
|
|
if (!std::all_of(values.begin(), values.end(), [](const double value) { return std::isfinite(value); }) ||
|
|
equations.massMass <= 0.0 || equations.stiffnessStiffness <= 0.0 || equations.targetTarget <= 0.0 ||
|
|
configuration.relativeMassCoefficientFloor <= 0.0 || configuration.gramRelativeTolerance <= 0.0 ||
|
|
configuration.gramRelativeTolerance >= 1.0 ||
|
|
configuration.calibration.target == SurfaceRieszCalibrationTarget::none ||
|
|
configuration.calibration.probeCount <= 0) {
|
|
throw std::invalid_argument(
|
|
"The surface H1 fit requires a calibration target, probes, and finite positive Gram data."
|
|
);
|
|
}
|
|
|
|
const double massNorm = std::sqrt(equations.massMass);
|
|
const double stiffnessNorm = std::sqrt(equations.stiffnessStiffness);
|
|
const double correlation = equations.massStiffness / massNorm / stiffnessNorm;
|
|
const double relativeDeterminant = 1.0 - correlation * correlation;
|
|
const double normalizedMassTarget = equations.massTarget / massNorm;
|
|
const double normalizedStiffnessTarget = equations.stiffnessTarget / stiffnessNorm;
|
|
if (!std::isfinite(massNorm) || !std::isfinite(stiffnessNorm) || !std::isfinite(correlation) ||
|
|
!std::isfinite(relativeDeterminant) || !std::isfinite(normalizedMassTarget) ||
|
|
!std::isfinite(normalizedStiffnessTarget) ||
|
|
relativeDeterminant <= configuration.gramRelativeTolerance) {
|
|
throw std::runtime_error("The surface H1 calibration probes do not distinguish mass and stiffness.");
|
|
}
|
|
|
|
const double amplitude = std::sqrt(equations.targetTarget) / massNorm;
|
|
const double minimumMassCoefficient = configuration.relativeMassCoefficientFloor * amplitude;
|
|
if (!std::isfinite(amplitude) || !std::isfinite(minimumMassCoefficient) || minimumMassCoefficient <= 0.0) {
|
|
throw std::runtime_error("The surface H1 calibration produced an invalid positive mass floor.");
|
|
}
|
|
struct Candidate final {
|
|
double sign{1.0};
|
|
double mass{0.0};
|
|
double stiffness{0.0};
|
|
double residual{std::numeric_limits<double>::infinity()};
|
|
};
|
|
const auto evaluate = [&](const double sign, const double mass, const double stiffness) {
|
|
Candidate candidate{.sign = sign, .mass = mass, .stiffness = stiffness};
|
|
if (!std::isfinite(mass) || !std::isfinite(stiffness) || mass < minimumMassCoefficient ||
|
|
stiffness < 0.0) {
|
|
return candidate;
|
|
}
|
|
const double normalizedMass = mass * massNorm;
|
|
const double normalizedStiffness = stiffness * stiffnessNorm;
|
|
candidate.residual =
|
|
equations.targetTarget + normalizedMass * normalizedMass +
|
|
2.0 * correlation * normalizedMass * normalizedStiffness +
|
|
normalizedStiffness * normalizedStiffness -
|
|
2.0 * sign *
|
|
(normalizedMass * normalizedMassTarget + normalizedStiffness * normalizedStiffnessTarget);
|
|
candidate.residual = std::max(0.0, candidate.residual);
|
|
return candidate;
|
|
};
|
|
|
|
Candidate best;
|
|
for (const double sign : {-1.0, 1.0}) {
|
|
const double unconstrainedMass = sign *
|
|
(normalizedMassTarget - correlation * normalizedStiffnessTarget) /
|
|
relativeDeterminant / massNorm;
|
|
const double unconstrainedStiffness = sign *
|
|
(normalizedStiffnessTarget - correlation * normalizedMassTarget) /
|
|
relativeDeterminant / stiffnessNorm;
|
|
if (unconstrainedMass >= minimumMassCoefficient && unconstrainedStiffness >= 0.0) {
|
|
const Candidate candidate = evaluate(sign, unconstrainedMass, unconstrainedStiffness);
|
|
if (candidate.residual < best.residual) {
|
|
best = candidate;
|
|
}
|
|
}
|
|
|
|
const Candidate massOnly =
|
|
evaluate(sign, std::max(minimumMassCoefficient, sign * normalizedMassTarget / massNorm), 0.0);
|
|
if (massOnly.residual < best.residual) {
|
|
best = massOnly;
|
|
}
|
|
|
|
const double boundaryStiffness = std::max(
|
|
0.0,
|
|
(sign * normalizedStiffnessTarget - minimumMassCoefficient * massNorm * correlation) / stiffnessNorm
|
|
);
|
|
const Candidate massFloor = evaluate(sign, minimumMassCoefficient, boundaryStiffness);
|
|
if (massFloor.residual < best.residual) {
|
|
best = massFloor;
|
|
}
|
|
}
|
|
if (!std::isfinite(best.residual) || !std::isfinite(best.mass) || !std::isfinite(best.stiffness)) {
|
|
throw std::runtime_error("The surface H1 calibration failed to produce a finite constrained fit.");
|
|
}
|
|
return {
|
|
.target = configuration.calibration.target,
|
|
.probeCount = configuration.calibration.probeCount,
|
|
.sign = best.sign,
|
|
.massCoefficient = best.mass,
|
|
.stiffnessCoefficient = best.stiffness,
|
|
.relativeResidual = std::sqrt(best.residual / equations.targetTarget),
|
|
.relativeGramDeterminant = relativeDeterminant,
|
|
.normalEquations = equations
|
|
};
|
|
}
|
|
} // namespace detail
|
|
|
|
using CoupledMaterialSurfaceCharacteristics = OperatorCharacteristics<
|
|
OperatorCategory::mixed,
|
|
OperatorValueStructure::block,
|
|
OperatorSymmetry::nonsymmetric,
|
|
OperatorDefiniteness::unspecified,
|
|
OperatorRepresentation::matrix_free,
|
|
OperatorDistribution::distributed_true_dof,
|
|
OperatorFESpace::product>;
|
|
|
|
namespace backend {
|
|
template <
|
|
MaterialSurfaceDescriptor Descriptor,
|
|
Registered MaterialBackend,
|
|
Registered SurfaceBackend,
|
|
MaterialSurfaceFactorizationPolicy Policy,
|
|
MaterialSurfaceSurrogate SurfaceSurrogate = SurfaceMassDiagonal>
|
|
requires Compatible<MaterialBackend, DensityMassDiagonalCharacteristics> &&
|
|
Compatible<MaterialBackend, EnthalpyMassDiagonalCharacteristics> &&
|
|
Compatible<SurfaceBackend, typename SurfaceSurrogateTraits<SurfaceSurrogate>::OperatorDescription>
|
|
struct MaterialSurface final {
|
|
using DescriptorType = Descriptor;
|
|
using MaterialBackendType = MaterialBackend;
|
|
using SurfaceBackendType = SurfaceBackend;
|
|
using FactorizationPolicyType = Policy;
|
|
using SurfaceSurrogateType = SurfaceSurrogate;
|
|
};
|
|
|
|
template <
|
|
MaterialSurfaceDescriptor Descriptor,
|
|
Registered MaterialBackend,
|
|
Registered SurfaceBackend,
|
|
MaterialSurfaceFactorizationPolicy Policy,
|
|
MaterialSurfaceSurrogate SurfaceSurrogate>
|
|
requires Compatible<MaterialBackend, DensityMassDiagonalCharacteristics> &&
|
|
Compatible<MaterialBackend, EnthalpyMassDiagonalCharacteristics> &&
|
|
Compatible<SurfaceBackend, typename SurfaceSurrogateTraits<SurfaceSurrogate>::OperatorDescription>
|
|
struct Traits<MaterialSurface<Descriptor, MaterialBackend, SurfaceBackend, Policy, SurfaceSurrogate>> {
|
|
static constexpr bool registered = true;
|
|
static constexpr ApplicationContract applicationContract =
|
|
::mean_field::preconditioning::backend::applicationContract<MaterialBackend> ==
|
|
ApplicationContract::stationary_linear &&
|
|
::mean_field::preconditioning::backend::applicationContract<SurfaceBackend> ==
|
|
ApplicationContract::stationary_linear
|
|
? ApplicationContract::stationary_linear
|
|
: ApplicationContract::flexible;
|
|
static constexpr bool supportsSerialExecution =
|
|
Traits<MaterialBackend>::supportsSerialExecution && Traits<SurfaceBackend>::supportsSerialExecution;
|
|
static constexpr bool supportsDistributedExecution =
|
|
Traits<MaterialBackend>::supportsDistributedExecution &&
|
|
Traits<SurfaceBackend>::supportsDistributedExecution;
|
|
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none;
|
|
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
|
|
static constexpr SurrogateRequirement surrogateRequirement = Traits<SurfaceBackend>::surrogateRequirement;
|
|
static constexpr bool requiresAssembledSparseSurrogate =
|
|
Traits<SurfaceBackend>::requiresAssembledSparseSurrogate;
|
|
|
|
using PreparationDependencies = preconditioning::PreparationDependencies<
|
|
PreparationDependency::discretization,
|
|
PreparationDependency::geometry,
|
|
PreparationDependency::equation_of_state,
|
|
PreparationDependency::linearization>;
|
|
|
|
template <OperatorCharacteristicsType Characteristics>
|
|
static constexpr bool supports =
|
|
Characteristics::category == OperatorCategory::mixed &&
|
|
Characteristics::valueStructure == OperatorValueStructure::block &&
|
|
Characteristics::symmetry == OperatorSymmetry::nonsymmetric &&
|
|
Characteristics::representation == OperatorRepresentation::matrix_free &&
|
|
Characteristics::distribution == OperatorDistribution::distributed_true_dof &&
|
|
Characteristics::finiteElementSpace == OperatorFESpace::product;
|
|
};
|
|
} // namespace backend
|
|
|
|
template <
|
|
ImplementedMaterialSurfaceDescriptor DescriptorT,
|
|
backend::Registered MaterialBackendT,
|
|
backend::Registered SurfaceBackendT,
|
|
MaterialSurfaceFactorizationPolicy FactorizationPolicyT,
|
|
MaterialSurfaceSurrogate SurfaceSurrogateT = SurfaceMassDiagonal>
|
|
requires backend::Compatible<MaterialBackendT, DensityMassDiagonalCharacteristics> &&
|
|
backend::Compatible<MaterialBackendT, EnthalpyMassDiagonalCharacteristics> &&
|
|
backend::Compatible<
|
|
SurfaceBackendT,
|
|
typename SurfaceSurrogateTraits<SurfaceSurrogateT>::OperatorDescription>
|
|
class MaterialSurfaceBlock final {
|
|
public:
|
|
using Descriptor = DescriptorT;
|
|
using CorrectionBlocks = typename Descriptor::CorrectionBlocks;
|
|
using ResidualBlocks = typename Descriptor::ResidualBlocks;
|
|
using RequiredCouplings = typename Descriptor::RequiredCouplings;
|
|
using OperatorDescription = CoupledMaterialSurfaceCharacteristics;
|
|
using BackendType = backend::
|
|
MaterialSurface<Descriptor, MaterialBackendT, SurfaceBackendT, FactorizationPolicyT, SurfaceSurrogateT>;
|
|
using PreparationDependencies = typename backend::Traits<BackendType>::PreparationDependencies;
|
|
using MaterialBackend = MaterialBackendT;
|
|
using SurfaceBackend = SurfaceBackendT;
|
|
using Factorization = FactorizationPolicyT;
|
|
using SurfaceSurrogate = SurfaceSurrogateT;
|
|
|
|
constexpr MaterialSurfaceBlock(
|
|
MaterialBackendT materialBackend = {},
|
|
SurfaceBackendT surfaceBackend = {},
|
|
FactorizationPolicyT factorizationPolicy = {},
|
|
MaterialSurfaceDiagonalOptions diagonalOptions = {},
|
|
SurfaceSurrogateT surfaceSurrogate = {}
|
|
)
|
|
: m_materialBackend(std::move(materialBackend)),
|
|
m_surfaceBackend(std::move(surfaceBackend)),
|
|
m_factorizationPolicy(std::move(factorizationPolicy)),
|
|
m_diagonalOptions(diagonalOptions),
|
|
m_surfaceSurrogate(std::move(surfaceSurrogate)) {
|
|
}
|
|
|
|
[[nodiscard]] constexpr const MaterialBackendT &materialBackend() const noexcept {
|
|
return m_materialBackend;
|
|
}
|
|
[[nodiscard]] constexpr const SurfaceBackendT &surfaceBackend() const noexcept {
|
|
return m_surfaceBackend;
|
|
}
|
|
[[nodiscard]] constexpr const FactorizationPolicyT &factorizationPolicy() const noexcept {
|
|
return m_factorizationPolicy;
|
|
}
|
|
[[nodiscard]] constexpr const MaterialSurfaceDiagonalOptions &diagonalOptions() const noexcept {
|
|
return m_diagonalOptions;
|
|
}
|
|
[[nodiscard]] constexpr const SurfaceSurrogateT &surfaceSurrogate() const noexcept {
|
|
return m_surfaceSurrogate;
|
|
}
|
|
|
|
private:
|
|
MaterialBackendT m_materialBackend;
|
|
SurfaceBackendT m_surfaceBackend;
|
|
FactorizationPolicyT m_factorizationPolicy;
|
|
MaterialSurfaceDiagonalOptions m_diagonalOptions;
|
|
SurfaceSurrogateT m_surfaceSurrogate;
|
|
};
|
|
|
|
template <
|
|
MaterialSurfacePreconditionerProblem Problem,
|
|
backend::Registered MaterialBackend = backend::Diagonal,
|
|
backend::Registered SurfaceBackend = backend::Diagonal,
|
|
MaterialSurfaceFactorizationPolicy Policy = SurfaceThenMaterialTriangular>
|
|
[[nodiscard]] constexpr auto materialSurfaceBlock(
|
|
const Problem &,
|
|
MaterialBackend materialBackend = {},
|
|
SurfaceBackend surfaceBackend = {},
|
|
Policy policy = {},
|
|
const MaterialSurfaceDiagonalOptions diagonalOptions = {}
|
|
) {
|
|
using Descriptor = MaterialSurfaceDescriptorFor<std::remove_cvref_t<Problem>>;
|
|
return MaterialSurfaceBlock<Descriptor, MaterialBackend, SurfaceBackend, Policy>{
|
|
std::move(materialBackend), std::move(surfaceBackend), std::move(policy), diagonalOptions
|
|
};
|
|
}
|
|
|
|
template <
|
|
MaterialSurfacePreconditionerProblem Problem,
|
|
backend::Registered MaterialBackend,
|
|
backend::Registered SurfaceBackend,
|
|
MaterialSurfaceFactorizationPolicy Policy,
|
|
MaterialSurfaceSurrogate SurfaceSurrogate>
|
|
[[nodiscard]] constexpr auto materialSurfaceBlock(
|
|
const Problem &,
|
|
MaterialBackend materialBackend,
|
|
SurfaceBackend surfaceBackend,
|
|
Policy policy,
|
|
SurfaceSurrogate surfaceSurrogate,
|
|
const MaterialSurfaceDiagonalOptions diagonalOptions = {}
|
|
) {
|
|
using Descriptor = MaterialSurfaceDescriptorFor<std::remove_cvref_t<Problem>>;
|
|
return MaterialSurfaceBlock<Descriptor, MaterialBackend, SurfaceBackend, Policy, SurfaceSurrogate>{
|
|
std::move(materialBackend), std::move(surfaceBackend), std::move(policy), diagonalOptions,
|
|
std::move(surfaceSurrogate)
|
|
};
|
|
}
|
|
|
|
class MaterialSurfaceJacobianOperator final : public mfem::Operator {
|
|
public:
|
|
explicit MaterialSurfaceJacobianOperator(const operators::PreparedStellarEquilibriumOperator &operation)
|
|
: mfem::Operator(TotalSize(operation)),
|
|
m_operation(std::addressof(operation)),
|
|
m_offsets(4),
|
|
m_zeroDensity(operation.GetBarotropicClosureOperator().GetDensitySize()),
|
|
m_zeroGravity(
|
|
operation.GetDisplacementOperator().GetGravityContext().GetGravityGradientMap().reduced_size()
|
|
),
|
|
m_zeroPotential(operation.GetHydrostaticOperator().GetGravityPotentialMap().reduced_size()),
|
|
m_zeroEnthalpy(operation.GetBarotropicClosureOperator().GetEnthalpySize()),
|
|
m_volumeDisplacement(operation.GetDomainDeformation().volumeDisplacementSize()),
|
|
m_mechanicalAction(operation.GetDomainDeformation().volumeDisplacementSize()),
|
|
m_pullbackAction(operation.GetDomainDeformation().parameterCount()),
|
|
m_fullDirection(operation.Width()),
|
|
m_fullAction(operation.Height()) {
|
|
m_offsets[0] = 0;
|
|
m_offsets[1] = m_zeroDensity.Size();
|
|
m_offsets[2] = m_offsets[1] + operation.GetDomainDeformation().parameterCount();
|
|
m_offsets[3] = Height();
|
|
m_zeroDensity = 0.0;
|
|
m_zeroGravity = 0.0;
|
|
m_zeroPotential = 0.0;
|
|
m_zeroEnthalpy = 0.0;
|
|
}
|
|
|
|
void Mult(
|
|
const mfem::Vector &direction,
|
|
mfem::Vector &action
|
|
) const override {
|
|
VerifyVectors(direction, action);
|
|
const auto densityDirection = ConstBlock(direction, 0);
|
|
const auto surfaceDirection = ConstBlock(direction, 1);
|
|
const auto enthalpyDirection = ConstBlock(direction, 2);
|
|
auto densityAction = MutableBlock(action, 0);
|
|
auto surfaceAction = MutableBlock(action, 1);
|
|
auto enthalpyAction = MutableBlock(action, 2);
|
|
|
|
// This operator is the diagnostic restriction R_material J
|
|
// P_material, so construct it through the authoritative stellar
|
|
// Jacobian. The block factorization below continues to use the
|
|
// direct coupling actions and does not pay for a full Jacobian
|
|
// application.
|
|
m_fullDirection = 0.0;
|
|
const auto fullDirectionView = m_operation->GetRootManifest().stateView(m_fullDirection);
|
|
mfem::Vector fullDensityDirection = fullDirectionView.block(utils::blocks::density_field.mass_term);
|
|
mfem::Vector fullSurfaceDirection =
|
|
fullDirectionView.block(utils::blocks::surface_deformation_field.parameters_term);
|
|
mfem::Vector fullEnthalpyDirection = fullDirectionView.block(utils::blocks::enthalpy_field.specific_term);
|
|
fullDensityDirection = densityDirection;
|
|
fullSurfaceDirection = surfaceDirection;
|
|
fullEnthalpyDirection = enthalpyDirection;
|
|
|
|
m_operation->Mult(m_fullDirection, m_fullAction);
|
|
const auto fullActionView = m_operation->GetRootManifest().residualView(m_fullAction);
|
|
const auto fullDensityAction = fullActionView.block(utils::blocks::density_field.mass_term);
|
|
const auto fullSurfaceAction =
|
|
fullActionView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
|
const auto fullEnthalpyAction = fullActionView.block(utils::blocks::enthalpy_field.specific_term);
|
|
densityAction = fullDensityAction;
|
|
surfaceAction = fullSurfaceAction;
|
|
enthalpyAction = fullEnthalpyAction;
|
|
}
|
|
|
|
void ApplyEnthalpyToDensity(
|
|
const mfem::Vector &enthalpy,
|
|
mfem::Vector &densityAction
|
|
) const {
|
|
VerifyBlock(enthalpy, 2, "enthalpy direction");
|
|
VerifyBlock(densityAction, 0, "density action");
|
|
m_operation->GetBarotropicClosureOperator().Mult(
|
|
m_zeroDensity, enthalpy, ZeroVolumeDisplacement(), densityAction
|
|
);
|
|
}
|
|
|
|
void ApplySurfaceToMaterial(
|
|
const mfem::Vector &surface,
|
|
mfem::Vector &densityAction,
|
|
mfem::Vector &enthalpyAction
|
|
) const {
|
|
VerifyBlock(surface, 1, "surface direction");
|
|
VerifyBlock(densityAction, 0, "density action");
|
|
VerifyBlock(enthalpyAction, 2, "enthalpy action");
|
|
GenerateDisplacement(surface);
|
|
m_operation->GetBarotropicClosureOperator().Mult(
|
|
m_zeroDensity, m_zeroEnthalpy, m_volumeDisplacement, densityAction
|
|
);
|
|
m_operation->GetHydrostaticOperator().ApplyDisplacementJacobianAction(m_volumeDisplacement, enthalpyAction);
|
|
m_operation->GetSurfaceConstraintOperator().ApplyJacobianRows(m_zeroEnthalpy, enthalpyAction);
|
|
}
|
|
|
|
void ApplyMaterialToSurface(
|
|
const mfem::Vector &density,
|
|
const mfem::Vector &enthalpy,
|
|
mfem::Vector &surfaceAction
|
|
) const {
|
|
VerifyBlock(density, 0, "density direction");
|
|
VerifyBlock(enthalpy, 2, "enthalpy direction");
|
|
VerifyBlock(surfaceAction, 1, "surface action");
|
|
m_operation->GetDisplacementOperator().ApplyCompleteJacobianAction(
|
|
density, ZeroVolumeDisplacement(), m_zeroGravity, enthalpy, m_mechanicalAction
|
|
);
|
|
m_operation->GetDomainDeformation().applyJacobianTranspose(
|
|
m_operation->GetSurfaceDeformationParameters(), m_mechanicalAction, surfaceAction
|
|
);
|
|
}
|
|
|
|
void ApplySurfaceToSurface(
|
|
const mfem::Vector &surface,
|
|
mfem::Vector &surfaceAction
|
|
) const {
|
|
VerifyBlock(surface, 1, "surface direction");
|
|
VerifyBlock(surfaceAction, 1, "surface action");
|
|
GenerateDisplacement(surface);
|
|
m_operation->GetDisplacementOperator().ApplyDisplacementJacobianAction(
|
|
m_volumeDisplacement, m_mechanicalAction
|
|
);
|
|
m_operation->GetDomainDeformation().applyJacobianTranspose(
|
|
m_operation->GetSurfaceDeformationParameters(), m_mechanicalAction, surfaceAction
|
|
);
|
|
m_operation->GetDomainDeformation().applyPullbackDerivative(
|
|
m_operation->GetSurfaceDeformationParameters(), surface, m_operation->GetFullMechanicalResidual(),
|
|
m_pullbackAction
|
|
);
|
|
surfaceAction += m_pullbackAction;
|
|
}
|
|
|
|
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept {
|
|
return m_offsets;
|
|
}
|
|
|
|
private:
|
|
[[nodiscard]] static int TotalSize(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
if (!operation.IsPrepared()) {
|
|
throw std::logic_error("The material-surface Jacobian requires a prepared stellar operator.");
|
|
}
|
|
return operation.GetBarotropicClosureOperator().GetDensitySize() +
|
|
operation.GetDomainDeformation().parameterCount() +
|
|
operation.GetBarotropicClosureOperator().GetEnthalpySize();
|
|
}
|
|
|
|
[[nodiscard]] mfem::Vector ConstBlock(
|
|
const mfem::Vector &vector,
|
|
int block
|
|
) const {
|
|
return mfem::Vector(
|
|
const_cast<mfem::real_t *>(vector.GetData()) + m_offsets[block], m_offsets[block + 1] - m_offsets[block]
|
|
);
|
|
}
|
|
[[nodiscard]] mfem::Vector MutableBlock(
|
|
mfem::Vector &vector,
|
|
int block
|
|
) const {
|
|
return mfem::Vector(vector.GetData() + m_offsets[block], m_offsets[block + 1] - m_offsets[block]);
|
|
}
|
|
void VerifyBlock(
|
|
const mfem::Vector &vector,
|
|
int block,
|
|
const char *name
|
|
) const {
|
|
if (vector.Size() != m_offsets[block + 1] - m_offsets[block]) {
|
|
throw std::invalid_argument(std::string("The material-surface ") + name + " has the wrong size.");
|
|
}
|
|
}
|
|
void VerifyVectors(
|
|
const mfem::Vector &direction,
|
|
const mfem::Vector &action
|
|
) const {
|
|
if (direction.Size() != Width() || action.Size() != Height()) {
|
|
throw std::invalid_argument("The material-surface Jacobian requires compatible, preallocated vectors.");
|
|
}
|
|
}
|
|
void GenerateDisplacement(const mfem::Vector &surface) const {
|
|
m_operation->GetDomainDeformation().applyJacobian(
|
|
m_operation->GetSurfaceDeformationParameters(), surface, m_volumeDisplacement
|
|
);
|
|
}
|
|
[[nodiscard]] const mfem::Vector &ZeroVolumeDisplacement() const {
|
|
m_volumeDisplacement = 0.0;
|
|
return m_volumeDisplacement;
|
|
}
|
|
|
|
const operators::PreparedStellarEquilibriumOperator *m_operation;
|
|
mfem::Array<int> m_offsets;
|
|
mfem::Vector m_zeroDensity;
|
|
mfem::Vector m_zeroGravity;
|
|
mfem::Vector m_zeroPotential;
|
|
mfem::Vector m_zeroEnthalpy;
|
|
mutable mfem::Vector m_volumeDisplacement;
|
|
mutable mfem::Vector m_mechanicalAction;
|
|
mutable mfem::Vector m_pullbackAction;
|
|
mutable mfem::Vector m_fullDirection;
|
|
mutable mfem::Vector m_fullAction;
|
|
};
|
|
|
|
struct MaterialSurfaceFactorizationStatistics final {
|
|
std::uint64_t applications{0};
|
|
std::uint64_t densityInverseApplications{0};
|
|
std::uint64_t surfaceInverseApplications{0};
|
|
std::uint64_t enthalpyInverseApplications{0};
|
|
std::uint64_t enthalpyToDensityApplications{0};
|
|
std::uint64_t surfaceToMaterialApplications{0};
|
|
std::uint64_t materialToSurfaceApplications{0};
|
|
};
|
|
|
|
template <typename Candidate>
|
|
concept MaterialSurfaceCouplingOperator = requires(
|
|
const Candidate &couplings,
|
|
const mfem::Vector &density,
|
|
const mfem::Vector &surface,
|
|
const mfem::Vector &enthalpy,
|
|
mfem::Vector &densityAction,
|
|
mfem::Vector &surfaceAction,
|
|
mfem::Vector &enthalpyAction
|
|
) {
|
|
{ couplings.Height() } -> std::same_as<int>;
|
|
{ couplings.GetOffsets() } -> std::same_as<const mfem::Array<int> &>;
|
|
couplings.ApplyEnthalpyToDensity(enthalpy, densityAction);
|
|
couplings.ApplySurfaceToMaterial(surface, densityAction, enthalpyAction);
|
|
couplings.ApplyMaterialToSurface(density, enthalpy, surfaceAction);
|
|
};
|
|
|
|
template <
|
|
MaterialSurfaceFactorizationPolicy Policy,
|
|
MaterialSurfaceCouplingOperator CouplingOperator = MaterialSurfaceJacobianOperator>
|
|
class MaterialSurfaceFactorizationOperator final : public mfem::Solver {
|
|
public:
|
|
MaterialSurfaceFactorizationOperator(
|
|
Policy policy,
|
|
const mfem::Solver &densityInverse,
|
|
const mfem::Solver &surfaceInverse,
|
|
const mfem::Solver &enthalpyInverse,
|
|
const CouplingOperator &couplings
|
|
)
|
|
: mfem::Solver(couplings.Height()),
|
|
m_policy(std::move(policy)),
|
|
m_densityInverse(std::addressof(densityInverse)),
|
|
m_surfaceInverse(std::addressof(surfaceInverse)),
|
|
m_enthalpyInverse(std::addressof(enthalpyInverse)),
|
|
m_couplings(std::addressof(couplings)),
|
|
m_offsets(couplings.GetOffsets()),
|
|
m_densityWorkspace(densityInverse.Height()),
|
|
m_densityCoupling(densityInverse.Height()),
|
|
m_surfaceWorkspace(surfaceInverse.Height()),
|
|
m_enthalpyWorkspace(enthalpyInverse.Height()) {
|
|
if (densityInverse.Height() != densityInverse.Width() ||
|
|
surfaceInverse.Height() != surfaceInverse.Width() ||
|
|
enthalpyInverse.Height() != enthalpyInverse.Width() || densityInverse.Height() != m_offsets[1] ||
|
|
surfaceInverse.Height() != m_offsets[2] - m_offsets[1] ||
|
|
enthalpyInverse.Height() != m_offsets[3] - m_offsets[2]) {
|
|
throw std::invalid_argument("Material-surface inverse blocks do not match the coupled operator.");
|
|
}
|
|
}
|
|
|
|
void SetOperator(const mfem::Operator &operation) override {
|
|
if (operation.Height() != Height() || operation.Width() != Width()) {
|
|
throw std::invalid_argument("The material-surface factorization received an incompatible operator.");
|
|
}
|
|
}
|
|
|
|
void Mult(
|
|
const mfem::Vector &rightHandSide,
|
|
mfem::Vector &action
|
|
) const override {
|
|
if (rightHandSide.Size() != Width() || action.Size() != Height()) {
|
|
throw std::invalid_argument(
|
|
"The material-surface factorization requires compatible, preallocated vectors."
|
|
);
|
|
}
|
|
const auto densityRightHandSide = ConstBlock(rightHandSide, 0);
|
|
const auto surfaceRightHandSide = ConstBlock(rightHandSide, 1);
|
|
const auto enthalpyRightHandSide = ConstBlock(rightHandSide, 2);
|
|
auto densityAction = MutableBlock(action, 0);
|
|
auto surfaceAction = MutableBlock(action, 1);
|
|
auto enthalpyAction = MutableBlock(action, 2);
|
|
|
|
if constexpr (std::same_as<Policy, MaterialSurfaceBlockDiagonal>) {
|
|
m_densityInverse->Mult(densityRightHandSide, densityAction);
|
|
m_surfaceInverse->Mult(surfaceRightHandSide, surfaceAction);
|
|
m_enthalpyInverse->Mult(enthalpyRightHandSide, enthalpyAction);
|
|
} else if constexpr (std::same_as<Policy, SurfaceThenMaterialTriangular>) {
|
|
m_surfaceInverse->Mult(surfaceRightHandSide, surfaceAction);
|
|
m_couplings->ApplySurfaceToMaterial(surfaceAction, m_densityWorkspace, m_enthalpyWorkspace);
|
|
m_enthalpyWorkspace *= -1.0;
|
|
m_enthalpyWorkspace += enthalpyRightHandSide;
|
|
m_enthalpyInverse->Mult(m_enthalpyWorkspace, enthalpyAction);
|
|
m_couplings->ApplyEnthalpyToDensity(enthalpyAction, m_densityCoupling);
|
|
m_densityWorkspace += m_densityCoupling;
|
|
m_densityWorkspace *= -1.0;
|
|
m_densityWorkspace += densityRightHandSide;
|
|
m_densityInverse->Mult(m_densityWorkspace, densityAction);
|
|
++m_statistics.surfaceToMaterialApplications;
|
|
++m_statistics.enthalpyToDensityApplications;
|
|
} else if constexpr (std::same_as<Policy, ApproximateMaterialSurfaceLDU>) {
|
|
// Form m_0 = M^{-1} b_m with the upper-triangular material
|
|
// inverse, where m = (rho, h).
|
|
m_enthalpyInverse->Mult(enthalpyRightHandSide, enthalpyAction);
|
|
m_couplings->ApplyEnthalpyToDensity(enthalpyAction, m_densityWorkspace);
|
|
m_densityWorkspace *= -1.0;
|
|
m_densityWorkspace += densityRightHandSide;
|
|
m_densityInverse->Mult(m_densityWorkspace, densityAction);
|
|
|
|
// Apply the surface inverse to b_q - A_qm m_0. The surface
|
|
// surrogate is an approximation to the resulting Schur
|
|
// complement rather than merely to A_qq.
|
|
m_couplings->ApplyMaterialToSurface(densityAction, enthalpyAction, m_surfaceWorkspace);
|
|
m_surfaceWorkspace *= -1.0;
|
|
m_surfaceWorkspace += surfaceRightHandSide;
|
|
m_surfaceInverse->Mult(m_surfaceWorkspace, surfaceAction);
|
|
|
|
// Recover m = M^{-1}(b_m - A_mq q). Recomputing the material
|
|
// solve is algebraically equivalent to the conventional LDU
|
|
// correction m_0 - M^{-1} A_mq q.
|
|
m_couplings->ApplySurfaceToMaterial(surfaceAction, m_densityWorkspace, m_enthalpyWorkspace);
|
|
m_enthalpyWorkspace *= -1.0;
|
|
m_enthalpyWorkspace += enthalpyRightHandSide;
|
|
m_enthalpyInverse->Mult(m_enthalpyWorkspace, enthalpyAction);
|
|
m_couplings->ApplyEnthalpyToDensity(enthalpyAction, m_densityCoupling);
|
|
m_densityWorkspace += m_densityCoupling;
|
|
m_densityWorkspace *= -1.0;
|
|
m_densityWorkspace += densityRightHandSide;
|
|
m_densityInverse->Mult(m_densityWorkspace, densityAction);
|
|
|
|
++m_statistics.materialToSurfaceApplications;
|
|
++m_statistics.surfaceToMaterialApplications;
|
|
m_statistics.enthalpyToDensityApplications += 2;
|
|
++m_statistics.densityInverseApplications;
|
|
++m_statistics.enthalpyInverseApplications;
|
|
} else {
|
|
m_enthalpyInverse->Mult(enthalpyRightHandSide, enthalpyAction);
|
|
m_couplings->ApplyEnthalpyToDensity(enthalpyAction, m_densityWorkspace);
|
|
m_densityWorkspace *= -1.0;
|
|
m_densityWorkspace += densityRightHandSide;
|
|
m_densityInverse->Mult(m_densityWorkspace, densityAction);
|
|
++m_statistics.enthalpyToDensityApplications;
|
|
|
|
if constexpr (std::same_as<Policy, MaterialThenSurfaceTriangular>) {
|
|
m_couplings->ApplyMaterialToSurface(densityAction, enthalpyAction, m_surfaceWorkspace);
|
|
m_surfaceWorkspace *= -1.0;
|
|
m_surfaceWorkspace += surfaceRightHandSide;
|
|
m_surfaceInverse->Mult(m_surfaceWorkspace, surfaceAction);
|
|
++m_statistics.materialToSurfaceApplications;
|
|
} else {
|
|
static_assert(std::same_as<Policy, CoupledMaterialIndependentSurface>);
|
|
m_surfaceInverse->Mult(surfaceRightHandSide, surfaceAction);
|
|
}
|
|
}
|
|
|
|
++m_statistics.densityInverseApplications;
|
|
++m_statistics.surfaceInverseApplications;
|
|
++m_statistics.enthalpyInverseApplications;
|
|
++m_statistics.applications;
|
|
}
|
|
|
|
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept {
|
|
return m_offsets;
|
|
}
|
|
[[nodiscard]] const MaterialSurfaceFactorizationStatistics &GetStatistics() const noexcept {
|
|
return m_statistics;
|
|
}
|
|
|
|
private:
|
|
[[nodiscard]] mfem::Vector ConstBlock(
|
|
const mfem::Vector &vector,
|
|
int block
|
|
) const {
|
|
return mfem::Vector(
|
|
const_cast<mfem::real_t *>(vector.GetData()) + m_offsets[block], m_offsets[block + 1] - m_offsets[block]
|
|
);
|
|
}
|
|
[[nodiscard]] mfem::Vector MutableBlock(
|
|
mfem::Vector &vector,
|
|
int block
|
|
) const {
|
|
return mfem::Vector(vector.GetData() + m_offsets[block], m_offsets[block + 1] - m_offsets[block]);
|
|
}
|
|
|
|
Policy m_policy;
|
|
const mfem::Solver *m_densityInverse;
|
|
const mfem::Solver *m_surfaceInverse;
|
|
const mfem::Solver *m_enthalpyInverse;
|
|
const CouplingOperator *m_couplings;
|
|
mfem::Array<int> m_offsets;
|
|
mutable mfem::Vector m_densityWorkspace;
|
|
mutable mfem::Vector m_densityCoupling;
|
|
mutable mfem::Vector m_surfaceWorkspace;
|
|
mutable mfem::Vector m_enthalpyWorkspace;
|
|
mutable MaterialSurfaceFactorizationStatistics m_statistics;
|
|
};
|
|
|
|
struct DiagonalPreparationQuality final {
|
|
double minimumAbsoluteEntryBeforeRegularization{0.0};
|
|
double maximumAbsoluteEntryBeforeRegularization{0.0};
|
|
double appliedFloor{0.0};
|
|
std::uint64_t regularizedEntries{0};
|
|
};
|
|
|
|
struct SurfaceRieszCalibrationReport final {
|
|
SurfaceRieszCalibrationTarget target{SurfaceRieszCalibrationTarget::none};
|
|
int probeCount{0};
|
|
SurfaceRieszCalibrationObjective objective{SurfaceRieszCalibrationObjective::operator_action};
|
|
double leastSquaresNumerator{0.0};
|
|
double leastSquaresDenominator{0.0};
|
|
double scale{1.0};
|
|
double inverseMultiplier{1.0};
|
|
|
|
[[nodiscard]] bool WasCalibrated() const noexcept {
|
|
return target != SurfaceRieszCalibrationTarget::none;
|
|
}
|
|
};
|
|
|
|
namespace detail {
|
|
struct SurfaceRieszScalarFit final {
|
|
double surrogateScale{1.0};
|
|
double inverseMultiplier{1.0};
|
|
};
|
|
|
|
[[nodiscard]] inline SurfaceRieszScalarFit fitSurfaceRieszScalar(
|
|
const double leastSquaresNumerator,
|
|
const double leastSquaresDenominator,
|
|
const SurfaceRieszCalibrationObjective objective
|
|
) {
|
|
if (!std::isfinite(leastSquaresNumerator) || !std::isfinite(leastSquaresDenominator) ||
|
|
leastSquaresDenominator <= 0.0) {
|
|
throw std::invalid_argument("Surface Riesz scalar calibration requires finite, nondegenerate data.");
|
|
}
|
|
const double fittedMultiplier = leastSquaresNumerator / leastSquaresDenominator;
|
|
if (!std::isfinite(fittedMultiplier) || fittedMultiplier == 0.0) {
|
|
throw std::runtime_error("Surface Riesz scalar calibration produced a zero or non-finite multiplier.");
|
|
}
|
|
const double surrogateScale = objective == SurfaceRieszCalibrationObjective::operator_action
|
|
? fittedMultiplier
|
|
: 1.0 / fittedMultiplier;
|
|
if (!std::isfinite(surrogateScale) || surrogateScale == 0.0) {
|
|
throw std::runtime_error("Surface Riesz scalar calibration produced a zero or non-finite scale.");
|
|
}
|
|
return {.surrogateScale = surrogateScale, .inverseMultiplier = 1.0 / surrogateScale};
|
|
}
|
|
} // namespace detail
|
|
|
|
struct MaterialSurfaceBlockPreparationReport final {
|
|
bool linearizationChanged{false};
|
|
bool rebuiltDensityInverse{false};
|
|
bool rebuiltSurfaceInverse{false};
|
|
bool rebuiltEnthalpyInverse{false};
|
|
DiagonalPreparationQuality densityDiagonal;
|
|
DiagonalPreparationQuality surfaceDiagonal;
|
|
DiagonalPreparationQuality enthalpyDiagonal;
|
|
|
|
[[nodiscard]] bool DidAnyWork() const noexcept {
|
|
return rebuiltDensityInverse || rebuiltSurfaceInverse || rebuiltEnthalpyInverse;
|
|
}
|
|
};
|
|
|
|
struct PreparedMaterialSurfaceBlockStatistics final {
|
|
std::uint64_t setups{0};
|
|
std::uint64_t refreshChecks{0};
|
|
std::uint64_t refreshes{0};
|
|
std::uint64_t noOpRefreshes{0};
|
|
std::uint64_t surfaceJacobianProbes{0};
|
|
std::uint64_t surfaceRieszAssemblies{0};
|
|
std::uint64_t surfaceH1Assemblies{0};
|
|
};
|
|
|
|
template <MaterialSurfaceDescriptor Descriptor, MaterialSurfaceFactorizationPolicy Policy>
|
|
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
|
class PreparedMaterialSurfaceBlock final : public mfem::Solver {
|
|
public:
|
|
using Block = MaterialSurfaceBlock<Descriptor, backend::Diagonal, backend::Diagonal, Policy>;
|
|
|
|
PreparedMaterialSurfaceBlock(
|
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
|
Block block
|
|
)
|
|
: mfem::Solver(MaterialSurfaceJacobianOperator(operation).Height()),
|
|
m_block(std::move(block)),
|
|
m_operation(std::addressof(operation)),
|
|
m_couplings(operation),
|
|
m_densityDiagonal(AssembleDensityDiagonal(operation)),
|
|
m_surfaceDiagonal(AssembleSurfaceRieszDiagonal(operation)),
|
|
m_enthalpyDiagonal(AssembleEnthalpyDiagonal(operation)),
|
|
m_densityQuality(Regularize(
|
|
m_densityDiagonal,
|
|
m_block.diagonalOptions(),
|
|
Communicator(operation)
|
|
)),
|
|
m_surfaceQuality(Regularize(
|
|
m_surfaceDiagonal,
|
|
m_block.diagonalOptions(),
|
|
Communicator(operation)
|
|
)),
|
|
m_enthalpyQuality(Regularize(
|
|
m_enthalpyDiagonal,
|
|
m_block.diagonalOptions(),
|
|
Communicator(operation)
|
|
)),
|
|
m_densityInverse(
|
|
m_block.materialBackend(),
|
|
m_densityDiagonal
|
|
),
|
|
m_surfaceInverse(
|
|
m_block.surfaceBackend(),
|
|
m_surfaceDiagonal
|
|
),
|
|
m_enthalpyInverse(
|
|
m_block.materialBackend(),
|
|
m_enthalpyDiagonal
|
|
),
|
|
m_factorization(
|
|
m_block.factorizationPolicy(),
|
|
m_densityInverse,
|
|
m_surfaceInverse,
|
|
m_enthalpyInverse,
|
|
m_couplings
|
|
),
|
|
m_dependencies(operation.GetDependencies()) {
|
|
m_statistics.setups = 1;
|
|
m_statistics.surfaceRieszAssemblies = 1;
|
|
m_surfaceCalibration = CalibrateSurfaceRiesz();
|
|
}
|
|
|
|
PreparedMaterialSurfaceBlock(const PreparedMaterialSurfaceBlock &) = delete;
|
|
PreparedMaterialSurfaceBlock &operator=(const PreparedMaterialSurfaceBlock &) = delete;
|
|
PreparedMaterialSurfaceBlock(PreparedMaterialSurfaceBlock &&) = delete;
|
|
PreparedMaterialSurfaceBlock &operator=(PreparedMaterialSurfaceBlock &&) = delete;
|
|
|
|
void SetOperator(const mfem::Operator &operation) override {
|
|
m_factorization.SetOperator(operation);
|
|
}
|
|
|
|
void Mult(
|
|
const mfem::Vector &rightHandSide,
|
|
mfem::Vector &action
|
|
) const override {
|
|
if (!IsCurrent()) {
|
|
throw std::logic_error("The material-surface block is stale; refresh it before application.");
|
|
}
|
|
m_factorization.Mult(rightHandSide, action);
|
|
}
|
|
|
|
[[nodiscard]] bool IsCurrent() const noexcept {
|
|
return m_operation->IsPrepared() && m_operation->GetDependencies() == m_dependencies;
|
|
}
|
|
|
|
[[nodiscard]] MaterialSurfaceBlockPreparationReport
|
|
Refresh(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
if (std::addressof(operation) != m_operation) {
|
|
throw std::invalid_argument("A material-surface block cannot change stellar-operator identity.");
|
|
}
|
|
if (!operation.IsPrepared()) {
|
|
throw std::logic_error("A material-surface block cannot refresh from an unprepared operator.");
|
|
}
|
|
++m_statistics.refreshChecks;
|
|
MaterialSurfaceBlockPreparationReport report{
|
|
.linearizationChanged = operation.GetDependencies() != m_dependencies
|
|
};
|
|
if (!report.linearizationChanged) {
|
|
++m_statistics.noOpRefreshes;
|
|
return report;
|
|
}
|
|
|
|
const operators::StellarEquilibriumDependencies currentDependencies = operation.GetDependencies();
|
|
const bool geometryChanged = currentDependencies.discretization != m_dependencies.discretization ||
|
|
currentDependencies.surfaceDeformation != m_dependencies.surfaceDeformation;
|
|
const bool calibratedSurface =
|
|
m_block.diagonalOptions().surfaceCalibration.target != SurfaceRieszCalibrationTarget::none;
|
|
|
|
if (geometryChanged || calibratedSurface) {
|
|
m_densityDiagonal = AssembleDensityDiagonal(operation);
|
|
m_surfaceDiagonal = AssembleSurfaceRieszDiagonal(operation);
|
|
++m_statistics.surfaceRieszAssemblies;
|
|
m_enthalpyDiagonal = AssembleEnthalpyDiagonal(operation);
|
|
m_densityQuality = Regularize(m_densityDiagonal, m_block.diagonalOptions(), Communicator(operation));
|
|
m_surfaceQuality = Regularize(m_surfaceDiagonal, m_block.diagonalOptions(), Communicator(operation));
|
|
m_enthalpyQuality = Regularize(m_enthalpyDiagonal, m_block.diagonalOptions(), Communicator(operation));
|
|
m_densityInverse.Refresh(m_densityDiagonal);
|
|
// Calibration always starts from the newly assembled,
|
|
// unscaled surface mass inverse. This is required by the
|
|
// right-preconditioned objective and also prevents repeated
|
|
// refreshes from compounding the previous calibration scale.
|
|
m_surfaceInverse.Refresh(m_surfaceDiagonal);
|
|
m_enthalpyInverse.Refresh(m_enthalpyDiagonal);
|
|
if (calibratedSurface) {
|
|
m_surfaceCalibration = CalibrateSurfaceRiesz();
|
|
}
|
|
report.rebuiltDensityInverse = true;
|
|
report.rebuiltSurfaceInverse = true;
|
|
report.rebuiltEnthalpyInverse = true;
|
|
report.densityDiagonal = m_densityQuality;
|
|
report.surfaceDiagonal = m_surfaceQuality;
|
|
report.enthalpyDiagonal = m_enthalpyQuality;
|
|
++m_statistics.refreshes;
|
|
} else {
|
|
++m_statistics.noOpRefreshes;
|
|
}
|
|
|
|
m_dependencies = currentDependencies;
|
|
return report;
|
|
}
|
|
|
|
[[nodiscard]] const MaterialSurfaceJacobianOperator &GetCoupledOperator() const noexcept {
|
|
return m_couplings;
|
|
}
|
|
[[nodiscard]] const MaterialSurfaceFactorizationOperator<Policy> &GetFactorization() const noexcept {
|
|
return m_factorization;
|
|
}
|
|
[[nodiscard]] const mfem::Vector &GetDensityDiagonal() const noexcept {
|
|
return m_densityDiagonal;
|
|
}
|
|
[[nodiscard]] const mfem::Vector &GetSurfaceDiagonal() const noexcept {
|
|
return m_surfaceDiagonal;
|
|
}
|
|
[[nodiscard]] const mfem::Vector &GetEnthalpyDiagonal() const noexcept {
|
|
return m_enthalpyDiagonal;
|
|
}
|
|
[[nodiscard]] const DiagonalPreparationQuality &GetDensityDiagonalQuality() const noexcept {
|
|
return m_densityQuality;
|
|
}
|
|
[[nodiscard]] const DiagonalPreparationQuality &GetSurfaceDiagonalQuality() const noexcept {
|
|
return m_surfaceQuality;
|
|
}
|
|
[[nodiscard]] const SurfaceRieszCalibrationReport &GetSurfaceCalibration() const noexcept {
|
|
return m_surfaceCalibration;
|
|
}
|
|
[[nodiscard]] const DiagonalPreparationQuality &GetEnthalpyDiagonalQuality() const noexcept {
|
|
return m_enthalpyQuality;
|
|
}
|
|
[[nodiscard]] const PreparedMaterialSurfaceBlockStatistics &GetStatistics() const noexcept {
|
|
return m_statistics;
|
|
}
|
|
|
|
private:
|
|
[[nodiscard]] static MPI_Comm Communicator(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
return operation.GetHydrostaticOperator().GetFEM().mesh->GetComm();
|
|
}
|
|
[[nodiscard]] static mfem::Vector
|
|
AssembleDensityDiagonal(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
mfem::Vector diagonal;
|
|
operation.GetBarotropicClosureOperator().AssembleDensityJacobianDiagonal(diagonal);
|
|
return diagonal;
|
|
}
|
|
[[nodiscard]] static mfem::Vector
|
|
AssembleEnthalpyDiagonal(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
mfem::Vector diagonal;
|
|
operation.GetHydrostaticOperator().AssembleEnthalpyJacobianDiagonal(diagonal);
|
|
mfem::Vector ones(diagonal.Size());
|
|
ones = 1.0;
|
|
operation.GetSurfaceConstraintOperator().ApplyJacobianRows(ones, diagonal);
|
|
return diagonal;
|
|
}
|
|
[[nodiscard]] static mfem::Vector
|
|
AssembleSurfaceRieszDiagonal(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
|
|
|
const fem::FEM &finiteElements = operation.GetHydrostaticOperator().GetFEM();
|
|
MFEM_VERIFY(
|
|
finiteElements.mesh != nullptr && finiteElements.surfaceDeformationFes != nullptr,
|
|
"The material/free-surface Riesz surrogate requires the surface finite-element space."
|
|
);
|
|
|
|
mfem::Array<int> stellarSurfaceMarker(finiteElements.mesh->bdr_attributes.Max());
|
|
stellarSurfaceMarker = 0;
|
|
constexpr int stellarSurfaceAttribute =
|
|
DomainSchema::template boundary_attribute<utils::domain::StellarSurface>();
|
|
MFEM_VERIFY(
|
|
stellarSurfaceAttribute > 0 && stellarSurfaceAttribute <= stellarSurfaceMarker.Size(),
|
|
"The stellar-surface boundary attribute is absent from the finite-element mesh."
|
|
);
|
|
stellarSurfaceMarker[stellarSurfaceAttribute - 1] = 1;
|
|
|
|
mfem::ParBilinearForm surfaceRiesz(finiteElements.surfaceDeformationFes.get());
|
|
surfaceRiesz.AddBoundaryIntegrator(new mfem::MassIntegrator(), stellarSurfaceMarker);
|
|
surfaceRiesz.Assemble();
|
|
surfaceRiesz.Finalize();
|
|
|
|
std::unique_ptr<mfem::HypreParMatrix> surfaceRieszMatrix(surfaceRiesz.ParallelAssemble());
|
|
MFEM_VERIFY(surfaceRieszMatrix != nullptr, "The stellar-surface Riesz surrogate failed to assemble.");
|
|
mfem::Vector ambientDiagonal;
|
|
surfaceRieszMatrix->GetDiag(ambientDiagonal);
|
|
|
|
const field::ScalarBoundaryDofMap surfaceMap =
|
|
field::make_stellar_surface_scalar_dof_map<DomainSchema>(*finiteElements.surfaceDeformationFes);
|
|
mfem::Vector diagonal = surfaceMap.gather(ambientDiagonal);
|
|
MFEM_VERIFY(
|
|
diagonal.Size() == operation.GetDomainDeformation().parameterCount(),
|
|
"The stellar-surface Riesz diagonal does not match the deformation parameter space."
|
|
);
|
|
return diagonal;
|
|
}
|
|
|
|
[[nodiscard]] SurfaceRieszCalibrationReport CalibrateSurfaceRiesz() {
|
|
const SurfaceRieszCalibrationOptions calibration = m_block.diagonalOptions().surfaceCalibration;
|
|
if (calibration.target == SurfaceRieszCalibrationTarget::none) {
|
|
if (calibration.probeCount != 0 ||
|
|
calibration.objective != SurfaceRieszCalibrationObjective::operator_action) {
|
|
throw std::invalid_argument(
|
|
"An uncalibrated surface Riesz surrogate must request zero probes and the default objective."
|
|
);
|
|
}
|
|
return {};
|
|
}
|
|
if (calibration.probeCount <= 0) {
|
|
throw std::invalid_argument("Surface Riesz calibration requires at least one deterministic probe.");
|
|
}
|
|
|
|
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.");
|
|
}
|
|
|
|
mfem::Vector probe(m_surfaceDiagonal.Size());
|
|
mfem::Vector rieszAction(m_surfaceDiagonal.Size());
|
|
mfem::Vector targetAction(m_surfaceDiagonal.Size());
|
|
mfem::Vector baseInverseAction(m_surfaceDiagonal.Size());
|
|
mfem::Vector materialFeedback(m_surfaceDiagonal.Size());
|
|
mfem::Vector densityRightHandSide(m_densityDiagonal.Size());
|
|
mfem::Vector densityCorrection(m_densityDiagonal.Size());
|
|
mfem::Vector densityCoupling(m_densityDiagonal.Size());
|
|
mfem::Vector enthalpyRightHandSide(m_enthalpyDiagonal.Size());
|
|
mfem::Vector enthalpyCorrection(m_enthalpyDiagonal.Size());
|
|
|
|
double localNumerator = 0.0;
|
|
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));
|
|
value += 0x9e3779b97f4a7c15ULL * static_cast<std::uint64_t>(sample + 1);
|
|
value = (value ^ (value >> 30U)) * 0xbf58476d1ce4e5b9ULL;
|
|
value = (value ^ (value >> 27U)) * 0x94d049bb133111ebULL;
|
|
value ^= value >> 31U;
|
|
probe(index) = (value & 1ULL) == 0ULL ? -1.0 : 1.0;
|
|
rieszAction(index) = m_surfaceDiagonal(index) * probe(index);
|
|
}
|
|
|
|
if (calibration.objective == SurfaceRieszCalibrationObjective::right_preconditioned_action) {
|
|
m_surfaceInverse.Mult(probe, baseInverseAction);
|
|
}
|
|
const mfem::Vector &targetDirection =
|
|
calibration.objective == SurfaceRieszCalibrationObjective::operator_action ? probe
|
|
: baseInverseAction;
|
|
m_couplings.ApplySurfaceToSurface(targetDirection, targetAction);
|
|
if (calibration.target == SurfaceRieszCalibrationTarget::approximate_material_schur) {
|
|
m_couplings.ApplySurfaceToMaterial(targetDirection, densityRightHandSide, enthalpyRightHandSide);
|
|
m_enthalpyInverse.Mult(enthalpyRightHandSide, enthalpyCorrection);
|
|
m_couplings.ApplyEnthalpyToDensity(enthalpyCorrection, densityCoupling);
|
|
densityRightHandSide -= densityCoupling;
|
|
m_densityInverse.Mult(densityRightHandSide, densityCorrection);
|
|
m_couplings.ApplyMaterialToSurface(densityCorrection, enthalpyCorrection, materialFeedback);
|
|
targetAction -= materialFeedback;
|
|
}
|
|
|
|
if (calibration.objective == SurfaceRieszCalibrationObjective::operator_action) {
|
|
// Fit s M q ~= T q. The stored surface surrogate is s M.
|
|
localNumerator += rieszAction * targetAction;
|
|
localDenominator += rieszAction * rieszAction;
|
|
} else {
|
|
// Fit alpha T M^{-1} q ~= q. Since the stored surrogate is
|
|
// s M, its inverse multiplier is alpha = 1 / s. This is the
|
|
// surface factor in the right-preconditioned product.
|
|
localNumerator += targetAction * probe;
|
|
localDenominator += targetAction * targetAction;
|
|
}
|
|
}
|
|
|
|
const MPI_Comm communicator = Communicator(*m_operation);
|
|
double globalNumerator = 0.0;
|
|
double globalDenominator = 0.0;
|
|
MPI_Allreduce(&localNumerator, &globalNumerator, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
|
MPI_Allreduce(&localDenominator, &globalDenominator, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
|
if (!std::isfinite(globalNumerator) || !std::isfinite(globalDenominator) || globalDenominator <= 0.0) {
|
|
throw std::runtime_error("Surface Riesz calibration produced an invalid least-squares problem.");
|
|
}
|
|
const detail::SurfaceRieszScalarFit fit =
|
|
detail::fitSurfaceRieszScalar(globalNumerator, globalDenominator, calibration.objective);
|
|
|
|
m_surfaceDiagonal *= fit.surrogateScale;
|
|
m_surfaceQuality = Regularize(m_surfaceDiagonal, m_block.diagonalOptions(), communicator);
|
|
m_surfaceInverse.Refresh(m_surfaceDiagonal);
|
|
m_statistics.surfaceJacobianProbes += static_cast<std::uint64_t>(calibration.probeCount);
|
|
return {
|
|
.target = calibration.target,
|
|
.probeCount = calibration.probeCount,
|
|
.objective = calibration.objective,
|
|
.leastSquaresNumerator = globalNumerator,
|
|
.leastSquaresDenominator = globalDenominator,
|
|
.scale = fit.surrogateScale,
|
|
.inverseMultiplier = fit.inverseMultiplier
|
|
};
|
|
}
|
|
|
|
[[nodiscard]] static DiagonalPreparationQuality Regularize(
|
|
mfem::Vector &diagonal,
|
|
const MaterialSurfaceDiagonalOptions options,
|
|
const MPI_Comm communicator
|
|
) {
|
|
if (!std::isfinite(options.relativeFloor) || options.relativeFloor < 0.0 ||
|
|
!std::isfinite(options.absoluteFloor) || options.absoluteFloor <= 0.0) {
|
|
throw std::invalid_argument("Material-surface diagonal floors must be finite and nonnegative.");
|
|
}
|
|
double localMaximum = 0.0;
|
|
double localMinimum = std::numeric_limits<double>::infinity();
|
|
for (int index = 0; index < diagonal.Size(); ++index) {
|
|
if (!std::isfinite(diagonal(index))) {
|
|
throw std::invalid_argument("A material-surface diagonal contains a non-finite entry.");
|
|
}
|
|
const double magnitude = std::abs(diagonal(index));
|
|
localMaximum = std::max(localMaximum, magnitude);
|
|
localMinimum = std::min(localMinimum, magnitude);
|
|
}
|
|
double globalMaximum = 0.0;
|
|
double globalMinimum = 0.0;
|
|
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
|
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
|
|
const double floor = std::max(options.absoluteFloor, options.relativeFloor * globalMaximum);
|
|
std::uint64_t localRegularized = 0;
|
|
for (int index = 0; index < diagonal.Size(); ++index) {
|
|
if (std::abs(diagonal(index)) < floor) {
|
|
diagonal(index) = std::copysign(floor, diagonal(index) == 0.0 ? 1.0 : diagonal(index));
|
|
++localRegularized;
|
|
}
|
|
}
|
|
std::uint64_t globalRegularized = 0;
|
|
MPI_Allreduce(&localRegularized, &globalRegularized, 1, MPI_UINT64_T, MPI_SUM, communicator);
|
|
return {
|
|
.minimumAbsoluteEntryBeforeRegularization = globalMinimum,
|
|
.maximumAbsoluteEntryBeforeRegularization = globalMaximum,
|
|
.appliedFloor = floor,
|
|
.regularizedEntries = globalRegularized
|
|
};
|
|
}
|
|
|
|
Block m_block;
|
|
const operators::PreparedStellarEquilibriumOperator *m_operation;
|
|
MaterialSurfaceJacobianOperator m_couplings;
|
|
mfem::Vector m_densityDiagonal;
|
|
mfem::Vector m_surfaceDiagonal;
|
|
mfem::Vector m_enthalpyDiagonal;
|
|
DiagonalPreparationQuality m_densityQuality;
|
|
DiagonalPreparationQuality m_surfaceQuality;
|
|
DiagonalPreparationQuality m_enthalpyQuality;
|
|
SurfaceRieszCalibrationReport m_surfaceCalibration;
|
|
backend::PreparedDiagonal m_densityInverse;
|
|
backend::PreparedDiagonal m_surfaceInverse;
|
|
backend::PreparedDiagonal m_enthalpyInverse;
|
|
MaterialSurfaceFactorizationOperator<Policy> m_factorization;
|
|
operators::StellarEquilibriumDependencies m_dependencies;
|
|
PreparedMaterialSurfaceBlockStatistics m_statistics;
|
|
};
|
|
|
|
// The AMG backend acts on the ambient scalar H1 true-DOF space. Surface
|
|
// deformation parameters, however, contain only the locally owned trace
|
|
// DOFs. This adapter is the sole conversion point between those two
|
|
// coordinate systems. The sign is deliberately applied outside AMG so the
|
|
// sparse surrogate supplied to BoomerAMG remains positive definite.
|
|
class SignedScalarBoundarySolverAdapter final : public mfem::Solver {
|
|
public:
|
|
SignedScalarBoundarySolverAdapter(
|
|
const mfem::Solver &ambientSolver,
|
|
field::ScalarBoundaryDofMap surfaceMap,
|
|
const double sign
|
|
)
|
|
: mfem::Solver(surfaceMap.local_size()),
|
|
m_ambientSolver(std::addressof(ambientSolver)),
|
|
m_surfaceMap(std::move(surfaceMap)),
|
|
m_ambientRightHandSide(m_surfaceMap.volume_true_dof_size()),
|
|
m_ambientAction(m_surfaceMap.volume_true_dof_size()) {
|
|
if (ambientSolver.Height() != m_surfaceMap.volume_true_dof_size() ||
|
|
ambientSolver.Width() != m_surfaceMap.volume_true_dof_size()) {
|
|
throw std::invalid_argument("The surface AMG solver is incompatible with its ambient H1 space.");
|
|
}
|
|
SetSign(sign);
|
|
}
|
|
|
|
void SetOperator(const mfem::Operator &operation) override {
|
|
if (operation.Height() != Height() || operation.Width() != Width()) {
|
|
throw std::invalid_argument("The surface trace solver received an operator of incompatible size.");
|
|
}
|
|
}
|
|
|
|
void SetSign(const double sign) {
|
|
if (sign != -1.0 && sign != 1.0) {
|
|
throw std::invalid_argument("The fitted surface-operator sign must be exactly -1 or +1.");
|
|
}
|
|
m_sign = sign;
|
|
}
|
|
|
|
void Mult(
|
|
const mfem::Vector &rightHandSide,
|
|
mfem::Vector &action
|
|
) const override {
|
|
if (rightHandSide.Size() != Width() || action.Size() != Height()) {
|
|
throw std::invalid_argument("The surface trace solver received incompatible vectors.");
|
|
}
|
|
m_surfaceMap.scatter(rightHandSide, m_ambientRightHandSide);
|
|
m_ambientSolver->Mult(m_ambientRightHandSide, m_ambientAction);
|
|
m_surfaceMap.gather(m_ambientAction, action);
|
|
action *= m_sign;
|
|
}
|
|
|
|
[[nodiscard]] double GetSign() const noexcept {
|
|
return m_sign;
|
|
}
|
|
|
|
[[nodiscard]] const field::ScalarBoundaryDofMap &GetSurfaceMap() const noexcept {
|
|
return m_surfaceMap;
|
|
}
|
|
|
|
private:
|
|
const mfem::Solver *m_ambientSolver;
|
|
field::ScalarBoundaryDofMap m_surfaceMap;
|
|
double m_sign{1.0};
|
|
mutable mfem::Vector m_ambientRightHandSide;
|
|
mutable mfem::Vector m_ambientAction;
|
|
};
|
|
|
|
template <
|
|
MaterialSurfaceDescriptor Descriptor,
|
|
MaterialSurfaceFactorizationPolicy Policy,
|
|
backend::ApplicationMode Mode>
|
|
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
|
class PreparedH1MaterialSurfaceBlock final : public mfem::Solver {
|
|
public:
|
|
using SurfaceBackend = backend::HypreBoomerAMG<Mode>;
|
|
using Block =
|
|
MaterialSurfaceBlock<Descriptor, backend::Diagonal, SurfaceBackend, Policy, SurfaceH1MassStiffness>;
|
|
|
|
PreparedH1MaterialSurfaceBlock(
|
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
|
Block block
|
|
)
|
|
: mfem::Solver(MaterialSurfaceJacobianOperator(operation).Height()),
|
|
m_block(std::move(block)),
|
|
m_operation(std::addressof(operation)),
|
|
m_couplings(operation),
|
|
m_surfaceMap(SurfaceMap(operation)),
|
|
m_densityDiagonal(AssembleDensityDiagonal(operation)),
|
|
m_enthalpyDiagonal(AssembleEnthalpyDiagonal(operation)),
|
|
m_dependencies(operation.GetDependencies()) {
|
|
ValidateConfiguration(m_block);
|
|
m_densityQuality = Regularize(m_densityDiagonal, m_block.diagonalOptions(), Communicator(operation));
|
|
m_enthalpyQuality = Regularize(m_enthalpyDiagonal, m_block.diagonalOptions(), Communicator(operation));
|
|
m_densityInverse =
|
|
std::make_unique<backend::PreparedDiagonal>(m_block.materialBackend(), m_densityDiagonal);
|
|
m_enthalpyInverse =
|
|
std::make_unique<backend::PreparedDiagonal>(m_block.materialBackend(), m_enthalpyDiagonal);
|
|
|
|
m_surfaceMass = AssembleSurfaceOperator(operation, 1.0, 0.0, false);
|
|
m_surfaceStiffness = AssembleSurfaceOperator(operation, 0.0, 1.0, false);
|
|
m_surfaceFit = FitSurfaceOperator();
|
|
m_surfaceSurrogate = AssembleSurfaceOperator(
|
|
operation, m_surfaceFit.massCoefficient, m_surfaceFit.stiffnessCoefficient, true
|
|
);
|
|
m_surfaceInverse =
|
|
std::make_unique<backend::PreparedHypreBoomerAMG<Mode>>(m_block.surfaceBackend(), *m_surfaceSurrogate);
|
|
RebindFactorization();
|
|
|
|
m_statistics.setups = 1;
|
|
m_statistics.surfaceJacobianProbes =
|
|
static_cast<std::uint64_t>(m_block.surfaceSurrogate().calibration.probeCount);
|
|
m_statistics.surfaceH1Assemblies = 3;
|
|
}
|
|
|
|
PreparedH1MaterialSurfaceBlock(const PreparedH1MaterialSurfaceBlock &) = delete;
|
|
PreparedH1MaterialSurfaceBlock &operator=(const PreparedH1MaterialSurfaceBlock &) = delete;
|
|
PreparedH1MaterialSurfaceBlock(PreparedH1MaterialSurfaceBlock &&) = delete;
|
|
PreparedH1MaterialSurfaceBlock &operator=(PreparedH1MaterialSurfaceBlock &&) = delete;
|
|
|
|
void SetOperator(const mfem::Operator &operation) override {
|
|
m_factorization->SetOperator(operation);
|
|
}
|
|
|
|
void Mult(
|
|
const mfem::Vector &rightHandSide,
|
|
mfem::Vector &action
|
|
) const override {
|
|
if (!IsCurrent()) {
|
|
throw std::logic_error("The H1 material-surface block is stale; refresh it before application.");
|
|
}
|
|
m_factorization->Mult(rightHandSide, action);
|
|
}
|
|
|
|
[[nodiscard]] bool IsCurrent() const noexcept {
|
|
return m_operation->IsPrepared() && m_operation->GetDependencies() == m_dependencies;
|
|
}
|
|
|
|
[[nodiscard]] MaterialSurfaceBlockPreparationReport
|
|
Refresh(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
if (std::addressof(operation) != m_operation) {
|
|
throw std::invalid_argument("An H1 material-surface block cannot change stellar-operator identity.");
|
|
}
|
|
if (!operation.IsPrepared()) {
|
|
throw std::logic_error("An H1 material-surface block cannot refresh from an unprepared operator.");
|
|
}
|
|
++m_statistics.refreshChecks;
|
|
MaterialSurfaceBlockPreparationReport report{
|
|
.linearizationChanged = operation.GetDependencies() != m_dependencies
|
|
};
|
|
if (!report.linearizationChanged) {
|
|
++m_statistics.noOpRefreshes;
|
|
return report;
|
|
}
|
|
|
|
const operators::StellarEquilibriumDependencies currentDependencies = operation.GetDependencies();
|
|
const bool geometryChanged = currentDependencies.discretization != m_dependencies.discretization ||
|
|
currentDependencies.surfaceDeformation != m_dependencies.surfaceDeformation;
|
|
|
|
m_densityDiagonal = AssembleDensityDiagonal(operation);
|
|
m_enthalpyDiagonal = AssembleEnthalpyDiagonal(operation);
|
|
m_densityQuality = Regularize(m_densityDiagonal, m_block.diagonalOptions(), Communicator(operation));
|
|
m_enthalpyQuality = Regularize(m_enthalpyDiagonal, m_block.diagonalOptions(), Communicator(operation));
|
|
m_densityInverse->Refresh(m_densityDiagonal);
|
|
m_enthalpyInverse->Refresh(m_enthalpyDiagonal);
|
|
|
|
if (geometryChanged) {
|
|
field::ScalarBoundaryDofMap currentSurfaceMap = SurfaceMap(operation);
|
|
if (currentSurfaceMap.local_size() != m_surfaceMap.local_size() ||
|
|
currentSurfaceMap.volume_true_dof_size() != m_surfaceMap.volume_true_dof_size()) {
|
|
throw std::invalid_argument(
|
|
"An H1 material-surface block cannot change discretization size during refresh."
|
|
);
|
|
}
|
|
m_surfaceMap = std::move(currentSurfaceMap);
|
|
m_surfaceMass = AssembleSurfaceOperator(operation, 1.0, 0.0, false);
|
|
m_surfaceStiffness = AssembleSurfaceOperator(operation, 0.0, 1.0, false);
|
|
m_statistics.surfaceH1Assemblies += 2;
|
|
}
|
|
|
|
m_surfaceFit = FitSurfaceOperator();
|
|
auto refreshedSurrogate = AssembleSurfaceOperator(
|
|
operation, m_surfaceFit.massCoefficient, m_surfaceFit.stiffnessCoefficient, true
|
|
);
|
|
m_surfaceInverse->Refresh(*refreshedSurrogate);
|
|
m_surfaceSurrogate = std::move(refreshedSurrogate);
|
|
RebindFactorization();
|
|
|
|
m_statistics.surfaceJacobianProbes +=
|
|
static_cast<std::uint64_t>(m_block.surfaceSurrogate().calibration.probeCount);
|
|
++m_statistics.surfaceH1Assemblies;
|
|
++m_statistics.refreshes;
|
|
report.rebuiltDensityInverse = true;
|
|
report.rebuiltSurfaceInverse = true;
|
|
report.rebuiltEnthalpyInverse = true;
|
|
report.densityDiagonal = m_densityQuality;
|
|
report.enthalpyDiagonal = m_enthalpyQuality;
|
|
m_dependencies = currentDependencies;
|
|
return report;
|
|
}
|
|
|
|
[[nodiscard]] const MaterialSurfaceJacobianOperator &GetCoupledOperator() const noexcept {
|
|
return m_couplings;
|
|
}
|
|
[[nodiscard]] const MaterialSurfaceFactorizationOperator<Policy> &GetFactorization() const noexcept {
|
|
return *m_factorization;
|
|
}
|
|
[[nodiscard]] const SurfaceH1FitReport &GetSurfaceFit() const noexcept {
|
|
return m_surfaceFit;
|
|
}
|
|
[[nodiscard]] const mfem::HypreParMatrix &GetSurfaceMassMatrix() const noexcept {
|
|
return *m_surfaceMass;
|
|
}
|
|
[[nodiscard]] const mfem::HypreParMatrix &GetSurfaceStiffnessMatrix() const noexcept {
|
|
return *m_surfaceStiffness;
|
|
}
|
|
[[nodiscard]] const mfem::HypreParMatrix &GetSurfaceSurrogateMatrix() const noexcept {
|
|
return *m_surfaceSurrogate;
|
|
}
|
|
[[nodiscard]] const SignedScalarBoundarySolverAdapter &GetSurfaceInverse() const noexcept {
|
|
return *m_surfaceBoundaryInverse;
|
|
}
|
|
[[nodiscard]] const backend::PreparedHypreBoomerAMG<Mode> &GetSurfaceBackend() const noexcept {
|
|
return *m_surfaceInverse;
|
|
}
|
|
[[nodiscard]] const mfem::Vector &GetDensityDiagonal() const noexcept {
|
|
return m_densityDiagonal;
|
|
}
|
|
[[nodiscard]] const mfem::Vector &GetEnthalpyDiagonal() const noexcept {
|
|
return m_enthalpyDiagonal;
|
|
}
|
|
[[nodiscard]] const DiagonalPreparationQuality &GetDensityDiagonalQuality() const noexcept {
|
|
return m_densityQuality;
|
|
}
|
|
[[nodiscard]] const DiagonalPreparationQuality &GetEnthalpyDiagonalQuality() const noexcept {
|
|
return m_enthalpyQuality;
|
|
}
|
|
[[nodiscard]] const PreparedMaterialSurfaceBlockStatistics &GetStatistics() const noexcept {
|
|
return m_statistics;
|
|
}
|
|
|
|
private:
|
|
static void ValidateConfiguration(const Block &block) {
|
|
const SurfaceRieszCalibrationOptions legacyCalibration = block.diagonalOptions().surfaceCalibration;
|
|
if (legacyCalibration.target != SurfaceRieszCalibrationTarget::none || legacyCalibration.probeCount != 0) {
|
|
throw std::invalid_argument(
|
|
"Surface H1 calibration must be configured on SurfaceH1MassStiffness, not diagonal options."
|
|
);
|
|
}
|
|
const SurfaceH1MassStiffness &surface = block.surfaceSurrogate();
|
|
if (surface.calibration.target == SurfaceRieszCalibrationTarget::none ||
|
|
surface.calibration.probeCount < 3 || surface.calibration.probeCount > 64 ||
|
|
surface.calibration.objective != SurfaceRieszCalibrationObjective::operator_action ||
|
|
!std::isfinite(surface.relativeMassCoefficientFloor) || surface.relativeMassCoefficientFloor <= 0.0 ||
|
|
!std::isfinite(surface.gramRelativeTolerance) || surface.gramRelativeTolerance <= 0.0 ||
|
|
surface.gramRelativeTolerance >= 1.0) {
|
|
throw std::invalid_argument(
|
|
"SurfaceH1MassStiffness requires an operator-action target, 3--64 probes, and finite positive fit "
|
|
"tolerances."
|
|
);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] static MPI_Comm Communicator(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
return operation.GetHydrostaticOperator().GetFEM().mesh->GetComm();
|
|
}
|
|
|
|
[[nodiscard]] static field::ScalarBoundaryDofMap
|
|
SurfaceMap(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
|
const fem::FEM &finiteElements = operation.GetHydrostaticOperator().GetFEM();
|
|
return field::make_stellar_surface_scalar_dof_map<DomainSchema>(*finiteElements.surfaceDeformationFes);
|
|
}
|
|
|
|
[[nodiscard]] static mfem::Vector
|
|
AssembleDensityDiagonal(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
mfem::Vector diagonal;
|
|
operation.GetBarotropicClosureOperator().AssembleDensityJacobianDiagonal(diagonal);
|
|
return diagonal;
|
|
}
|
|
|
|
[[nodiscard]] static mfem::Vector
|
|
AssembleEnthalpyDiagonal(const operators::PreparedStellarEquilibriumOperator &operation) {
|
|
mfem::Vector diagonal;
|
|
operation.GetHydrostaticOperator().AssembleEnthalpyJacobianDiagonal(diagonal);
|
|
mfem::Vector ones(diagonal.Size());
|
|
ones = 1.0;
|
|
operation.GetSurfaceConstraintOperator().ApplyJacobianRows(ones, diagonal);
|
|
return diagonal;
|
|
}
|
|
|
|
[[nodiscard]] static std::unique_ptr<mfem::HypreParMatrix> AssembleSurfaceOperator(
|
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
|
const double massCoefficient,
|
|
const double stiffnessCoefficient,
|
|
const bool eliminateZeroRows
|
|
) {
|
|
if (!std::isfinite(massCoefficient) || !std::isfinite(stiffnessCoefficient) || massCoefficient < 0.0 ||
|
|
stiffnessCoefficient < 0.0 || massCoefficient + stiffnessCoefficient <= 0.0) {
|
|
throw std::invalid_argument("Surface H1 coefficients must be finite, nonnegative, and nonzero.");
|
|
}
|
|
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
|
const fem::FEM &finiteElements = operation.GetHydrostaticOperator().GetFEM();
|
|
MFEM_VERIFY(
|
|
finiteElements.mesh != nullptr && finiteElements.surfaceDeformationFes != nullptr,
|
|
"The surface H1 surrogate requires the ambient scalar finite-element space."
|
|
);
|
|
|
|
mfem::Array<int> stellarSurfaceMarker(finiteElements.mesh->bdr_attributes.Max());
|
|
stellarSurfaceMarker = 0;
|
|
constexpr int stellarSurfaceAttribute =
|
|
DomainSchema::template boundary_attribute<utils::domain::StellarSurface>();
|
|
MFEM_VERIFY(
|
|
stellarSurfaceAttribute > 0 && stellarSurfaceAttribute <= stellarSurfaceMarker.Size(),
|
|
"The stellar-surface boundary attribute is absent from the finite-element mesh."
|
|
);
|
|
stellarSurfaceMarker[stellarSurfaceAttribute - 1] = 1;
|
|
|
|
mfem::ConstantCoefficient massWeight(massCoefficient);
|
|
mfem::ConstantCoefficient stiffnessWeight(stiffnessCoefficient);
|
|
mfem::ParBilinearForm surfaceOperator(finiteElements.surfaceDeformationFes.get());
|
|
if (massCoefficient > 0.0) {
|
|
surfaceOperator.AddBoundaryIntegrator(new mfem::MassIntegrator(massWeight), stellarSurfaceMarker);
|
|
}
|
|
if (stiffnessCoefficient > 0.0) {
|
|
surfaceOperator.AddBoundaryIntegrator(
|
|
new mfem::DiffusionIntegrator(stiffnessWeight), stellarSurfaceMarker
|
|
);
|
|
}
|
|
surfaceOperator.Assemble();
|
|
surfaceOperator.Finalize();
|
|
std::unique_ptr<mfem::HypreParMatrix> matrix(surfaceOperator.ParallelAssemble());
|
|
MFEM_VERIFY(matrix != nullptr, "The surface H1 surrogate failed to assemble.");
|
|
if (eliminateZeroRows) {
|
|
matrix->EliminateZeroRows();
|
|
}
|
|
return matrix;
|
|
}
|
|
|
|
[[nodiscard]] static double Legendre12(const double coordinate) {
|
|
double previous = 1.0;
|
|
double current = coordinate;
|
|
for (int degree = 2; degree <= 12; ++degree) {
|
|
const double next = ((2.0 * static_cast<double>(degree) - 1.0) * coordinate * current -
|
|
(static_cast<double>(degree) - 1.0) * previous) /
|
|
static_cast<double>(degree);
|
|
previous = current;
|
|
current = next;
|
|
}
|
|
return current;
|
|
}
|
|
|
|
void FillProbe(
|
|
const int sample,
|
|
mfem::Vector &probe
|
|
) const {
|
|
const bool useOrderedMode = sample < 3;
|
|
for (int index = 0; index < probe.Size(); ++index) {
|
|
const long long globalDof = m_surfaceMap.global_boundary_dof(index);
|
|
if (useOrderedMode) {
|
|
const double coordinate = -1.0 + 2.0 * (static_cast<double>(globalDof) + 0.5) /
|
|
static_cast<double>(m_surfaceMap.global_size());
|
|
const double mode = sample == 0 ? 1.0
|
|
: (sample == 1 ? 0.5 * (3.0 * coordinate * coordinate - 1.0)
|
|
: Legendre12(coordinate));
|
|
probe(index) = mode;
|
|
} else {
|
|
std::uint64_t value = static_cast<std::uint64_t>(globalDof);
|
|
value += 0x9e3779b97f4a7c15ULL * static_cast<std::uint64_t>(sample + 1);
|
|
value = (value ^ (value >> 30U)) * 0xbf58476d1ce4e5b9ULL;
|
|
value = (value ^ (value >> 27U)) * 0x94d049bb133111ebULL;
|
|
value ^= value >> 31U;
|
|
probe(index) = (value & 1ULL) == 0ULL ? -1.0 : 1.0;
|
|
}
|
|
}
|
|
const double localNormSquared = probe * probe;
|
|
double globalNormSquared = 0.0;
|
|
MPI_Allreduce(&localNormSquared, &globalNormSquared, 1, MPI_DOUBLE, MPI_SUM, Communicator(*m_operation));
|
|
if (!std::isfinite(globalNormSquared) || globalNormSquared <= 0.0) {
|
|
throw std::runtime_error("A deterministic surface H1 calibration probe has zero norm.");
|
|
}
|
|
probe /= std::sqrt(globalNormSquared);
|
|
}
|
|
|
|
void ApplyAmbientSurfaceOperator(
|
|
const mfem::HypreParMatrix &surfaceOperator,
|
|
const mfem::Vector &probe,
|
|
mfem::Vector &action,
|
|
mfem::Vector &ambientProbe,
|
|
mfem::Vector &ambientAction
|
|
) const {
|
|
m_surfaceMap.scatter(probe, ambientProbe);
|
|
surfaceOperator.Mult(ambientProbe, ambientAction);
|
|
m_surfaceMap.gather(ambientAction, action);
|
|
}
|
|
|
|
[[nodiscard]] SurfaceH1FitReport FitSurfaceOperator() {
|
|
const SurfaceH1MassStiffness &configuration = m_block.surfaceSurrogate();
|
|
if (configuration.calibration.target == SurfaceRieszCalibrationTarget::none ||
|
|
configuration.calibration.probeCount < 3) {
|
|
throw std::invalid_argument(
|
|
"The surface H1 mass-plus-stiffness surrogate requires at least three calibration probes."
|
|
);
|
|
}
|
|
|
|
mfem::Vector probe(m_surfaceMap.local_size());
|
|
mfem::Vector massAction(m_surfaceMap.local_size());
|
|
mfem::Vector stiffnessAction(m_surfaceMap.local_size());
|
|
mfem::Vector targetAction(m_surfaceMap.local_size());
|
|
mfem::Vector materialFeedback(m_surfaceMap.local_size());
|
|
mfem::Vector ambientProbe(m_surfaceMap.volume_true_dof_size());
|
|
mfem::Vector ambientAction(m_surfaceMap.volume_true_dof_size());
|
|
mfem::Vector densityRightHandSide(m_densityDiagonal.Size());
|
|
mfem::Vector densityCorrection(m_densityDiagonal.Size());
|
|
mfem::Vector densityCoupling(m_densityDiagonal.Size());
|
|
mfem::Vector enthalpyRightHandSide(m_enthalpyDiagonal.Size());
|
|
mfem::Vector enthalpyCorrection(m_enthalpyDiagonal.Size());
|
|
|
|
SurfaceH1NormalEquations local;
|
|
for (int sample = 0; sample < configuration.calibration.probeCount; ++sample) {
|
|
FillProbe(sample, probe);
|
|
ApplyAmbientSurfaceOperator(*m_surfaceMass, probe, massAction, ambientProbe, ambientAction);
|
|
ApplyAmbientSurfaceOperator(*m_surfaceStiffness, probe, stiffnessAction, ambientProbe, ambientAction);
|
|
|
|
m_couplings.ApplySurfaceToSurface(probe, targetAction);
|
|
if (configuration.calibration.target == SurfaceRieszCalibrationTarget::approximate_material_schur) {
|
|
m_couplings.ApplySurfaceToMaterial(probe, densityRightHandSide, enthalpyRightHandSide);
|
|
m_enthalpyInverse->Mult(enthalpyRightHandSide, enthalpyCorrection);
|
|
m_couplings.ApplyEnthalpyToDensity(enthalpyCorrection, densityCoupling);
|
|
densityRightHandSide -= densityCoupling;
|
|
m_densityInverse->Mult(densityRightHandSide, densityCorrection);
|
|
m_couplings.ApplyMaterialToSurface(densityCorrection, enthalpyCorrection, materialFeedback);
|
|
targetAction -= materialFeedback;
|
|
}
|
|
|
|
local.massMass += massAction * massAction;
|
|
local.massStiffness += massAction * stiffnessAction;
|
|
local.stiffnessStiffness += stiffnessAction * stiffnessAction;
|
|
local.massTarget += massAction * targetAction;
|
|
local.stiffnessTarget += stiffnessAction * targetAction;
|
|
local.targetTarget += targetAction * targetAction;
|
|
}
|
|
|
|
std::array<double, 6> localValues{local.massMass, local.massStiffness, local.stiffnessStiffness,
|
|
local.massTarget, local.stiffnessTarget, local.targetTarget};
|
|
std::array<double, 6> globalValues{};
|
|
MPI_Allreduce(
|
|
localValues.data(), globalValues.data(), static_cast<int>(globalValues.size()), MPI_DOUBLE, MPI_SUM,
|
|
Communicator(*m_operation)
|
|
);
|
|
const SurfaceH1NormalEquations global{
|
|
.massMass = globalValues[0],
|
|
.massStiffness = globalValues[1],
|
|
.stiffnessStiffness = globalValues[2],
|
|
.massTarget = globalValues[3],
|
|
.stiffnessTarget = globalValues[4],
|
|
.targetTarget = globalValues[5]
|
|
};
|
|
return detail::fitSurfaceH1Coefficients(global, configuration);
|
|
}
|
|
|
|
void RebindFactorization() {
|
|
auto surfaceBoundaryInverse =
|
|
std::make_unique<SignedScalarBoundarySolverAdapter>(*m_surfaceInverse, m_surfaceMap, m_surfaceFit.sign);
|
|
auto factorization = std::make_unique<MaterialSurfaceFactorizationOperator<Policy>>(
|
|
m_block.factorizationPolicy(), *m_densityInverse, *surfaceBoundaryInverse, *m_enthalpyInverse,
|
|
m_couplings
|
|
);
|
|
m_factorization = std::move(factorization);
|
|
m_surfaceBoundaryInverse = std::move(surfaceBoundaryInverse);
|
|
}
|
|
|
|
[[nodiscard]] static DiagonalPreparationQuality Regularize(
|
|
mfem::Vector &diagonal,
|
|
const MaterialSurfaceDiagonalOptions options,
|
|
const MPI_Comm communicator
|
|
) {
|
|
if (!std::isfinite(options.relativeFloor) || options.relativeFloor < 0.0 ||
|
|
!std::isfinite(options.absoluteFloor) || options.absoluteFloor <= 0.0) {
|
|
throw std::invalid_argument("Material-surface diagonal floors must be finite and nonnegative.");
|
|
}
|
|
double localMaximum = 0.0;
|
|
double localMinimum = std::numeric_limits<double>::infinity();
|
|
for (int index = 0; index < diagonal.Size(); ++index) {
|
|
if (!std::isfinite(diagonal(index))) {
|
|
throw std::invalid_argument("A material-surface diagonal contains a non-finite entry.");
|
|
}
|
|
const double magnitude = std::abs(diagonal(index));
|
|
localMaximum = std::max(localMaximum, magnitude);
|
|
localMinimum = std::min(localMinimum, magnitude);
|
|
}
|
|
double globalMaximum = 0.0;
|
|
double globalMinimum = 0.0;
|
|
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
|
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
|
|
const double floor = std::max(options.absoluteFloor, options.relativeFloor * globalMaximum);
|
|
std::uint64_t localRegularized = 0;
|
|
for (int index = 0; index < diagonal.Size(); ++index) {
|
|
if (std::abs(diagonal(index)) < floor) {
|
|
diagonal(index) = std::copysign(floor, diagonal(index) == 0.0 ? 1.0 : diagonal(index));
|
|
++localRegularized;
|
|
}
|
|
}
|
|
std::uint64_t globalRegularized = 0;
|
|
MPI_Allreduce(&localRegularized, &globalRegularized, 1, MPI_UINT64_T, MPI_SUM, communicator);
|
|
return {
|
|
.minimumAbsoluteEntryBeforeRegularization = globalMinimum,
|
|
.maximumAbsoluteEntryBeforeRegularization = globalMaximum,
|
|
.appliedFloor = floor,
|
|
.regularizedEntries = globalRegularized
|
|
};
|
|
}
|
|
|
|
Block m_block;
|
|
const operators::PreparedStellarEquilibriumOperator *m_operation;
|
|
MaterialSurfaceJacobianOperator m_couplings;
|
|
field::ScalarBoundaryDofMap m_surfaceMap;
|
|
mfem::Vector m_densityDiagonal;
|
|
mfem::Vector m_enthalpyDiagonal;
|
|
DiagonalPreparationQuality m_densityQuality;
|
|
DiagonalPreparationQuality m_enthalpyQuality;
|
|
std::unique_ptr<backend::PreparedDiagonal> m_densityInverse;
|
|
std::unique_ptr<backend::PreparedDiagonal> m_enthalpyInverse;
|
|
std::unique_ptr<mfem::HypreParMatrix> m_surfaceMass;
|
|
std::unique_ptr<mfem::HypreParMatrix> m_surfaceStiffness;
|
|
std::unique_ptr<mfem::HypreParMatrix> m_surfaceSurrogate;
|
|
SurfaceH1FitReport m_surfaceFit;
|
|
std::unique_ptr<backend::PreparedHypreBoomerAMG<Mode>> m_surfaceInverse;
|
|
std::unique_ptr<SignedScalarBoundarySolverAdapter> m_surfaceBoundaryInverse;
|
|
std::unique_ptr<MaterialSurfaceFactorizationOperator<Policy>> m_factorization;
|
|
operators::StellarEquilibriumDependencies m_dependencies;
|
|
PreparedMaterialSurfaceBlockStatistics m_statistics;
|
|
};
|
|
|
|
template <
|
|
MaterialSurfaceDescriptor Descriptor,
|
|
MaterialSurfaceFactorizationPolicy Policy>
|
|
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
|
[[nodiscard]] auto prepare(
|
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
|
MaterialSurfaceBlock<
|
|
Descriptor,
|
|
backend::Diagonal,
|
|
backend::Diagonal,
|
|
Policy> block
|
|
) {
|
|
return PreparedMaterialSurfaceBlock<Descriptor, Policy>{operation, std::move(block)};
|
|
}
|
|
|
|
template <
|
|
equilibrium::StellarEquilibriumModel Model,
|
|
equilibrium::StellarDiscretizationType Discretization,
|
|
MaterialSurfaceFactorizationPolicy Policy>
|
|
requires MaterialSurfacePreconditionerProblem<
|
|
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
|
|
[[nodiscard]] auto prepare(
|
|
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
|
MaterialSurfaceBlock<
|
|
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
|
|
backend::Diagonal,
|
|
backend::Diagonal,
|
|
Policy> block
|
|
) {
|
|
return prepare(problem.GetPhysicalOperator(), std::move(block));
|
|
}
|
|
|
|
template <
|
|
MaterialSurfaceDescriptor Descriptor,
|
|
MaterialSurfaceFactorizationPolicy Policy,
|
|
backend::ApplicationMode Mode>
|
|
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
|
[[nodiscard]] auto prepare(
|
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
|
MaterialSurfaceBlock<
|
|
Descriptor,
|
|
backend::Diagonal,
|
|
backend::HypreBoomerAMG<Mode>,
|
|
Policy,
|
|
SurfaceH1MassStiffness> block
|
|
) {
|
|
return PreparedH1MaterialSurfaceBlock<Descriptor, Policy, Mode>{operation, std::move(block)};
|
|
}
|
|
|
|
template <
|
|
equilibrium::StellarEquilibriumModel Model,
|
|
equilibrium::StellarDiscretizationType Discretization,
|
|
MaterialSurfaceFactorizationPolicy Policy,
|
|
backend::ApplicationMode Mode>
|
|
requires MaterialSurfacePreconditionerProblem<
|
|
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
|
|
[[nodiscard]] auto prepare(
|
|
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
|
MaterialSurfaceBlock<
|
|
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
|
|
backend::Diagonal,
|
|
backend::HypreBoomerAMG<Mode>,
|
|
Policy,
|
|
SurfaceH1MassStiffness> block
|
|
) {
|
|
return prepare(problem.GetPhysicalOperator(), std::move(block));
|
|
}
|
|
} // namespace mean_field::preconditioning
|