feat(preconditioner): major work on preconditioner system

first preconditioner MVP
This commit is contained in:
2026-09-04 07:54:10 -04:00
parent 25510008dd
commit 71423d543f
61 changed files with 15920 additions and 422 deletions

View File

@@ -764,6 +764,11 @@ export namespace mean_field::field {
require_reduced_size(reduced);
if (is_identity()) {
reduced = full;
return;
}
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
reduced(reducedDof) = full(m_reducedToTrue[reducedDof]);
}
@@ -798,6 +803,11 @@ export namespace mean_field::field {
require_full_size(full);
if (is_identity()) {
full = reduced;
return;
}
full = 0.0;
scatter_into(reduced, full);
@@ -828,6 +838,11 @@ export namespace mean_field::field {
require_full_size(full);
if (is_identity()) {
full = reduced;
return;
}
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
full(m_reducedToTrue[reducedDof]) = reduced(reducedDof);
}
@@ -847,6 +862,11 @@ export namespace mean_field::field {
require_full_size(full);
if (is_identity()) {
full.Add(scale, reduced);
return;
}
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
full(m_reducedToTrue[reducedDof]) += scale * reduced(reducedDof);
}

View File

@@ -9,6 +9,7 @@ module;
export module mean_field:field.registry;
export import :dimensions.quantities;
export import :field.base;
export import :quadrature.policy;
export import :utils.domain;
@@ -25,6 +26,7 @@ export namespace mean_field::field {
static constexpr std::string_view name = "density";
static constexpr int scalarOrder = 2 + uniformPolynomialOrderIncrement;
using PhysicalQuantity = dimensions::quantity::Density;
using Support = DomainSupport<utils::domain::Stellar>;
struct Scalar final : ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
@@ -261,6 +263,7 @@ export namespace mean_field::field {
static constexpr std::string_view name = "specific_enthalpy";
static constexpr int scalarOrder = 3 + uniformPolynomialOrderIncrement;
using PhysicalQuantity = dimensions::quantity::SpecificEnthalpy;
using Support = DomainSupport<utils::domain::Stellar>;
struct Scalar final : ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {

View File

@@ -0,0 +1,271 @@
module;
#include <concepts>
#include <type_traits>
export module mean_field:material.thermodynamic_equations;
export import :eos.polytrope;
export import :surface.compiler;
export import :utils.blocks;
export namespace mean_field::material {
/**
* A thermodynamic field identifies the physical quantity represented by
* its discrete degree of freedom. The quantity belongs to the field, not
* to an EOS-specific aggregate description.
*/
template <typename Candidate>
concept ThermodynamicField = surface::SurfaceFieldType<Candidate> && requires {
typename std::remove_cvref_t<Candidate>::PhysicalQuantity;
requires eos::ThermodynamicQuantityType<typename std::remove_cvref_t<Candidate>::PhysicalQuantity>;
};
/**
* Declares the algebraic blocks owned by one thermodynamic governing
* equation. Its physical quantity is inferred from Field.
*/
template <ThermodynamicField Field, typename CorrectionBlock, typename ResidualBlock>
requires std::derived_from<CorrectionBlock, utils::blocks::value_block_base> &&
std::derived_from<ResidualBlock, utils::blocks::residual_block_base>
struct ThermodynamicEquation final {
using FieldType = Field;
using PhysicalQuantity = typename Field::PhysicalQuantity;
using Correction = CorrectionBlock;
using Residual = ResidualBlock;
};
/**
* Registry of thermodynamic governing equations available to a problem.
* The problem form, rather than the registry, selects the active subset.
*/
template <typename... Equations> struct ThermodynamicEquationCatalog final {
static constexpr int size = sizeof...(Equations);
};
namespace detail {
template <typename Candidate> struct IsThermodynamicEquation : std::false_type { };
template <typename Field, typename CorrectionBlock, typename ResidualBlock>
struct IsThermodynamicEquation<ThermodynamicEquation<Field, CorrectionBlock, ResidualBlock>> : std::true_type {
};
template <typename Candidate> struct IsThermodynamicEquationCatalog : std::false_type { };
template <typename... Equations>
struct IsThermodynamicEquationCatalog<ThermodynamicEquationCatalog<Equations...>>
: std::bool_constant<(sizeof...(Equations) > 0) && (IsThermodynamicEquation<Equations>::value && ...)> { };
template <typename Catalog> struct CatalogEntriesAreUnique : std::false_type { };
template <typename... Equations>
struct CatalogEntriesAreUnique<ThermodynamicEquationCatalog<Equations...>>
: std::bool_constant<
utils::blocks::types_are_unique_v<utils::blocks::type_list<typename Equations::FieldType...>> &&
utils::blocks::types_are_unique_v<
utils::blocks::type_list<typename Equations::PhysicalQuantity...>> &&
utils::blocks::types_are_unique_v<utils::blocks::type_list<typename Equations::Correction...>> &&
utils::blocks::types_are_unique_v<utils::blocks::type_list<typename Equations::Residual...>>> { };
template <typename Catalog, typename Field> struct EquationForField;
template <typename Field, typename First, typename... Remaining>
struct EquationForField<ThermodynamicEquationCatalog<First, Remaining...>, Field>
: std::conditional_t<
std::same_as<Field, typename First::FieldType>,
std::type_identity<First>,
EquationForField<ThermodynamicEquationCatalog<Remaining...>, Field>> { };
template <typename Field> struct EquationForField<ThermodynamicEquationCatalog<>, Field> {
using type = void;
};
template <typename Catalog, typename Field> struct EquationFieldCount;
template <typename Field, typename... Equations>
struct EquationFieldCount<ThermodynamicEquationCatalog<Equations...>, Field>
: std::integral_constant<
int,
(int{0} + ... + (std::same_as<Field, typename Equations::FieldType> ? 1 : 0))> { };
template <typename Catalog, typename Correction> struct EquationForCorrection;
template <typename Correction, typename First, typename... Remaining>
struct EquationForCorrection<ThermodynamicEquationCatalog<First, Remaining...>, Correction>
: std::conditional_t<
std::same_as<Correction, typename First::Correction>,
std::type_identity<First>,
EquationForCorrection<ThermodynamicEquationCatalog<Remaining...>, Correction>> { };
template <typename Correction> struct EquationForCorrection<ThermodynamicEquationCatalog<>, Correction> {
using type = void;
};
template <typename Head, typename Catalog> struct PrependEquation;
template <typename Head, typename... Equations>
struct PrependEquation<Head, ThermodynamicEquationCatalog<Equations...>> {
using Type = ThermodynamicEquationCatalog<Head, Equations...>;
};
template <typename ValueBlocks, typename AvailableEquations> struct SelectActiveEquations;
template <typename AvailableEquations>
struct SelectActiveEquations<utils::blocks::type_list<>, AvailableEquations> {
using Type = ThermodynamicEquationCatalog<>;
};
template <typename FirstValue, typename... RemainingValues, typename AvailableEquations>
struct SelectActiveEquations<utils::blocks::type_list<FirstValue, RemainingValues...>, AvailableEquations> {
private:
using Tail =
typename SelectActiveEquations<utils::blocks::type_list<RemainingValues...>, AvailableEquations>::Type;
using Match = typename EquationForCorrection<AvailableEquations, FirstValue>::type;
public:
using Type =
std::conditional_t<std::same_as<Match, void>, Tail, typename PrependEquation<Match, Tail>::Type>;
};
template <typename AvailableEquations, typename Form> struct CatalogMatchesForm : std::false_type { };
template <typename... Equations, typename... Values, typename... Residuals>
struct CatalogMatchesForm<
ThermodynamicEquationCatalog<Equations...>,
utils::blocks::block_form<utils::blocks::type_list<Values...>, utils::blocks::type_list<Residuals...>>>
: std::bool_constant<
((utils::blocks::
contains_type_v<typename Equations::Correction, utils::blocks::type_list<Values...>> ==
utils::blocks::
contains_type_v<typename Equations::Residual, utils::blocks::type_list<Residuals...>>) &&
...)> { };
template <typename Equations> struct SurfaceBindingsForEquations;
template <typename... Equations>
struct SurfaceBindingsForEquations<ThermodynamicEquationCatalog<Equations...>> {
using Type = surface::SurfaceStateBindings<
surface::SurfaceStateBinding<typename Equations::PhysicalQuantity, typename Equations::FieldType>...>;
};
template <typename SurfaceFields, typename Equations>
struct SurfaceFieldsBelongToEquations : std::false_type { };
template <typename... Fields, typename Equations>
struct SurfaceFieldsBelongToEquations<field::TypeList<Fields...>, Equations>
: std::bool_constant<((EquationFieldCount<Equations, Fields>::value == 1) && ...)> { };
template <typename EquationOfState, typename Form, typename AvailableEquations, typename = void>
struct ThermodynamicCompilationIsAvailable : std::false_type { };
template <typename EquationOfState, typename Form, typename AvailableEquations>
struct ThermodynamicCompilationIsAvailable<
EquationOfState,
Form,
AvailableEquations,
std::enable_if_t<
eos::EquationOfStateModel<EquationOfState> && utils::blocks::block_form_is_valid_v<Form> &&
IsThermodynamicEquationCatalog<AvailableEquations>::value &&
CatalogEntriesAreUnique<AvailableEquations>::value &&
CatalogMatchesForm<AvailableEquations, Form>::value>> {
private:
using ActiveEquations =
typename SelectActiveEquations<typename Form::value_blocks, AvailableEquations>::Type;
using StateBindings = typename SurfaceBindingsForEquations<ActiveEquations>::Type;
public:
static constexpr bool value = (ActiveEquations::size > 0) &&
surface::PressureSurfaceFormulationCompilable<StateBindings, EquationOfState>;
};
template <typename Candidate, typename = void> struct IsCompiledThermodynamicEquations : std::false_type { };
template <typename Candidate>
struct IsCompiledThermodynamicEquations<
Candidate,
std::void_t<
typename Candidate::EquationOfStateType,
typename Candidate::Equations,
typename Candidate::StateBindings,
typename Candidate::PressureSurfaceFormulation>>
: std::bool_constant<
eos::EquationOfStateModel<typename Candidate::EquationOfStateType> &&
IsThermodynamicEquationCatalog<typename Candidate::Equations>::value &&
CatalogEntriesAreUnique<typename Candidate::Equations>::value &&
surface::ValidSurfaceStateBindings<typename Candidate::StateBindings> &&
std::same_as<
typename Candidate::StateBindings,
typename SurfaceBindingsForEquations<typename Candidate::Equations>::Type> &&
surface::SurfaceConstraintFormulationType<typename Candidate::PressureSurfaceFormulation> &&
std::same_as<
typename Candidate::PressureSurfaceFormulation::StateBindings,
typename Candidate::StateBindings>> { };
} // namespace detail
template <typename Candidate>
concept ThermodynamicEquationType = detail::IsThermodynamicEquation<std::remove_cvref_t<Candidate>>::value;
template <typename Candidate>
concept ValidThermodynamicEquationCatalog =
detail::IsThermodynamicEquationCatalog<std::remove_cvref_t<Candidate>>::value &&
detail::CatalogEntriesAreUnique<std::remove_cvref_t<Candidate>>::value;
template <typename Candidate>
concept CompiledThermodynamicEquations =
detail::IsCompiledThermodynamicEquations<std::remove_cvref_t<Candidate>>::value;
template <ValidThermodynamicEquationCatalog Equations, surface::SurfaceFieldType Field>
requires(detail::EquationFieldCount<Equations, Field>::value == 1)
using ThermodynamicEquationForFieldT = typename detail::EquationForField<Equations, Field>::type;
template <typename Fields, ValidThermodynamicEquationCatalog Equations>
inline constexpr bool fieldsBelongToThermodynamicEquations =
detail::SurfaceFieldsBelongToEquations<Fields, Equations>::value;
template <typename EquationOfState, typename Form, typename AvailableEquations>
concept ThermodynamicEquationsCompilable = detail::ThermodynamicCompilationIsAvailable<
std::remove_cvref_t<EquationOfState>,
std::remove_cvref_t<Form>,
std::remove_cvref_t<AvailableEquations>>::value;
template <eos::EquationOfStateModel EquationOfState, ValidThermodynamicEquationCatalog ActiveEquations>
requires surface::PressureSurfaceFormulationCompilable<
typename detail::SurfaceBindingsForEquations<ActiveEquations>::Type,
EquationOfState>
struct ThermodynamicEquationSet final {
using EquationOfStateType = EquationOfState;
using Equations = ActiveEquations;
using StateBindings = typename detail::SurfaceBindingsForEquations<Equations>::Type;
using PressureSurfaceFormulation =
surface::CompiledPressureSurfaceFormulationT<StateBindings, EquationOfStateType>;
};
template <
eos::EquationOfStateModel EquationOfState,
typename Form,
ValidThermodynamicEquationCatalog AvailableEquations>
requires ThermodynamicEquationsCompilable<EquationOfState, Form, AvailableEquations>
struct CompileThermodynamicEquations final {
using Equations = typename detail::SelectActiveEquations<typename Form::value_blocks, AvailableEquations>::Type;
using Type = ThermodynamicEquationSet<EquationOfState, Equations>;
};
template <typename EquationOfState, typename Form, typename AvailableEquations>
requires ThermodynamicEquationsCompilable<EquationOfState, Form, AvailableEquations>
using CompiledThermodynamicEquationsT = typename CompileThermodynamicEquations<
std::remove_cvref_t<EquationOfState>,
std::remove_cvref_t<Form>,
std::remove_cvref_t<AvailableEquations>>::Type;
using StellarEquilibriumThermodynamicEquations = ThermodynamicEquationCatalog<
ThermodynamicEquation<
field::Density,
utils::blocks::density::mass::value,
utils::blocks::density::mass::residual>,
ThermodynamicEquation<
field::Enthalpy,
utils::blocks::enthalpy::specific::value,
utils::blocks::enthalpy::specific::residual>>;
static_assert(ValidThermodynamicEquationCatalog<StellarEquilibriumThermodynamicEquations>);
} // namespace mean_field::material

View File

@@ -26,6 +26,7 @@ export import :quadrature.policy;
export import :quadrature.mfem;
export import :solver.fields;
export import :solver.preconditioning_diagnostics;
export import :preconditioning;
export import :utils.blocks;
export import :operators.gravity_field;
export import :operators.gravity_field_jacobian;
@@ -72,6 +73,7 @@ export import :surface.constant;
export import :surface.dependencies;
export import :surface.compiled;
export import :surface.compiler;
export import :material.thermodynamic_equations;
export import :deformation.descriptors;
export import :deformation.surface_prescription;
export import :deformation.nodal_radial_surface;

View File

@@ -76,6 +76,12 @@ export namespace mean_field::operators::context::gravity_field {
DisplacementRevision displacement_revision
);
GravityFieldGeometryPreparation PreparePrimal(
const mfem::Vector &displacement,
DiscretizationRevision discretization_revision,
DisplacementRevision displacement_revision
);
[[nodiscard]] const PreparedMappedHDivMassOperator &GetMassOperator() const;
[[nodiscard]] const PreparedMappedGravitySourceOperator &GetSourceOperator() const;
[[nodiscard]] const mfem::Operator &GetDivergenceOperator() const;
@@ -87,12 +93,21 @@ export namespace mean_field::operators::context::gravity_field {
[[nodiscard]] bool IsPrepared() const noexcept;
private:
enum class PreparationMode : std::uint8_t { primal, linearization };
GravityFieldGeometryPreparation PrepareImpl(
const mfem::Vector &displacement,
DiscretizationRevision discretization_revision,
DisplacementRevision displacement_revision,
PreparationMode mode
);
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domain_mapper;
std::unique_ptr<PreparedMappedHDivMassOperator> m_mass_operator;
std::unique_ptr<PreparedMappedGravitySourceOperator> m_source_operator;
std::unique_ptr<mfem::ParMixedBilinearForm> m_divergence_operator;
std::unique_ptr<mfem::Operator> m_divergence_operator;
std::unique_ptr<mfem::TransposeOperator> m_transpose_divergence_operator;
field::FieldDofMap m_displacement_map;
@@ -102,6 +117,7 @@ export namespace mean_field::operators::context::gravity_field {
DisplacementRevision m_displacement_revision;
bool m_is_prepared{false};
bool m_variation_state_prepared{false};
};
struct GravityFieldPreparationReport {

View File

@@ -60,6 +60,12 @@ export namespace mean_field::operators {
mfem::Array<int> m_state_offsets;
mfem::Array<int> m_residual_offsets;
GravityFieldJacobianOperator &m_jacobian;
mutable mfem::Vector m_potential_true;
mutable mfem::Vector m_transpose_divergence_action_true;
mutable mfem::Vector m_transpose_divergence_action;
mutable mfem::Vector m_gradient_true;
mutable mfem::Vector m_divergence_action_true;
};
class ReducedGravityFieldOperator final : public mfem::Operator {

View File

@@ -54,6 +54,10 @@ export namespace mean_field::operators {
void BuildResidual(mfem::Vector &residual) const;
// Exact diagonal of the prepared density-to-closure block, expressed
// in the reduced density coordinates used by the root operator.
void AssembleDensityJacobianDiagonal(mfem::Vector &diagonal) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
[[nodiscard]] int GetDensitySize() const noexcept;

View File

@@ -18,6 +18,7 @@ export namespace mean_field::operators {
);
void Prepare(const mfem::Vector &displacement);
void PreparePrimal(const mfem::Vector &displacement);
void Mult(
const mfem::Vector &density,
mfem::Vector &action
@@ -29,6 +30,7 @@ export namespace mean_field::operators {
) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] bool HasVariationData() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
[[nodiscard]] const field::FieldDofMap &GetDensityMap() const noexcept;
@@ -41,6 +43,8 @@ export namespace mean_field::operators {
) const override;
private:
enum class PreparationMode : std::uint8_t { primal, linearization };
struct ElementPAData {
int element_id{-1};
@@ -64,6 +68,11 @@ export namespace mean_field::operators {
mfem::Vector quadrature_data;
};
void PrepareImpl(
const mfem::Vector &displacement,
PreparationMode mode
);
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domain_mapper;
@@ -77,6 +86,11 @@ export namespace mean_field::operators {
mutable mfem::Vector m_density_true;
mutable mfem::Vector m_potential_true;
mutable mfem::Vector m_action_true;
mutable mfem::Vector m_potential_local;
mutable mfem::Vector m_local_action;
mutable mfem::Vector m_element_input;
mutable mfem::Vector m_quadrature_action;
mutable mfem::Vector m_element_action;
mutable mfem::Vector m_density_local;
mutable mfem::Vector m_displacement_variation_local;
mutable mfem::Vector m_local_variation_action;
@@ -90,5 +104,6 @@ export namespace mean_field::operators {
std::uint64_t m_preparation_count{0};
bool m_is_prepared{false};
bool m_has_variation_data{false};
};
} // namespace mean_field::operators

View File

@@ -18,6 +18,7 @@ export namespace mean_field::operators {
);
void Prepare(const mfem::Vector &displacement);
void PreparePrimal(const mfem::Vector &displacement);
void Mult(
const mfem::Vector &gravity_gradient,
mfem::Vector &action
@@ -31,12 +32,15 @@ export namespace mean_field::operators {
void AssembleTrueDiagonal(mfem::Vector &diagonal) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] bool HasVariationData() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
[[nodiscard]] const field::FieldDofMap &GetFluxMap() const noexcept;
[[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept;
private:
enum class PreparationMode : std::uint8_t { primal, linearization };
struct ElementVariationData {
int elementId{-1};
mfem::Array<int> gravityGradientDofs;
@@ -51,6 +55,10 @@ export namespace mean_field::operators {
};
void PrepareVariationData();
void PrepareImpl(
const mfem::Vector &displacement,
PreparationMode mode
);
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domain_mapper;
@@ -68,6 +76,9 @@ export namespace mean_field::operators {
mutable mfem::Vector m_flux_true;
mutable mfem::Vector m_action_true;
mutable mfem::Vector m_domain_action_true;
mutable mfem::Vector m_flux_local;
mutable mfem::Vector m_action_local;
mutable mfem::Vector m_domain_action_local;
mfem::Vector m_displacement_true;
std::vector<ElementVariationData> m_variationElements;
@@ -86,5 +97,7 @@ export namespace mean_field::operators {
mutable mfem::DenseMatrix m_massTensorVariation;
std::uint64_t m_preparation_count{0};
bool m_is_prepared{false};
bool m_has_variation_data{false};
bool m_single_rank{true};
};
} // namespace mean_field::operators

View File

@@ -138,6 +138,10 @@ export namespace mean_field::operators {
mfem::Vector &action
) const;
// Exact diagonal of the volume enthalpy-to-hydrostatic block. Surface
// boundary-row replacement is deliberately applied by its owner.
void AssembleEnthalpyJacobianDiagonal(mfem::Vector &diagonal) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] const context::hydrostatic::HydrostaticPreparationStatistics &

View File

@@ -11,6 +11,7 @@ export module mean_field:operators.stellar_equilibrium_problem;
export import :deformation.domain_deformation;
export import :equilibrium.stellar_discretization;
export import :material.thermodynamic_equations;
export import :model.typed_stellar;
export import :operators.prepared_central_density_stellar_equilibrium;
export import :surface.compiler;
@@ -39,8 +40,25 @@ export namespace mean_field::equilibrium {
hasFixedCentralDensity,
operators::PreparedCentralDensityStellarEquilibriumOperator,
operators::PreparedStellarEquilibriumOperator>;
using CompiledSurfaceConstraintType =
surface::CompiledPressureSurfaceConstraintT<surface::BarotropicSurfaceFormulation, eos::Polytrope>;
using FormType = std::conditional_t<
hasFixedCentralDensity,
operators::CentralDensityStellarEquilibriumForm,
utils::blocks::surface_deformed_stellar_equilibrium_form>;
using JacobianFormType = std::conditional_t<
hasFixedCentralDensity,
operators::CentralDensityStellarEquilibriumJacobianForm,
utils::blocks::surface_deformed_stellar_equilibrium_jacobian_form>;
using ManifestType = std::conditional_t<
hasFixedCentralDensity,
operators::CentralDensityStellarEquilibriumSystemManifest,
operators::StellarEquilibriumSystemManifest>;
using EquationOfStateType = eos::Polytrope;
using AvailableThermodynamicEquations = material::StellarEquilibriumThermodynamicEquations;
using ThermodynamicEquationsType =
material::CompiledThermodynamicEquationsT<EquationOfStateType, FormType, AvailableThermodynamicEquations>;
using CompiledSurfaceConstraintType = surface::CompiledPressureSurfaceConstraintT<
typename ThermodynamicEquationsType::PressureSurfaceFormulation,
EquationOfStateType>;
StellarEquilibriumProblem(
ModelType stellarModel,
@@ -113,6 +131,26 @@ export namespace mean_field::equilibrium {
return m_preparedOperator.GetRootManifest();
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_preparedOperator.IsPrepared();
}
[[nodiscard]] const operators::StellarEquilibriumDependencies &GetLinearizationDependencies() const {
if constexpr (hasFixedCentralDensity) {
return m_preparedOperator.GetPhysicalOperator().GetDependencies();
} else {
return m_preparedOperator.GetDependencies();
}
}
[[nodiscard]] const operators::StellarEquilibriumDependencyStamp &GetGeometryDependency() const {
if constexpr (hasFixedCentralDensity) {
return m_preparedOperator.GetPhysicalOperator().GetGeneratedDisplacementDependency();
} else {
return m_preparedOperator.GetGeneratedDisplacementDependency();
}
}
[[nodiscard]] const field::FieldBoundaryDofMap &GetPressureSurfaceRows() const noexcept {
if constexpr (hasFixedCentralDensity) {
return m_preparedOperator.GetPhysicalOperator().GetSurfaceConstraintOperator().GetSurfaceRows();
@@ -154,9 +192,10 @@ export namespace mean_field::equilibrium {
private:
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
return surface::compilePressureSurfaceConstraint<surface::BarotropicSurfaceFormulation>(
return surface::compilePressureSurfaceConstraint<
typename ThermodynamicEquationsType::PressureSurfaceFormulation>(
stellarModel.template specification<surface::Isobaric>(),
stellarModel.template specification<eos::Polytrope>()
stellarModel.template specification<EquationOfStateType>()
);
}
@@ -207,4 +246,12 @@ export namespace mean_field::equilibrium {
) {
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel});
}
template <typename Candidate> struct IsStellarEquilibriumProblem : std::false_type { };
template <StellarEquilibriumModel Model>
struct IsStellarEquilibriumProblem<StellarEquilibriumProblem<Model>> : std::true_type { };
template <typename Candidate>
concept DiscretizedStellarEquilibriumProblem = IsStellarEquilibriumProblem<std::remove_cvref_t<Candidate>>::value;
} // namespace mean_field::equilibrium

View File

@@ -0,0 +1,332 @@
module;
#include <concepts>
#include <cstdint>
#include <type_traits>
#include <utility>
export module mean_field:preconditioning.backend;
export namespace mean_field::preconditioning {
enum class OperatorCategory : std::uint8_t {
identity,
mass_like,
elliptic_like,
surface_like,
mixed,
dense_border
};
enum class OperatorValueStructure : std::uint8_t { scalar, vector, block };
enum class OperatorSymmetry : std::uint8_t { symmetric, nonsymmetric };
enum class OperatorDefiniteness : std::uint8_t {
positive_definite,
positive_semidefinite,
indefinite,
unspecified
};
enum class OperatorRepresentation : std::uint8_t { none, diagonal, matrix_free, assembled_sparse, assembled_dense };
enum class OperatorDistribution : std::uint8_t { not_applicable, local, distributed_true_dof };
enum class OperatorFESpace : std::uint8_t { not_applicable, h1, h_curl, h_div, l2, product };
enum class OperatorNullspace : std::uint8_t { none, constant_mode, supplied_basis };
enum class ApplicationContract : std::uint8_t { stationary_linear, flexible };
enum class SymmetryRequirement : std::uint8_t { none, symmetric };
enum class NullspaceRequirement : std::uint8_t { none, constant_mode_supported, supplied_basis_required };
enum class SurrogateRequirement : std::uint8_t { none, diagonal, assembled_dense, assembled_sparse };
enum class PreparationDependency : std::uint8_t {
discretization = 1U << 0U,
geometry = 1U << 1U,
equation_of_state = 1U << 2U,
linearization = 1U << 3U
};
template <PreparationDependency... Dependencies> struct PreparationDependencies final {
static constexpr std::uint8_t mask = (std::uint8_t{0} | ... | static_cast<std::uint8_t>(Dependencies));
[[nodiscard]] static consteval bool contains(const PreparationDependency dependency) noexcept {
return (mask & static_cast<std::uint8_t>(dependency)) != 0U;
}
};
template <typename Candidate> struct IsPreparationDependencies : std::false_type { };
template <PreparationDependency... Dependencies>
struct IsPreparationDependencies<PreparationDependencies<Dependencies...>> : std::true_type { };
template <typename Candidate>
concept PreparationDependenciesType = IsPreparationDependencies<std::remove_cvref_t<Candidate>>::value;
using NoPreparationDependencies = PreparationDependencies<>;
template <
OperatorCategory Category,
OperatorValueStructure ValueStructure,
OperatorSymmetry Symmetry,
OperatorDefiniteness Definiteness,
OperatorRepresentation Representation,
OperatorDistribution Distribution,
OperatorFESpace FESpace = OperatorFESpace::not_applicable,
OperatorNullspace Nullspace = OperatorNullspace::none>
struct OperatorCharacteristics final {
static constexpr OperatorCategory category = Category;
static constexpr OperatorValueStructure valueStructure = ValueStructure;
static constexpr OperatorSymmetry symmetry = Symmetry;
static constexpr OperatorDefiniteness definiteness = Definiteness;
static constexpr OperatorRepresentation representation = Representation;
static constexpr OperatorDistribution distribution = Distribution;
static constexpr OperatorFESpace finiteElementSpace = FESpace;
static constexpr OperatorNullspace nullspace = Nullspace;
};
template <typename Candidate> struct IsOperatorCharacteristics : std::false_type { };
template <
OperatorCategory Category,
OperatorValueStructure ValueStructure,
OperatorSymmetry Symmetry,
OperatorDefiniteness Definiteness,
OperatorRepresentation Representation,
OperatorDistribution Distribution,
OperatorFESpace FESpace,
OperatorNullspace Nullspace>
struct IsOperatorCharacteristics<OperatorCharacteristics<
Category,
ValueStructure,
Symmetry,
Definiteness,
Representation,
Distribution,
FESpace,
Nullspace>> : std::true_type { };
template <typename Candidate>
concept OperatorCharacteristicsType = IsOperatorCharacteristics<std::remove_cvref_t<Candidate>>::value;
using IdentityOperatorCharacteristics = OperatorCharacteristics<
OperatorCategory::identity,
OperatorValueStructure::block,
OperatorSymmetry::symmetric,
OperatorDefiniteness::positive_definite,
OperatorRepresentation::none,
OperatorDistribution::not_applicable>;
namespace backend {
struct FixedCycles final {
int cycles{1};
};
struct SolveToTolerance final {
double relativeTolerance{1.0e-8};
int maximumCycles{100};
};
template <typename Candidate> struct ApplicationModeTraits {
static constexpr bool registered = false;
};
template <> struct ApplicationModeTraits<FixedCycles> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
};
template <> struct ApplicationModeTraits<SolveToTolerance> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract = ApplicationContract::flexible;
};
template <typename Candidate>
concept ApplicationMode = ApplicationModeTraits<std::remove_cvref_t<Candidate>>::registered;
struct Identity final { };
struct Diagonal final { };
struct DenseDirect final { };
struct MatrixFreeChebyshev final {
int order{2};
int powerIterations{10};
double powerTolerance{1.0e-8};
int powerSeed{12345};
};
template <ApplicationMode Mode = FixedCycles> struct HypreBoomerAMG final {
using ApplicationModeType = Mode;
Mode application{};
constexpr HypreBoomerAMG() = default;
constexpr explicit HypreBoomerAMG(Mode applicationMode) : application(std::move(applicationMode)) {
}
};
template <ApplicationMode Mode> HypreBoomerAMG(Mode) -> HypreBoomerAMG<Mode>;
template <typename Candidate> struct Traits {
static constexpr bool registered = false;
using PreparationDependencies = NoPreparationDependencies;
};
template <> struct Traits<Identity> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
static constexpr bool supportsSerialExecution = true;
static constexpr bool supportsDistributedExecution = true;
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none;
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::none;
static constexpr bool requiresAssembledSparseSurrogate = false;
using PreparationDependencies = NoPreparationDependencies;
template <OperatorCharacteristicsType Characteristics>
static constexpr bool supports = Characteristics::category == OperatorCategory::identity &&
Characteristics::representation == OperatorRepresentation::none;
};
template <> struct Traits<Diagonal> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
static constexpr bool supportsSerialExecution = true;
static constexpr bool supportsDistributedExecution = true;
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::symmetric;
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::diagonal;
static constexpr bool requiresAssembledSparseSurrogate = false;
using PreparationDependencies =
preconditioning::PreparationDependencies<PreparationDependency::linearization>;
template <OperatorCharacteristicsType Characteristics>
static constexpr bool supports =
(Characteristics::category == OperatorCategory::mass_like ||
Characteristics::category == OperatorCategory::elliptic_like ||
Characteristics::category == OperatorCategory::surface_like) &&
Characteristics::symmetry == OperatorSymmetry::symmetric &&
(Characteristics::representation == OperatorRepresentation::diagonal ||
(Characteristics::category == OperatorCategory::mass_like &&
Characteristics::representation == OperatorRepresentation::matrix_free)) &&
Characteristics::nullspace == OperatorNullspace::none &&
(Characteristics::distribution == OperatorDistribution::local ||
Characteristics::distribution == OperatorDistribution::distributed_true_dof);
};
template <> struct Traits<MatrixFreeChebyshev> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
static constexpr bool supportsSerialExecution = true;
static constexpr bool supportsDistributedExecution = true;
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::symmetric;
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::diagonal;
static constexpr bool requiresAssembledSparseSurrogate = false;
using PreparationDependencies = preconditioning::PreparationDependencies<
PreparationDependency::discretization,
PreparationDependency::geometry,
PreparationDependency::linearization>;
template <OperatorCharacteristicsType Characteristics>
static constexpr bool supports =
Characteristics::category == OperatorCategory::mass_like &&
Characteristics::symmetry == OperatorSymmetry::symmetric &&
Characteristics::definiteness == OperatorDefiniteness::positive_definite &&
Characteristics::representation == OperatorRepresentation::matrix_free &&
Characteristics::finiteElementSpace == OperatorFESpace::h_div &&
Characteristics::nullspace == OperatorNullspace::none &&
(Characteristics::distribution == OperatorDistribution::local ||
Characteristics::distribution == OperatorDistribution::distributed_true_dof);
};
template <> struct Traits<DenseDirect> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
static constexpr bool supportsSerialExecution = true;
static constexpr bool supportsDistributedExecution = false;
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none;
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_dense;
static constexpr bool requiresAssembledSparseSurrogate = false;
using PreparationDependencies =
preconditioning::PreparationDependencies<PreparationDependency::linearization>;
template <OperatorCharacteristicsType Characteristics>
static constexpr bool supports =
Characteristics::category == OperatorCategory::dense_border &&
Characteristics::representation == OperatorRepresentation::assembled_dense &&
Characteristics::distribution == OperatorDistribution::local &&
Characteristics::nullspace == OperatorNullspace::none;
};
template <ApplicationMode Mode> struct Traits<HypreBoomerAMG<Mode>> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract = ApplicationModeTraits<Mode>::applicationContract;
static constexpr bool supportsSerialExecution = false;
static constexpr bool supportsDistributedExecution = true;
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::symmetric;
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::constant_mode_supported;
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_sparse;
static constexpr bool requiresAssembledSparseSurrogate = true;
using PreparationDependencies = preconditioning::PreparationDependencies<
PreparationDependency::discretization,
PreparationDependency::geometry,
PreparationDependency::equation_of_state,
PreparationDependency::linearization>;
template <OperatorCharacteristicsType Characteristics>
static constexpr bool supports =
Characteristics::category == OperatorCategory::elliptic_like &&
Characteristics::valueStructure == OperatorValueStructure::scalar &&
Characteristics::symmetry == OperatorSymmetry::symmetric &&
(Characteristics::definiteness == OperatorDefiniteness::positive_definite ||
Characteristics::definiteness == OperatorDefiniteness::positive_semidefinite) &&
Characteristics::representation == OperatorRepresentation::assembled_sparse &&
Characteristics::distribution == OperatorDistribution::distributed_true_dof &&
(Characteristics::finiteElementSpace == OperatorFESpace::h1 ||
Characteristics::finiteElementSpace == OperatorFESpace::l2) &&
(Characteristics::nullspace == OperatorNullspace::none ||
Characteristics::nullspace == OperatorNullspace::constant_mode);
};
template <typename Candidate>
concept Registered = Traits<std::remove_cvref_t<Candidate>>::registered;
namespace detail {
template <
typename Backend,
typename Characteristics,
bool = Registered<Backend> && OperatorCharacteristicsType<Characteristics>>
struct IsCompatible : std::false_type { };
template <typename Backend, typename Characteristics>
struct IsCompatible<Backend, Characteristics, true>
: std::bool_constant<
Traits<std::remove_cvref_t<Backend>>::template supports<std::remove_cvref_t<Characteristics>>> {
};
} // namespace detail
template <typename Backend, typename Characteristics>
inline constexpr bool isCompatible = detail::IsCompatible<Backend, Characteristics>::value;
template <typename Backend, typename Characteristics>
concept Compatible = isCompatible<Backend, Characteristics>;
template <Registered Backend>
inline constexpr ApplicationContract applicationContract =
Traits<std::remove_cvref_t<Backend>>::applicationContract;
template <typename Backend>
concept ArnoldiAdmissible = Registered<Backend> && applicationContract<std::remove_cvref_t<Backend>> ==
ApplicationContract::stationary_linear;
template <Registered Backend>
inline constexpr bool requiresAssembledSparseSurrogate =
Traits<std::remove_cvref_t<Backend>>::requiresAssembledSparseSurrogate;
} // namespace backend
} // namespace mean_field::preconditioning

View File

@@ -0,0 +1,422 @@
module;
#include <cmath>
#include <concepts>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:preconditioning.backend_implementations;
export import :preconditioning.backend;
export namespace mean_field::preconditioning::backend {
struct BackendStatistics final {
std::uint64_t setups{0};
std::uint64_t applications{0};
std::uint64_t innerIterations{0};
std::uint64_t lastInnerIterations{0};
};
namespace detail {
inline void verifyApplicationDimensions(
const mfem::Solver &solver,
const mfem::Vector &rightHandSide,
const mfem::Vector &action
) {
if (rightHandSide.Size() != solver.Width() || action.Size() != solver.Height()) {
throw std::invalid_argument(
"A prepared preconditioning backend requires compatible, preallocated input and output vectors."
);
}
}
inline void verifySquarePositiveSize(
const int height,
const int width
) {
if (height <= 0 || height != width) {
throw std::invalid_argument("A preconditioning backend requires a positive square operator.");
}
}
inline void configure(
mfem::HypreBoomerAMG &solver,
const FixedCycles &mode
) {
if (mode.cycles <= 0) {
throw std::invalid_argument("Fixed-cycle AMG requires at least one cycle.");
}
solver.SetMaxIter(mode.cycles);
solver.SetTol(0.0);
solver.SetPrintLevel(0);
solver.iterative_mode = false;
}
inline void configure(
mfem::HypreBoomerAMG &solver,
const SolveToTolerance &mode
) {
if (!std::isfinite(mode.relativeTolerance) || mode.relativeTolerance <= 0.0 ||
mode.relativeTolerance >= 1.0) {
throw std::invalid_argument("Tolerance-driven AMG requires a finite relative tolerance in (0, 1).");
}
if (mode.maximumCycles <= 0) {
throw std::invalid_argument("Tolerance-driven AMG requires at least one permitted cycle.");
}
solver.SetMaxIter(mode.maximumCycles);
solver.SetTol(mode.relativeTolerance);
solver.SetPrintLevel(0);
solver.iterative_mode = false;
}
} // namespace detail
class PreparedDiagonal final : public mfem::Solver {
public:
PreparedDiagonal(
Diagonal configuration,
const mfem::Vector &diagonal
)
: mfem::Solver(diagonal.Size()),
m_configuration(std::move(configuration)) {
Refresh(diagonal);
}
PreparedDiagonal(const PreparedDiagonal &) = delete;
PreparedDiagonal &operator=(const PreparedDiagonal &) = delete;
PreparedDiagonal(PreparedDiagonal &&) = delete;
PreparedDiagonal &operator=(PreparedDiagonal &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument("The diagonal backend received an operator with incompatible dimensions.");
}
}
void Mult(
const mfem::Vector &rightHandSide,
mfem::Vector &action
) const override {
detail::verifyApplicationDimensions(*this, rightHandSide, action);
for (int index = 0; index < Height(); ++index) {
action(index) = m_inverseDiagonal(index) * rightHandSide(index);
}
++m_statistics.applications;
}
void Refresh(const mfem::Vector &diagonal) {
if (diagonal.Size() <= 0 || diagonal.Size() != Height()) {
throw std::invalid_argument("The diagonal backend requires a positive diagonal of unchanged size.");
}
m_inverseDiagonal.SetSize(diagonal.Size());
for (int index = 0; index < diagonal.Size(); ++index) {
const double entry = diagonal(index);
if (!std::isfinite(entry) || entry == 0.0) {
throw std::invalid_argument("The diagonal backend cannot invert a zero or non-finite entry.");
}
m_inverseDiagonal(index) = 1.0 / entry;
}
++m_statistics.setups;
}
[[nodiscard]] const Diagonal &GetConfiguration() const noexcept {
return m_configuration;
}
[[nodiscard]] const mfem::Vector &GetInverseDiagonal() const noexcept {
return m_inverseDiagonal;
}
[[nodiscard]] const BackendStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
Diagonal m_configuration;
mfem::Vector m_inverseDiagonal;
mutable BackendStatistics m_statistics;
};
class PreparedMatrixFreeChebyshev final : public mfem::Solver {
public:
PreparedMatrixFreeChebyshev(
MatrixFreeChebyshev configuration,
const mfem::Operator &operation,
const MPI_Comm communicator
)
: mfem::Solver(operation.Height()),
m_configuration(std::move(configuration)),
m_communicator(communicator) {
ValidateConfiguration();
Refresh(operation);
}
PreparedMatrixFreeChebyshev(const PreparedMatrixFreeChebyshev &) = delete;
PreparedMatrixFreeChebyshev &operator=(const PreparedMatrixFreeChebyshev &) = delete;
PreparedMatrixFreeChebyshev(PreparedMatrixFreeChebyshev &&) = delete;
PreparedMatrixFreeChebyshev &operator=(PreparedMatrixFreeChebyshev &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
Refresh(operation);
}
void Mult(
const mfem::Vector &rightHandSide,
mfem::Vector &action
) const override {
detail::verifyApplicationDimensions(*this, rightHandSide, action);
m_smoother->Mult(rightHandSide, action);
++m_statistics.applications;
m_statistics.lastInnerIterations = static_cast<std::uint64_t>(m_configuration.order);
m_statistics.innerIterations += static_cast<std::uint64_t>(m_configuration.order);
}
void Refresh(const mfem::Operator &operation) {
detail::verifySquarePositiveSize(operation.Height(), operation.Width());
if (operation.Height() != Height()) {
throw std::invalid_argument("The matrix-free Chebyshev backend cannot change size during refresh.");
}
operation.AssembleDiagonal(m_diagonal);
if (m_diagonal.Size() != Height()) {
throw std::invalid_argument(
"The matrix-free Chebyshev backend received an incompatible assembled diagonal."
);
}
for (int index = 0; index < m_diagonal.Size(); ++index) {
if (!std::isfinite(m_diagonal(index)) || m_diagonal(index) <= 0.0) {
throw std::invalid_argument(
"The matrix-free Chebyshev backend requires a finite, strictly positive diagonal."
);
}
}
m_operation = std::addressof(operation);
m_essentialTrueDofs.SetSize(0);
m_smoother = std::make_unique<mfem::OperatorChebyshevSmoother>(
operation, m_diagonal, m_essentialTrueDofs, m_configuration.order, m_communicator,
m_configuration.powerIterations, m_configuration.powerTolerance, m_configuration.powerSeed
);
m_smoother->iterative_mode = false;
++m_statistics.setups;
}
[[nodiscard]] const MatrixFreeChebyshev &GetConfiguration() const noexcept {
return m_configuration;
}
[[nodiscard]] const mfem::Operator &GetOperator() const noexcept {
return *m_operation;
}
[[nodiscard]] const mfem::Vector &GetDiagonal() const noexcept {
return m_diagonal;
}
[[nodiscard]] const BackendStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
void ValidateConfiguration() const {
if (m_configuration.order <= 0 || m_configuration.order > 5) {
throw std::invalid_argument("Matrix-free Chebyshev requires a polynomial order in [1, 5].");
}
if (m_configuration.powerIterations <= 0) {
throw std::invalid_argument("Matrix-free Chebyshev requires at least one power iteration.");
}
if (!std::isfinite(m_configuration.powerTolerance) || m_configuration.powerTolerance <= 0.0 ||
m_configuration.powerTolerance >= 1.0) {
throw std::invalid_argument(
"Matrix-free Chebyshev requires a finite power-method tolerance strictly between zero and one."
);
}
if (m_configuration.powerSeed <= 0) {
throw std::invalid_argument("Matrix-free Chebyshev requires a strictly positive power-method seed.");
}
}
MatrixFreeChebyshev m_configuration;
MPI_Comm m_communicator;
const mfem::Operator *m_operation{nullptr};
mfem::Vector m_diagonal;
mfem::Array<int> m_essentialTrueDofs;
std::unique_ptr<mfem::OperatorChebyshevSmoother> m_smoother;
mutable BackendStatistics m_statistics;
};
class PreparedDenseDirect final : public mfem::Solver {
public:
PreparedDenseDirect(
DenseDirect configuration,
const mfem::DenseMatrix &matrix
)
: mfem::Solver(matrix.Height()),
m_configuration(std::move(configuration)) {
Refresh(matrix);
}
PreparedDenseDirect(const PreparedDenseDirect &) = delete;
PreparedDenseDirect &operator=(const PreparedDenseDirect &) = delete;
PreparedDenseDirect(PreparedDenseDirect &&) = delete;
PreparedDenseDirect &operator=(PreparedDenseDirect &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
const auto *matrix = dynamic_cast<const mfem::DenseMatrix *>(&operation);
if (matrix == nullptr) {
throw std::invalid_argument("The dense-direct backend requires an mfem::DenseMatrix.");
}
Refresh(*matrix);
}
void Mult(
const mfem::Vector &rightHandSide,
mfem::Vector &action
) const override {
detail::verifyApplicationDimensions(*this, rightHandSide, action);
m_inverse->Mult(rightHandSide, action);
++m_statistics.applications;
}
void Refresh(const mfem::DenseMatrix &matrix) {
detail::verifySquarePositiveSize(matrix.Height(), matrix.Width());
if (matrix.Height() != Height()) {
throw std::invalid_argument("The dense-direct backend cannot change size during refresh.");
}
m_matrix = matrix;
m_inverse = std::make_unique<mfem::DenseMatrixInverse>(m_matrix);
++m_statistics.setups;
}
[[nodiscard]] const DenseDirect &GetConfiguration() const noexcept {
return m_configuration;
}
[[nodiscard]] const mfem::DenseMatrix &GetDenseSurrogate() const noexcept {
return m_matrix;
}
[[nodiscard]] const BackendStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
DenseDirect m_configuration;
mfem::DenseMatrix m_matrix;
std::unique_ptr<mfem::DenseMatrixInverse> m_inverse;
mutable BackendStatistics m_statistics;
};
template <ApplicationMode Mode> class PreparedHypreBoomerAMG final : public mfem::Solver {
public:
using Configuration = HypreBoomerAMG<Mode>;
PreparedHypreBoomerAMG(
Configuration configuration,
const mfem::HypreParMatrix &matrix
)
: mfem::Solver(matrix.Height()),
m_configuration(std::move(configuration)) {
Refresh(matrix);
}
PreparedHypreBoomerAMG(const PreparedHypreBoomerAMG &) = delete;
PreparedHypreBoomerAMG &operator=(const PreparedHypreBoomerAMG &) = delete;
PreparedHypreBoomerAMG(PreparedHypreBoomerAMG &&) = delete;
PreparedHypreBoomerAMG &operator=(PreparedHypreBoomerAMG &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
const auto *matrix = dynamic_cast<const mfem::HypreParMatrix *>(&operation);
if (matrix == nullptr) {
throw std::invalid_argument("The BoomerAMG backend requires an mfem::HypreParMatrix surrogate.");
}
Refresh(*matrix);
}
void Mult(
const mfem::Vector &rightHandSide,
mfem::Vector &action
) const override {
detail::verifyApplicationDimensions(*this, rightHandSide, action);
m_solver->Mult(rightHandSide, action);
int iterations = 0;
m_solver->GetNumIterations(iterations);
++m_statistics.applications;
m_statistics.lastInnerIterations = static_cast<std::uint64_t>(iterations);
m_statistics.innerIterations += static_cast<std::uint64_t>(iterations);
}
void Refresh(const mfem::HypreParMatrix &matrix) {
detail::verifySquarePositiveSize(matrix.Height(), matrix.Width());
if (matrix.Height() != Height()) {
throw std::invalid_argument("The BoomerAMG backend cannot change size during refresh.");
}
m_sparseSurrogate = std::addressof(matrix);
m_solver = std::make_unique<mfem::HypreBoomerAMG>(matrix);
detail::configure(*m_solver, m_configuration.application);
mfem::Vector setupRightHandSide(Width());
mfem::Vector setupAction(Height());
setupRightHandSide = 0.0;
setupAction = 0.0;
m_solver->Setup(setupRightHandSide, setupAction);
++m_statistics.setups;
}
[[nodiscard]] const Configuration &GetConfiguration() const noexcept {
return m_configuration;
}
[[nodiscard]] const mfem::HypreParMatrix &GetSparseSurrogate() const noexcept {
return *m_sparseSurrogate;
}
[[nodiscard]] const BackendStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
Configuration m_configuration;
const mfem::HypreParMatrix *m_sparseSurrogate{nullptr};
std::unique_ptr<mfem::HypreBoomerAMG> m_solver;
mutable BackendStatistics m_statistics;
};
[[nodiscard]] inline PreparedDiagonal prepare(
Diagonal configuration,
const mfem::Vector &diagonal
) {
return PreparedDiagonal{std::move(configuration), diagonal};
}
[[nodiscard]] inline PreparedMatrixFreeChebyshev prepare(
MatrixFreeChebyshev configuration,
const mfem::Operator &operation,
const MPI_Comm communicator
) {
return PreparedMatrixFreeChebyshev{std::move(configuration), operation, communicator};
}
[[nodiscard]] inline PreparedDenseDirect prepare(
DenseDirect configuration,
const mfem::DenseMatrix &matrix
) {
return PreparedDenseDirect{std::move(configuration), matrix};
}
template <ApplicationMode Mode>
[[nodiscard]] PreparedHypreBoomerAMG<Mode> prepare(
HypreBoomerAMG<Mode> configuration,
const mfem::HypreParMatrix &matrix
) {
return PreparedHypreBoomerAMG<Mode>{std::move(configuration), matrix};
}
} // namespace mean_field::preconditioning::backend

View File

@@ -0,0 +1,402 @@
module;
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:preconditioning.equilibrium_coordinates;
export import :preconditioning.specification_border;
export namespace mean_field::preconditioning {
namespace detail {
template <typename CandidateList, typename Universe> struct EquilibriumCoordinateListIsSubset;
template <typename... Candidates, typename Universe>
struct EquilibriumCoordinateListIsSubset<utils::blocks::type_list<Candidates...>, Universe>
: std::bool_constant<(utils::blocks::contains_type_v<Candidates, Universe> && ...)> { };
} // namespace detail
template <typename Component, typename Form>
concept EquilibriumCoordinateComponentFor =
PreconditionerComponent<Component> && utils::blocks::block_form_is_valid_v<Form> &&
std::remove_cvref_t<Component>::CorrectionBlocks::size == Form::value_block_count &&
std::remove_cvref_t<Component>::ResidualBlocks::size == Form::residual_block_count &&
detail::EquilibriumCoordinateListIsSubset<
typename std::remove_cvref_t<Component>::CorrectionBlocks,
typename Form::value_blocks>::value &&
detail::EquilibriumCoordinateListIsSubset<
typename std::remove_cvref_t<Component>::ResidualBlocks,
typename Form::residual_blocks>::value;
struct EquilibriumCoordinateRange final {
int equilibriumOffset{0};
int preconditionerOffset{0};
int size{0};
constexpr bool operator==(const EquilibriumCoordinateRange &) const = default;
};
struct EquilibriumCoordinateMapStatistics final {
std::uint64_t residualPacks{0};
std::uint64_t residualUnpacks{0};
std::uint64_t correctionPacks{0};
std::uint64_t correctionUnpacks{0};
};
template <typename Form, typename Component>
requires EquilibriumCoordinateComponentFor<Component, Form>
class EquilibriumPreconditionerCoordinateMap final {
private:
using ComponentType = std::remove_cvref_t<Component>;
using Layout = utils::blocks::form_layout<Form>;
static constexpr std::size_t correctionBlockCount = ComponentType::CorrectionBlocks::size;
static constexpr std::size_t residualBlockCount = ComponentType::ResidualBlocks::size;
public:
explicit EquilibriumPreconditionerCoordinateMap(const Layout &layout)
: m_correctionRanges(MakeCorrectionRanges(
layout,
typename ComponentType::CorrectionBlocks{}
)),
m_residualRanges(MakeResidualRanges(
layout,
typename ComponentType::ResidualBlocks{}
)),
m_equilibriumStateSize(layout.value_offsets().Last()),
m_equilibriumResidualSize(layout.residual_offsets().Last()),
m_preconditionerCorrectionSize(TotalSize(m_correctionRanges)),
m_preconditionerResidualSize(TotalSize(m_residualRanges)) {
if (m_preconditionerCorrectionSize != m_equilibriumStateSize ||
m_preconditionerResidualSize != m_equilibriumResidualSize) {
throw std::logic_error(
"The typed preconditioner coordinate map does not span the complete equilibrium operator."
);
}
}
void PackResidual(
const mfem::Vector &equilibriumResidual,
mfem::Vector &preconditionerResidual
) const {
VerifySizes(
equilibriumResidual, m_equilibriumResidualSize, preconditionerResidual, m_preconditionerResidualSize,
"residual pack"
);
EquilibriumToPreconditioner(equilibriumResidual, preconditionerResidual, m_residualRanges);
++m_statistics.residualPacks;
}
void UnpackResidual(
const mfem::Vector &preconditionerResidual,
mfem::Vector &equilibriumResidual
) const {
VerifySizes(
preconditionerResidual, m_preconditionerResidualSize, equilibriumResidual, m_equilibriumResidualSize,
"residual unpack"
);
PreconditionerToEquilibrium(preconditionerResidual, equilibriumResidual, m_residualRanges);
++m_statistics.residualUnpacks;
}
void PackCorrection(
const mfem::Vector &equilibriumCorrection,
mfem::Vector &preconditionerCorrection
) const {
VerifySizes(
equilibriumCorrection, m_equilibriumStateSize, preconditionerCorrection, m_preconditionerCorrectionSize,
"correction pack"
);
EquilibriumToPreconditioner(equilibriumCorrection, preconditionerCorrection, m_correctionRanges);
++m_statistics.correctionPacks;
}
void UnpackCorrection(
const mfem::Vector &preconditionerCorrection,
mfem::Vector &equilibriumCorrection
) const {
VerifySizes(
preconditionerCorrection, m_preconditionerCorrectionSize, equilibriumCorrection, m_equilibriumStateSize,
"correction unpack"
);
PreconditionerToEquilibrium(preconditionerCorrection, equilibriumCorrection, m_correctionRanges);
++m_statistics.correctionUnpacks;
}
[[nodiscard]] int EquilibriumStateSize() const noexcept {
return m_equilibriumStateSize;
}
[[nodiscard]] int EquilibriumResidualSize() const noexcept {
return m_equilibriumResidualSize;
}
[[nodiscard]] int PreconditionerCorrectionSize() const noexcept {
return m_preconditionerCorrectionSize;
}
[[nodiscard]] int PreconditionerResidualSize() const noexcept {
return m_preconditionerResidualSize;
}
[[nodiscard]] const std::array<
EquilibriumCoordinateRange,
correctionBlockCount> &
GetCorrectionRanges() const noexcept {
return m_correctionRanges;
}
[[nodiscard]] const std::array<
EquilibriumCoordinateRange,
residualBlockCount> &
GetResidualRanges() const noexcept {
return m_residualRanges;
}
[[nodiscard]] const EquilibriumCoordinateMapStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
template <typename... Blocks>
[[nodiscard]] static std::array<
EquilibriumCoordinateRange,
sizeof...(Blocks)>
MakeCorrectionRanges(
const Layout &layout,
utils::blocks::type_list<Blocks...>
) {
std::array<EquilibriumCoordinateRange, sizeof...(Blocks)> ranges{};
int preconditionerOffset = 0;
std::size_t range = 0;
(
[&] {
constexpr int equilibriumBlock = utils::blocks::type_index_v<Blocks, typename Form::value_blocks>;
const int size = layout.size(utils::blocks::value_block<equilibriumBlock>{});
ranges[range++] = {
.equilibriumOffset = layout.offset(utils::blocks::value_block<equilibriumBlock>{}),
.preconditionerOffset = preconditionerOffset,
.size = size
};
preconditionerOffset += size;
}(),
...);
return ranges;
}
template <typename... Blocks>
[[nodiscard]] static std::array<
EquilibriumCoordinateRange,
sizeof...(Blocks)>
MakeResidualRanges(
const Layout &layout,
utils::blocks::type_list<Blocks...>
) {
std::array<EquilibriumCoordinateRange, sizeof...(Blocks)> ranges{};
int preconditionerOffset = 0;
std::size_t range = 0;
(
[&] {
constexpr int equilibriumBlock =
utils::blocks::type_index_v<Blocks, typename Form::residual_blocks>;
const int size = layout.size(utils::blocks::residual_block<equilibriumBlock>{});
ranges[range++] = {
.equilibriumOffset = layout.offset(utils::blocks::residual_block<equilibriumBlock>{}),
.preconditionerOffset = preconditionerOffset,
.size = size
};
preconditionerOffset += size;
}(),
...);
return ranges;
}
template <std::size_t Size>
[[nodiscard]] static int TotalSize(
const std::array<
EquilibriumCoordinateRange,
Size> &ranges
) noexcept {
int size = 0;
for (const auto &range : ranges) {
size += range.size;
}
return size;
}
template <std::size_t Size>
static void EquilibriumToPreconditioner(
const mfem::Vector &equilibrium,
mfem::Vector &preconditioner,
const std::array<
EquilibriumCoordinateRange,
Size> &ranges
) {
for (const auto &range : ranges) {
for (int index = 0; index < range.size; ++index) {
preconditioner(range.preconditionerOffset + index) = equilibrium(range.equilibriumOffset + index);
}
}
}
template <std::size_t Size>
static void PreconditionerToEquilibrium(
const mfem::Vector &preconditioner,
mfem::Vector &equilibrium,
const std::array<
EquilibriumCoordinateRange,
Size> &ranges
) {
for (const auto &range : ranges) {
for (int index = 0; index < range.size; ++index) {
equilibrium(range.equilibriumOffset + index) = preconditioner(range.preconditionerOffset + index);
}
}
}
static void VerifySizes(
const mfem::Vector &source,
const int expectedSourceSize,
const mfem::Vector &destination,
const int expectedDestinationSize,
const char *operation
) {
if (source.Size() != expectedSourceSize || destination.Size() != expectedDestinationSize) {
throw std::invalid_argument(
std::string("The equilibrium preconditioner ") + operation + " received an incompatible vector."
);
}
}
std::array<EquilibriumCoordinateRange, correctionBlockCount> m_correctionRanges;
std::array<EquilibriumCoordinateRange, residualBlockCount> m_residualRanges;
int m_equilibriumStateSize;
int m_equilibriumResidualSize;
int m_preconditionerCorrectionSize;
int m_preconditionerResidualSize;
mutable EquilibriumCoordinateMapStatistics m_statistics;
};
struct PreparedStellarPreconditionerStatistics final {
std::uint64_t applications{0};
std::uint64_t residualCoordinateMappings{0};
std::uint64_t correctionCoordinateMappings{0};
};
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem, SpecificationBorderBlockType Block>
requires EquilibriumCoordinateComponentFor<Block, typename std::remove_cvref_t<Problem>::FormType>
class PreparedStellarPreconditioner final : public mfem::Solver {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using BlockType = std::remove_cvref_t<Block>;
public:
using Form = typename ProblemType::FormType;
using BackendType = typename BlockType::BackendType;
using GroupedPreconditioner = PreparedSpecificationBorderBlock<ProblemType, BlockType>;
using CoordinateMap = EquilibriumPreconditionerCoordinateMap<Form, BlockType>;
PreparedStellarPreconditioner(
const ProblemType &problem,
BlockType block
)
: mfem::Solver(problem.StateSize()),
m_grouped(
problem,
std::move(block)
),
m_coordinates(problem.GetManifest().layout()),
m_groupedResidual(m_coordinates.PreconditionerResidualSize()),
m_groupedCorrection(m_coordinates.PreconditionerCorrectionSize()) {
if (problem.StateSize() != problem.EquationSize() || m_grouped.Height() != problem.StateSize() ||
m_grouped.Width() != problem.EquationSize()) {
throw std::logic_error(
"The prepared stellar preconditioner is incompatible with the complete equilibrium operator."
);
}
}
PreparedStellarPreconditioner(const PreparedStellarPreconditioner &) = delete;
PreparedStellarPreconditioner &operator=(const PreparedStellarPreconditioner &) = delete;
PreparedStellarPreconditioner(PreparedStellarPreconditioner &&) = delete;
PreparedStellarPreconditioner &operator=(PreparedStellarPreconditioner &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument(
"The prepared stellar preconditioner received an incompatible equilibrium operator."
);
}
m_grouped.SetOperator(operation);
}
void Mult(
const mfem::Vector &equilibriumResidual,
mfem::Vector &equilibriumCorrection
) const override {
if (equilibriumResidual.Size() != Width() || equilibriumCorrection.Size() != Height()) {
throw std::invalid_argument(
"The prepared stellar preconditioner requires compatible, preallocated equilibrium vectors."
);
}
m_coordinates.PackResidual(equilibriumResidual, m_groupedResidual);
++m_statistics.residualCoordinateMappings;
m_grouped.Mult(m_groupedResidual, m_groupedCorrection);
m_coordinates.UnpackCorrection(m_groupedCorrection, equilibriumCorrection);
++m_statistics.correctionCoordinateMappings;
++m_statistics.applications;
}
[[nodiscard]] SpecificationBorderBlockPreparationReport Refresh() {
return m_grouped.Refresh();
}
[[nodiscard]] bool IsCurrent() const {
return m_grouped.IsCurrent();
}
[[nodiscard]] const BlockType &GetBlock() const noexcept {
return m_grouped.GetBlock();
}
[[nodiscard]] const GroupedPreconditioner &GetGroupedPreconditioner() const noexcept {
return m_grouped;
}
[[nodiscard]] const CoordinateMap &GetCoordinateMap() const noexcept {
return m_coordinates;
}
[[nodiscard]] const PreparedStellarPreconditionerStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
GroupedPreconditioner m_grouped;
CoordinateMap m_coordinates;
mutable mfem::Vector m_groupedResidual;
mutable mfem::Vector m_groupedCorrection;
mutable PreparedStellarPreconditionerStatistics m_statistics;
};
template <
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
SpecificationBorderBlockType Block>
requires EquilibriumCoordinateComponentFor<
Block,
typename std::remove_cvref_t<Problem>::FormType>
[[nodiscard]] auto prepare(
const Problem &problem,
Block block
) {
return PreparedStellarPreconditioner<Problem, Block>{problem, std::move(block)};
}
} // namespace mean_field::preconditioning

View File

@@ -0,0 +1,676 @@
module;
#include <algorithm>
#include <concepts>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:preconditioning.gravity_field;
export import :fem;
export import :operators.context.gravity_field;
export import :preconditioning.backend_implementations;
export import :preconditioning.plan;
export namespace mean_field::preconditioning {
struct GravityBlockDiagonal final { };
struct GravityLowerTriangular final { };
struct GravityUpperTriangular final { };
struct GravityApproximateLDU final { };
template <typename Candidate> struct IsGravityFactorizationPolicy : std::false_type { };
template <> struct IsGravityFactorizationPolicy<GravityBlockDiagonal> : std::true_type { };
template <> struct IsGravityFactorizationPolicy<GravityLowerTriangular> : std::true_type { };
template <> struct IsGravityFactorizationPolicy<GravityUpperTriangular> : std::true_type { };
template <> struct IsGravityFactorizationPolicy<GravityApproximateLDU> : std::true_type { };
template <typename Candidate>
concept GravityFactorizationPolicy = IsGravityFactorizationPolicy<std::remove_cvref_t<Candidate>>::value;
using GravityMassInverseCharacteristics = OperatorCharacteristics<
OperatorCategory::mass_like,
OperatorValueStructure::vector,
OperatorSymmetry::symmetric,
OperatorDefiniteness::positive_definite,
OperatorRepresentation::matrix_free,
OperatorDistribution::distributed_true_dof,
OperatorFESpace::h_div>;
using GravityPotentialSchurCharacteristics = OperatorCharacteristics<
OperatorCategory::elliptic_like,
OperatorValueStructure::scalar,
OperatorSymmetry::symmetric,
OperatorDefiniteness::positive_semidefinite,
OperatorRepresentation::assembled_sparse,
OperatorDistribution::distributed_true_dof,
OperatorFESpace::l2,
OperatorNullspace::constant_mode>;
using CoupledGravityCharacteristics = OperatorCharacteristics<
OperatorCategory::mixed,
OperatorValueStructure::block,
OperatorSymmetry::symmetric,
OperatorDefiniteness::indefinite,
OperatorRepresentation::matrix_free,
OperatorDistribution::distributed_true_dof,
OperatorFESpace::product>;
namespace backend {
template <Registered MassInverseBackend, Registered PotentialSchurBackend, GravityFactorizationPolicy Policy>
requires Compatible<MassInverseBackend, GravityMassInverseCharacteristics> &&
Compatible<PotentialSchurBackend, GravityPotentialSchurCharacteristics>
struct CoupledGravity final {
using MassBackendType = MassInverseBackend;
using PotentialSchurBackendType = PotentialSchurBackend;
using FactorizationPolicyType = Policy;
};
template <Registered MassInverseBackend, Registered PotentialSchurBackend, GravityFactorizationPolicy Policy>
requires Compatible<MassInverseBackend, GravityMassInverseCharacteristics> &&
Compatible<PotentialSchurBackend, GravityPotentialSchurCharacteristics>
struct Traits<CoupledGravity<MassInverseBackend, PotentialSchurBackend, Policy>> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract =
::mean_field::preconditioning::backend::applicationContract<MassInverseBackend> ==
ApplicationContract::stationary_linear &&
::mean_field::preconditioning::backend::applicationContract<PotentialSchurBackend> ==
ApplicationContract::stationary_linear
? ApplicationContract::stationary_linear
: ApplicationContract::flexible;
static constexpr bool supportsSerialExecution = false;
static constexpr bool supportsDistributedExecution = true;
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::symmetric;
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::constant_mode_supported;
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_sparse;
static constexpr bool requiresAssembledSparseSurrogate = true;
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::symmetric &&
Characteristics::definiteness == OperatorDefiniteness::indefinite &&
Characteristics::representation == OperatorRepresentation::matrix_free &&
Characteristics::distribution == OperatorDistribution::distributed_true_dof &&
Characteristics::finiteElementSpace == OperatorFESpace::product;
};
} // namespace backend
template <
backend::Registered MassBackendT,
backend::Registered PotentialSchurBackendT,
GravityFactorizationPolicy FactorizationPolicyT>
requires backend::Compatible<MassBackendT, GravityMassInverseCharacteristics> &&
backend::Compatible<PotentialSchurBackendT, GravityPotentialSchurCharacteristics>
class GravityFieldBlock final {
public:
using CorrectionBlocks =
utils::blocks::type_list<utils::blocks::gravity::gradient::value, utils::blocks::gravity::poisson::value>;
using ResidualBlocks = utils::blocks::
type_list<utils::blocks::gravity::gradient::residual, utils::blocks::gravity::poisson::residual>;
using RequiredCouplings = utils::blocks::type_list<
Coupling<utils::blocks::gravity::gradient::residual, utils::blocks::gravity::gradient::value>,
Coupling<utils::blocks::gravity::gradient::residual, utils::blocks::gravity::poisson::value>,
Coupling<utils::blocks::gravity::poisson::residual, utils::blocks::gravity::gradient::value>>;
using OperatorDescription = CoupledGravityCharacteristics;
using BackendType = backend::CoupledGravity<MassBackendT, PotentialSchurBackendT, FactorizationPolicyT>;
using PreparationDependencies = typename backend::Traits<BackendType>::PreparationDependencies;
using MassBackend = MassBackendT;
using PotentialSchurBackend = PotentialSchurBackendT;
using Factorization = FactorizationPolicyT;
constexpr GravityFieldBlock(
MassBackendT massInverseBackend = {},
PotentialSchurBackendT potentialSchurBackend = {},
FactorizationPolicyT factorizationPolicy = {}
)
: m_massInverseBackend(std::move(massInverseBackend)),
m_potentialSchurBackend(std::move(potentialSchurBackend)),
m_factorizationPolicy(std::move(factorizationPolicy)) {
}
[[nodiscard]] constexpr const MassBackendT &massInverseBackend() const noexcept {
return m_massInverseBackend;
}
[[nodiscard]] constexpr const PotentialSchurBackendT &potentialSchurBackend() const noexcept {
return m_potentialSchurBackend;
}
[[nodiscard]] constexpr const FactorizationPolicyT &factorizationPolicy() const noexcept {
return m_factorizationPolicy;
}
private:
MassBackendT m_massInverseBackend;
PotentialSchurBackendT m_potentialSchurBackend;
FactorizationPolicyT m_factorizationPolicy;
};
template <
typename MassInverseBackend,
typename PotentialSchurBackend,
typename FactorizationPolicy>
GravityFieldBlock(
MassInverseBackend,
PotentialSchurBackend,
FactorizationPolicy
)
-> GravityFieldBlock<
MassInverseBackend,
PotentialSchurBackend,
FactorizationPolicy>;
struct GravityFactorizationStatistics final {
std::uint64_t applications{0};
std::uint64_t massInverseApplications{0};
std::uint64_t potentialSchurApplications{0};
std::uint64_t divergenceApplications{0};
std::uint64_t transposeDivergenceApplications{0};
};
template <GravityFactorizationPolicy Policy> class GravityFactorizationOperator final : public mfem::Solver {
public:
GravityFactorizationOperator(
Policy policy,
const mfem::Solver &massInverse,
const mfem::Solver &potentialSchurInverse,
const mfem::Operator &divergence
)
: mfem::Solver(massInverse.Height() + potentialSchurInverse.Height()),
m_policy(std::move(policy)),
m_massInverse(std::addressof(massInverse)),
m_potentialSchurInverse(std::addressof(potentialSchurInverse)),
m_divergence(std::addressof(divergence)),
m_offsets(3),
m_potentialWorkspace(potentialSchurInverse.Height()),
m_gradientWorkspace(massInverse.Height()),
m_massCorrection(massInverse.Height()) {
if (massInverse.Height() <= 0 || massInverse.Height() != massInverse.Width()) {
throw std::invalid_argument("The gravity factorization requires a square gradient-mass inverse.");
}
if (potentialSchurInverse.Height() <= 0 ||
potentialSchurInverse.Height() != potentialSchurInverse.Width()) {
throw std::invalid_argument("The gravity factorization requires a square potential-Schur inverse.");
}
if (divergence.Width() != massInverse.Width() || divergence.Height() != potentialSchurInverse.Width()) {
throw std::invalid_argument("The gravity divergence does not connect the supplied inverse blocks.");
}
m_offsets[0] = 0;
m_offsets[1] = massInverse.Height();
m_offsets[2] = Height();
}
GravityFactorizationOperator(const GravityFactorizationOperator &) = delete;
GravityFactorizationOperator &operator=(const GravityFactorizationOperator &) = delete;
GravityFactorizationOperator(GravityFactorizationOperator &&) = delete;
GravityFactorizationOperator &operator=(GravityFactorizationOperator &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument("The gravity factorization received an operator of incompatible size.");
}
}
void Mult(
const mfem::Vector &rightHandSide,
mfem::Vector &action
) const override {
if (rightHandSide.Size() != Width() || action.Size() != Height()) {
throw std::invalid_argument(
"The gravity factorization requires compatible, preallocated input and output vectors."
);
}
const mfem::Vector gradientRightHandSide(
const_cast<mfem::real_t *>(rightHandSide.GetData()) + m_offsets[0], m_offsets[1] - m_offsets[0]
);
const mfem::Vector potentialRightHandSide(
const_cast<mfem::real_t *>(rightHandSide.GetData()) + m_offsets[1], m_offsets[2] - m_offsets[1]
);
mfem::Vector gradientAction(action.GetData() + m_offsets[0], m_offsets[1] - m_offsets[0]);
mfem::Vector potentialAction(action.GetData() + m_offsets[1], m_offsets[2] - m_offsets[1]);
if constexpr (std::same_as<Policy, GravityBlockDiagonal>) {
m_massInverse->Mult(gradientRightHandSide, gradientAction);
m_potentialSchurInverse->Mult(potentialRightHandSide, potentialAction);
++m_statistics.massInverseApplications;
++m_statistics.potentialSchurApplications;
} else if constexpr (std::same_as<Policy, GravityLowerTriangular>) {
m_massInverse->Mult(gradientRightHandSide, gradientAction);
m_divergence->Mult(gradientAction, m_potentialWorkspace);
m_potentialWorkspace -= potentialRightHandSide;
m_potentialSchurInverse->Mult(m_potentialWorkspace, potentialAction);
++m_statistics.massInverseApplications;
++m_statistics.divergenceApplications;
++m_statistics.potentialSchurApplications;
} else if constexpr (std::same_as<Policy, GravityUpperTriangular>) {
m_potentialWorkspace = potentialRightHandSide;
m_potentialWorkspace *= -1.0;
m_potentialSchurInverse->Mult(m_potentialWorkspace, potentialAction);
m_divergence->MultTranspose(potentialAction, m_gradientWorkspace);
m_gradientWorkspace *= -1.0;
m_gradientWorkspace += gradientRightHandSide;
m_massInverse->Mult(m_gradientWorkspace, gradientAction);
++m_statistics.potentialSchurApplications;
++m_statistics.transposeDivergenceApplications;
++m_statistics.massInverseApplications;
} else {
static_assert(std::same_as<Policy, GravityApproximateLDU>);
m_massInverse->Mult(gradientRightHandSide, gradientAction);
m_divergence->Mult(gradientAction, m_potentialWorkspace);
m_potentialWorkspace -= potentialRightHandSide;
m_potentialSchurInverse->Mult(m_potentialWorkspace, potentialAction);
m_divergence->MultTranspose(potentialAction, m_gradientWorkspace);
m_massInverse->Mult(m_gradientWorkspace, m_massCorrection);
gradientAction -= m_massCorrection;
m_statistics.massInverseApplications += 2;
++m_statistics.divergenceApplications;
++m_statistics.potentialSchurApplications;
++m_statistics.transposeDivergenceApplications;
}
++m_statistics.applications;
}
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept {
return m_offsets;
}
[[nodiscard]] const GravityFactorizationStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
Policy m_policy;
const mfem::Solver *m_massInverse;
const mfem::Solver *m_potentialSchurInverse;
const mfem::Operator *m_divergence;
mfem::Array<int> m_offsets;
mutable mfem::Vector m_potentialWorkspace;
mutable mfem::Vector m_gradientWorkspace;
mutable mfem::Vector m_massCorrection;
mutable GravityFactorizationStatistics m_statistics;
};
class ReducedGravityDivergenceOperator final : public mfem::Operator {
public:
ReducedGravityDivergenceOperator(
const mfem::Operator &trueDofDivergence,
field::FieldDofMap gradientMap,
field::FieldDofMap potentialMap
)
: mfem::Operator(
potentialMap.reduced_size(),
gradientMap.reduced_size()
),
m_trueDofDivergence(std::addressof(trueDofDivergence)),
m_gradientMap(std::move(gradientMap)),
m_potentialMap(std::move(potentialMap)),
m_gradientTrue(m_gradientMap.full_size()),
m_potentialTrue(m_potentialMap.full_size()) {
VerifyOperator(trueDofDivergence);
}
void Rebind(const mfem::Operator &trueDofDivergence) {
VerifyOperator(trueDofDivergence);
m_trueDofDivergence = std::addressof(trueDofDivergence);
}
void Mult(
const mfem::Vector &gradient,
mfem::Vector &potentialAction
) const override {
if (gradient.Size() != Width() || potentialAction.Size() != Height()) {
throw std::invalid_argument("The reduced gravity divergence received incompatible vectors.");
}
m_gradientMap.scatter(gradient, m_gradientTrue);
m_trueDofDivergence->Mult(m_gradientTrue, m_potentialTrue);
m_potentialMap.gather(m_potentialTrue, potentialAction);
}
void MultTranspose(
const mfem::Vector &potential,
mfem::Vector &gradientAction
) const override {
if (potential.Size() != Height() || gradientAction.Size() != Width()) {
throw std::invalid_argument("The reduced transpose divergence received incompatible vectors.");
}
m_potentialMap.scatter(potential, m_potentialTrue);
m_trueDofDivergence->MultTranspose(m_potentialTrue, m_gradientTrue);
m_gradientMap.gather(m_gradientTrue, gradientAction);
}
private:
void VerifyOperator(const mfem::Operator &operation) const {
if (operation.Width() != m_gradientMap.full_size() || operation.Height() != m_potentialMap.full_size()) {
throw std::invalid_argument("The true-DOF divergence is incompatible with the gravity field maps.");
}
}
const mfem::Operator *m_trueDofDivergence;
field::FieldDofMap m_gradientMap;
field::FieldDofMap m_potentialMap;
mutable mfem::Vector m_gradientTrue;
mutable mfem::Vector m_potentialTrue;
};
class ReducedFieldSolverAdapter final : public mfem::Solver {
public:
ReducedFieldSolverAdapter(
const mfem::Solver &trueDofSolver,
field::FieldDofMap map
)
: mfem::Solver(map.reduced_size()),
m_trueDofSolver(std::addressof(trueDofSolver)),
m_map(std::move(map)),
m_rightHandSideTrue(m_map.full_size()),
m_actionTrue(m_map.full_size()) {
if (trueDofSolver.Height() != m_map.full_size() || trueDofSolver.Width() != m_map.full_size()) {
throw std::invalid_argument("The true-DOF solver is incompatible with the reduced field map.");
}
}
void SetOperator(const mfem::Operator &operation) override {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument("The reduced field solver received an operator of incompatible size.");
}
}
void Mult(
const mfem::Vector &rightHandSide,
mfem::Vector &action
) const override {
if (rightHandSide.Size() != Width() || action.Size() != Height()) {
throw std::invalid_argument("The reduced field solver received incompatible vectors.");
}
m_map.scatter(rightHandSide, m_rightHandSideTrue);
m_trueDofSolver->Mult(m_rightHandSideTrue, m_actionTrue);
m_map.gather(m_actionTrue, action);
}
private:
const mfem::Solver *m_trueDofSolver;
field::FieldDofMap m_map;
mutable mfem::Vector m_rightHandSideTrue;
mutable mfem::Vector m_actionTrue;
};
[[nodiscard]] std::unique_ptr<mfem::HypreParMatrix> assembleGravityDivergenceSurrogate(const fem::FEM &f);
[[nodiscard]] std::unique_ptr<mfem::HypreParMatrix> assembleGravityPotentialSchurSurrogate(
const fem::FEM &f,
const mfem::Vector &trueMassDiagonal
);
struct GravityFieldBlockPreparationReport final {
bool discretizationChanged{false};
bool geometryChanged{false};
bool rebuiltMassInverse{false};
bool rebuiltDivergenceBinding{false};
bool rebuiltPotentialSchur{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return rebuiltMassInverse || rebuiltDivergenceBinding || rebuiltPotentialSchur;
}
};
struct PreparedGravityFieldBlockStatistics final {
std::uint64_t setups{0};
std::uint64_t refreshChecks{0};
std::uint64_t refreshes{0};
std::uint64_t noOpRefreshes{0};
};
template <typename Candidate>
concept ImplementedGravityMassBackend = std::same_as<std::remove_cvref_t<Candidate>, backend::Diagonal> ||
std::same_as<std::remove_cvref_t<Candidate>, backend::MatrixFreeChebyshev>;
template <backend::Registered MassBackend, backend::ApplicationMode Mode, GravityFactorizationPolicy Policy>
requires ImplementedGravityMassBackend<MassBackend> &&
backend::Compatible<MassBackend, GravityMassInverseCharacteristics>
class PreparedGravityFieldBlock final : public mfem::Solver {
public:
using Block = GravityFieldBlock<MassBackend, backend::HypreBoomerAMG<Mode>, Policy>;
using PreparedMassInverse = std::conditional_t<
std::same_as<MassBackend, backend::Diagonal>,
backend::PreparedDiagonal,
backend::PreparedMatrixFreeChebyshev>;
PreparedGravityFieldBlock(
const fem::FEM &f,
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext,
Block block
)
: mfem::Solver(GravitySize(geometryContext)),
m_block(std::move(block)),
m_geometryContext(std::addressof(geometryContext)),
m_gradientMap(geometryContext.GetMassOperator().GetFluxMap()),
m_potentialMap(geometryContext.GetSourceOperator().GetPotentialMap()),
m_divergence(
geometryContext.GetDivergenceOperator(),
m_gradientMap,
m_potentialMap
),
m_massInverse(MakeMassInverse(
f,
geometryContext,
m_block.massInverseBackend()
)),
m_potentialSchurSurrogate(AssemblePotentialSchur(
f,
geometryContext
)),
m_potentialSchurInverse(
m_block.potentialSchurBackend(),
*m_potentialSchurSurrogate
),
m_reducedPotentialSchurInverse(
m_potentialSchurInverse,
m_potentialMap
),
m_factorization(
m_block.factorizationPolicy(),
m_massInverse,
m_reducedPotentialSchurInverse,
m_divergence
),
m_discretizationRevision(geometryContext.GetDiscretizationRevision()),
m_displacementRevision(geometryContext.GetDisplacementRevision()) {
if (!geometryContext.IsPrepared()) {
throw std::logic_error("The gravity field block requires a prepared gravity geometry context.");
}
m_statistics.setups = 1;
}
PreparedGravityFieldBlock(const PreparedGravityFieldBlock &) = delete;
PreparedGravityFieldBlock &operator=(const PreparedGravityFieldBlock &) = delete;
PreparedGravityFieldBlock(PreparedGravityFieldBlock &&) = delete;
PreparedGravityFieldBlock &operator=(PreparedGravityFieldBlock &&) = 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 gravity field block is stale; refresh it before application.");
}
m_factorization.Mult(rightHandSide, action);
}
[[nodiscard]] bool IsCurrent() const noexcept {
return m_geometryContext->IsPrepared() &&
m_geometryContext->GetDiscretizationRevision() == m_discretizationRevision &&
m_geometryContext->GetDisplacementRevision() == m_displacementRevision;
}
[[nodiscard]] GravityFieldBlockPreparationReport Refresh(
const fem::FEM &f,
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext
) {
if (!geometryContext.IsPrepared()) {
throw std::logic_error("The gravity field block cannot refresh from unprepared geometry.");
}
if (std::addressof(geometryContext) != m_geometryContext) {
throw std::invalid_argument("A prepared gravity field block cannot change geometry-context identity.");
}
++m_statistics.refreshChecks;
GravityFieldBlockPreparationReport report{
.discretizationChanged = geometryContext.GetDiscretizationRevision() != m_discretizationRevision,
.geometryChanged = geometryContext.GetDisplacementRevision() != m_displacementRevision
};
if (!report.discretizationChanged && !report.geometryChanged) {
++m_statistics.noOpRefreshes;
return report;
}
m_divergence.Rebind(geometryContext.GetDivergenceOperator());
report.rebuiltDivergenceBinding = report.discretizationChanged;
RefreshMassInverse(geometryContext);
report.rebuiltMassInverse = true;
auto potentialSchur = AssemblePotentialSchur(f, geometryContext);
m_potentialSchurInverse.Refresh(*potentialSchur);
m_potentialSchurSurrogate = std::move(potentialSchur);
report.rebuiltPotentialSchur = true;
m_discretizationRevision = geometryContext.GetDiscretizationRevision();
m_displacementRevision = geometryContext.GetDisplacementRevision();
++m_statistics.refreshes;
return report;
}
[[nodiscard]] const Block &GetBlock() const noexcept {
return m_block;
}
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept {
return m_factorization.GetOffsets();
}
[[nodiscard]] const PreparedMassInverse &GetMassInverse() const {
if (!IsCurrent()) {
throw std::logic_error("The gravity mass inverse is stale; refresh its owning gravity block first.");
}
return m_massInverse;
}
[[nodiscard]] const backend::PreparedHypreBoomerAMG<Mode> &GetPotentialSchurInverse() const noexcept {
return m_potentialSchurInverse;
}
[[nodiscard]] const mfem::HypreParMatrix &GetPotentialSchurSurrogate() const noexcept {
return *m_potentialSchurSurrogate;
}
[[nodiscard]] const GravityFactorizationOperator<Policy> &GetFactorization() const noexcept {
return m_factorization;
}
[[nodiscard]] const PreparedGravityFieldBlockStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
[[nodiscard]] static int
GravitySize(const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext) {
if (!geometryContext.IsPrepared()) {
throw std::logic_error("The gravity field block requires a prepared gravity geometry context.");
}
return geometryContext.GetMassOperator().GetFluxMap().reduced_size() +
geometryContext.GetSourceOperator().GetPotentialMap().reduced_size();
}
[[nodiscard]] static mfem::Vector AssembleReducedMassDiagonal(
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext
) {
mfem::Vector diagonal;
geometryContext.GetMassOperator().AssembleDiagonal(diagonal);
return diagonal;
}
[[nodiscard]] static PreparedMassInverse MakeMassInverse(
const fem::FEM &f,
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext,
const MassBackend &backendConfiguration
) {
if constexpr (std::same_as<MassBackend, backend::Diagonal>) {
return PreparedMassInverse{backendConfiguration, AssembleReducedMassDiagonal(geometryContext)};
} else {
static_assert(std::same_as<MassBackend, backend::MatrixFreeChebyshev>);
return PreparedMassInverse{
backendConfiguration, geometryContext.GetMassOperator(), f.gravityFluxFes->GetComm()
};
}
}
void RefreshMassInverse(const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext) {
if constexpr (std::same_as<MassBackend, backend::Diagonal>) {
m_massInverse.Refresh(AssembleReducedMassDiagonal(geometryContext));
} else {
static_assert(std::same_as<MassBackend, backend::MatrixFreeChebyshev>);
m_massInverse.Refresh(geometryContext.GetMassOperator());
}
}
[[nodiscard]] static std::unique_ptr<mfem::HypreParMatrix> AssemblePotentialSchur(
const fem::FEM &f,
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext
) {
mfem::Vector trueMassDiagonal;
geometryContext.GetMassOperator().AssembleTrueDiagonal(trueMassDiagonal);
return assembleGravityPotentialSchurSurrogate(f, trueMassDiagonal);
}
Block m_block;
const operators::context::gravity_field::GravityFieldGeometryContext *m_geometryContext;
field::FieldDofMap m_gradientMap;
field::FieldDofMap m_potentialMap;
ReducedGravityDivergenceOperator m_divergence;
PreparedMassInverse m_massInverse;
std::unique_ptr<mfem::HypreParMatrix> m_potentialSchurSurrogate;
backend::PreparedHypreBoomerAMG<Mode> m_potentialSchurInverse;
ReducedFieldSolverAdapter m_reducedPotentialSchurInverse;
GravityFactorizationOperator<Policy> m_factorization;
operators::context::gravity_field::DiscretizationRevision m_discretizationRevision;
operators::context::gravity_field::DisplacementRevision m_displacementRevision;
PreparedGravityFieldBlockStatistics m_statistics;
};
template <
backend::Registered MassBackend,
backend::ApplicationMode Mode,
GravityFactorizationPolicy Policy>
requires ImplementedGravityMassBackend<MassBackend> && backend::Compatible<
MassBackend,
GravityMassInverseCharacteristics>
[[nodiscard]] auto prepare(
const fem::FEM &f,
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext,
GravityFieldBlock<
MassBackend,
backend::HypreBoomerAMG<Mode>,
Policy> block
) {
return PreparedGravityFieldBlock<MassBackend, Mode, Policy>{f, geometryContext, std::move(block)};
}
} // namespace mean_field::preconditioning

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,381 @@
module;
#include <concepts>
#include <tuple>
#include <type_traits>
#include <utility>
export module mean_field:preconditioning.plan;
export import :preconditioning.backend;
export import :utils.blocks;
export namespace mean_field::preconditioning {
template <typename ResidualBlock, typename CorrectionBlock> struct Coupling final {
using Residual = ResidualBlock;
using Correction = CorrectionBlock;
};
template <
typename CorrectionBlockList,
typename ResidualBlockList,
typename RequiredCouplingList,
typename Characteristics,
typename Backend,
typename PreparationRequirements =
typename backend::Traits<std::remove_cvref_t<Backend>>::PreparationDependencies>
struct ComponentDeclaration {
using CorrectionBlocks = CorrectionBlockList;
using ResidualBlocks = ResidualBlockList;
using RequiredCouplings = RequiredCouplingList;
using OperatorDescription = Characteristics;
using BackendType = Backend;
using PreparationDependencies = PreparationRequirements;
};
template <typename CorrectionBlock, typename ResidualBlock>
requires std::derived_from<CorrectionBlock, utils::blocks::value_block_base> &&
std::derived_from<ResidualBlock, utils::blocks::residual_block_base>
struct IdentityBlock final {
using CorrectionBlocks = utils::blocks::type_list<CorrectionBlock>;
using ResidualBlocks = utils::blocks::type_list<ResidualBlock>;
using RequiredCouplings = utils::blocks::type_list<>;
using OperatorDescription = IdentityOperatorCharacteristics;
using BackendType = backend::Identity;
using PreparationDependencies = NoPreparationDependencies;
};
namespace detail {
template <typename Candidate> struct IsTypeList : std::false_type { };
template <typename... Types> struct IsTypeList<utils::blocks::type_list<Types...>> : std::true_type { };
template <typename Candidate>
inline constexpr bool isTypeList = IsTypeList<std::remove_cvref_t<Candidate>>::value;
template <typename List, typename Base> struct IsUniqueDerivedBlockList : std::false_type { };
template <typename Base, typename... Blocks>
struct IsUniqueDerivedBlockList<utils::blocks::type_list<Blocks...>, Base>
: std::bool_constant<
(std::derived_from<Blocks, Base> && ...) &&
utils::blocks::types_are_unique_v<utils::blocks::type_list<Blocks...>>> { };
template <typename Candidate> struct IsCoupling : std::false_type { };
template <typename ResidualBlock, typename CorrectionBlock>
struct IsCoupling<Coupling<ResidualBlock, CorrectionBlock>>
: std::bool_constant<
std::derived_from<ResidualBlock, utils::blocks::residual_block_base> &&
std::derived_from<CorrectionBlock, utils::blocks::value_block_base>> { };
template <typename Candidate> struct IsCouplingList : std::false_type { };
template <typename... Couplings>
struct IsCouplingList<utils::blocks::type_list<Couplings...>>
: std::bool_constant<
(IsCoupling<Couplings>::value && ...) &&
utils::blocks::types_are_unique_v<utils::blocks::type_list<Couplings...>>> { };
template <typename Candidate, typename = void> struct ComponentTraits {
static constexpr bool valid = false;
};
template <typename Candidate>
struct ComponentTraits<
Candidate,
std::void_t<
typename Candidate::CorrectionBlocks,
typename Candidate::ResidualBlocks,
typename Candidate::RequiredCouplings,
typename Candidate::OperatorDescription,
typename Candidate::BackendType,
typename Candidate::PreparationDependencies>> {
using CorrectionBlocks = typename Candidate::CorrectionBlocks;
using ResidualBlocks = typename Candidate::ResidualBlocks;
using RequiredCouplings = typename Candidate::RequiredCouplings;
using OperatorDescription = typename Candidate::OperatorDescription;
using BackendType = typename Candidate::BackendType;
using PreparationDependencies = typename Candidate::PreparationDependencies;
using BackendPreparationDependencies = typename backend::Traits<BackendType>::PreparationDependencies;
static constexpr bool valid =
IsUniqueDerivedBlockList<CorrectionBlocks, utils::blocks::value_block_base>::value &&
IsUniqueDerivedBlockList<ResidualBlocks, utils::blocks::residual_block_base>::value &&
IsCouplingList<RequiredCouplings>::value && OperatorCharacteristicsType<OperatorDescription> &&
backend::Registered<BackendType> && backend::isCompatible<BackendType, OperatorDescription> &&
PreparationDependenciesType<PreparationDependencies> &&
((PreparationDependencies::mask & BackendPreparationDependencies::mask) ==
BackendPreparationDependencies::mask);
};
template <typename... Lists> struct Concatenate;
template <> struct Concatenate<> {
using Type = utils::blocks::type_list<>;
};
template <typename... Types> struct Concatenate<utils::blocks::type_list<Types...>> {
using Type = utils::blocks::type_list<Types...>;
};
template <typename... Left, typename... Right, typename... Remaining>
struct Concatenate<utils::blocks::type_list<Left...>, utils::blocks::type_list<Right...>, Remaining...> {
using Type = typename Concatenate<utils::blocks::type_list<Left..., Right...>, Remaining...>::Type;
};
template <typename... Lists> using ConcatenateT = typename Concatenate<Lists...>::Type;
template <typename List, typename Type> struct Append;
template <typename... Types, typename Type> struct Append<utils::blocks::type_list<Types...>, Type> {
using Result = utils::blocks::type_list<Types..., Type>;
};
template <typename List, typename Type> using AppendT = typename Append<List, Type>::Result;
template <typename List, typename Type>
using AppendUniqueT = std::conditional_t<utils::blocks::contains_type_v<Type, List>, List, AppendT<List, Type>>;
template <typename Source, typename Excluded> struct ListDifference;
template <typename Excluded> struct ListDifference<utils::blocks::type_list<>, Excluded> {
using Type = utils::blocks::type_list<>;
};
template <typename Head, typename... Tail, typename Excluded>
struct ListDifference<utils::blocks::type_list<Head, Tail...>, Excluded> {
private:
using Remaining = typename ListDifference<utils::blocks::type_list<Tail...>, Excluded>::Type;
public:
using Type = std::conditional_t<
utils::blocks::contains_type_v<Head, Excluded>,
Remaining,
ConcatenateT<utils::blocks::type_list<Head>, Remaining>>;
};
template <typename Source, typename Excluded>
using ListDifferenceT = typename ListDifference<Source, Excluded>::Type;
template <typename Remaining, typename Original, typename Repeated> struct CollectRepeatedTypes;
template <typename Original, typename Repeated>
struct CollectRepeatedTypes<utils::blocks::type_list<>, Original, Repeated> {
using Type = Repeated;
};
template <typename Head, typename... Tail, typename Original, typename Repeated>
struct CollectRepeatedTypes<utils::blocks::type_list<Head, Tail...>, Original, Repeated> {
private:
using Next = std::conditional_t<
(utils::blocks::type_count_v<Head, Original> > 1),
AppendUniqueT<Repeated, Head>,
Repeated>;
public:
using Type = typename CollectRepeatedTypes<utils::blocks::type_list<Tail...>, Original, Next>::Type;
};
template <typename List>
using RepeatedTypesT = typename CollectRepeatedTypes<List, List, utils::blocks::type_list<>>::Type;
template <bool AllowsOverlap, typename... Components> class PlanStorage {
public:
using ComponentTypes = utils::blocks::type_list<Components...>;
using CorrectionBlocks = ConcatenateT<typename ComponentTraits<Components>::CorrectionBlocks...>;
using ResidualBlocks = ConcatenateT<typename ComponentTraits<Components>::ResidualBlocks...>;
using RequiredCouplings = ConcatenateT<typename ComponentTraits<Components>::RequiredCouplings...>;
static constexpr bool allowsOverlappingOwnership = AllowsOverlap;
static constexpr bool stationaryLinear =
((backend::applicationContract<typename ComponentTraits<Components>::BackendType> ==
ApplicationContract::stationary_linear) &&
...);
constexpr explicit PlanStorage(Components... components) : m_components(std::move(components)...) {
}
template <typename Component> [[nodiscard]] constexpr const Component &component() const noexcept {
return std::get<Component>(m_components);
}
[[nodiscard]] constexpr const std::tuple<Components...> &components() const noexcept {
return m_components;
}
private:
std::tuple<Components...> m_components;
};
template <
bool ComponentsAreValid,
typename DeclaredCorrectionBlocks,
typename DeclaredResidualBlocks,
typename DeclaredCouplings,
typename... Components>
struct CoherentPlanDeclaration : std::false_type { };
template <
typename DeclaredCorrectionBlocks,
typename DeclaredResidualBlocks,
typename DeclaredCouplings,
typename... Components>
struct CoherentPlanDeclaration<
true,
DeclaredCorrectionBlocks,
DeclaredResidualBlocks,
DeclaredCouplings,
Components...>
: std::bool_constant<
std::same_as<
DeclaredCorrectionBlocks,
ConcatenateT<typename ComponentTraits<Components>::CorrectionBlocks...>> &&
std::same_as<
DeclaredResidualBlocks,
ConcatenateT<typename ComponentTraits<Components>::ResidualBlocks...>> &&
std::same_as<
DeclaredCouplings,
ConcatenateT<typename ComponentTraits<Components>::RequiredCouplings...>>> { };
template <
typename ComponentList,
typename DeclaredCorrectionBlocks,
typename DeclaredResidualBlocks,
typename DeclaredCouplings>
struct PlanDeclarationIsCoherent : std::false_type { };
template <
typename... Components,
typename DeclaredCorrectionBlocks,
typename DeclaredResidualBlocks,
typename DeclaredCouplings>
struct PlanDeclarationIsCoherent<
utils::blocks::type_list<Components...>,
DeclaredCorrectionBlocks,
DeclaredResidualBlocks,
DeclaredCouplings>
: CoherentPlanDeclaration<
(ComponentTraits<Components>::valid && ...),
DeclaredCorrectionBlocks,
DeclaredResidualBlocks,
DeclaredCouplings,
Components...> { };
template <typename Candidate, typename = void> struct PlanTraits {
static constexpr bool valid = false;
};
template <typename Candidate>
struct PlanTraits<
Candidate,
std::void_t<
typename Candidate::ComponentTypes,
typename Candidate::CorrectionBlocks,
typename Candidate::ResidualBlocks,
typename Candidate::RequiredCouplings>> {
static constexpr bool valid =
isTypeList<typename Candidate::ComponentTypes> && isTypeList<typename Candidate::CorrectionBlocks> &&
isTypeList<typename Candidate::ResidualBlocks> && isTypeList<typename Candidate::RequiredCouplings> &&
PlanDeclarationIsCoherent<
typename Candidate::ComponentTypes,
typename Candidate::CorrectionBlocks,
typename Candidate::ResidualBlocks,
typename Candidate::RequiredCouplings>::value;
};
template <typename CouplingList, typename JacobianForm> struct CouplingsExistInJacobian;
template <typename JacobianForm>
struct CouplingsExistInJacobian<utils::blocks::type_list<>, JacobianForm> : std::true_type { };
template <typename Residual, typename Correction, typename... Remaining, typename JacobianForm>
struct CouplingsExistInJacobian<
utils::blocks::type_list<Coupling<Residual, Correction>, Remaining...>,
JacobianForm>
: std::bool_constant<
utils::blocks::has_jacobian_coupling_v<Residual, Correction, JacobianForm> &&
CouplingsExistInJacobian<utils::blocks::type_list<Remaining...>, JacobianForm>::value> { };
template <typename Plan, typename JacobianForm, bool = PlanTraits<std::remove_cvref_t<Plan>>::valid>
struct RequiredCouplingsExist : std::false_type { };
template <typename Plan, typename JacobianForm>
struct RequiredCouplingsExist<Plan, JacobianForm, true>
: CouplingsExistInJacobian<typename std::remove_cvref_t<Plan>::RequiredCouplings, JacobianForm> { };
} // namespace detail
template <typename Candidate>
concept PreconditionerComponent = detail::ComponentTraits<std::remove_cvref_t<Candidate>>::valid;
template <PreconditionerComponent... Components>
class PreconditionerPlan final : public detail::PlanStorage<false, Components...> {
using Base = detail::PlanStorage<false, Components...>;
public:
using Base::Base;
};
template <typename... Components> PreconditionerPlan(Components...) -> PreconditionerPlan<Components...>;
template <PreconditionerComponent... Components>
class OverlappingPreconditionerPlan final : public detail::PlanStorage<true, Components...> {
using Base = detail::PlanStorage<true, Components...>;
public:
using Base::Base;
};
template <typename... Components>
OverlappingPreconditionerPlan(Components...) -> OverlappingPreconditionerPlan<Components...>;
template <typename Candidate>
concept PreconditionerPlanType = detail::PlanTraits<std::remove_cvref_t<Candidate>>::valid;
template <typename Form, typename Plan>
requires utils::blocks::block_form_is_valid_v<Form> && PreconditionerPlanType<Plan>
struct PreconditionerCoverage final {
using DeclaredCorrectionBlocks = typename Plan::CorrectionBlocks;
using DeclaredResidualBlocks = typename Plan::ResidualBlocks;
using MissingCorrectionBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredCorrectionBlocks>;
using UnexpectedCorrectionBlocks =
detail::ListDifferenceT<DeclaredCorrectionBlocks, typename Form::value_blocks>;
using RepeatedCorrectionBlocks = detail::RepeatedTypesT<DeclaredCorrectionBlocks>;
using MissingResidualBlocks = detail::ListDifferenceT<typename Form::residual_blocks, DeclaredResidualBlocks>;
using UnexpectedResidualBlocks =
detail::ListDifferenceT<DeclaredResidualBlocks, typename Form::residual_blocks>;
using RepeatedResidualBlocks = detail::RepeatedTypesT<DeclaredResidualBlocks>;
static constexpr bool hasEveryCorrectionBlock = MissingCorrectionBlocks::size == 0;
static constexpr bool hasOnlyCorrectionBlocks = UnexpectedCorrectionBlocks::size == 0;
static constexpr bool hasUniqueCorrectionOwners =
Plan::allowsOverlappingOwnership || RepeatedCorrectionBlocks::size == 0;
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
static constexpr bool hasUniqueResidualOwners =
Plan::allowsOverlappingOwnership || RepeatedResidualBlocks::size == 0;
static constexpr bool complete = hasEveryCorrectionBlock && hasOnlyCorrectionBlocks &&
hasUniqueCorrectionOwners && hasEveryResidualBlock && hasOnlyResidualBlocks &&
hasUniqueResidualOwners;
};
template <typename Plan, typename Form>
concept CompletePreconditionerFor = utils::blocks::block_form_is_valid_v<Form> && PreconditionerPlanType<Plan> &&
PreconditionerCoverage<Form, std::remove_cvref_t<Plan>>::complete;
template <typename Plan, typename JacobianForm>
inline constexpr bool requiredCouplingsExist = detail::RequiredCouplingsExist<Plan, JacobianForm>::value;
template <typename Plan, typename Form, typename JacobianForm>
concept CompatiblePreconditionerFor =
CompletePreconditionerFor<Plan, Form> && utils::blocks::valid_jacobian_form<Form, JacobianForm> &&
requiredCouplingsExist<Plan, JacobianForm>;
template <typename Plan>
concept StationaryLinearPreconditionerPlan =
PreconditionerPlanType<Plan> && std::remove_cvref_t<Plan>::stationaryLinear;
} // namespace mean_field::preconditioning

View File

@@ -0,0 +1,11 @@
export module mean_field:preconditioning;
export import :preconditioning.backend;
export import :preconditioning.backend_implementations;
export import :preconditioning.gravity_field;
export import :preconditioning.material_surface;
export import :preconditioning.plan;
export import :preconditioning.stellar_equilibrium;
export import :preconditioning.stellar_structure;
export import :preconditioning.specification_border;
export import :preconditioning.equilibrium_coordinates;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,513 @@
module;
#include <algorithm>
#include <chrono>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:preconditioning.stellar_equilibrium;
export import :operators.stellar_equilibrium_problem;
export import :preconditioning.plan;
export namespace mean_field::preconditioning {
struct StellarPreconditionerLifecycleSnapshot final {
operators::StellarEquilibriumDependencyStamp discretization;
operators::StellarEquilibriumDependencyStamp geometry;
const void *equationOfStateIdentity{nullptr};
operators::StellarEquilibriumDependencies linearization;
constexpr bool operator==(const StellarPreconditionerLifecycleSnapshot &) const = default;
};
struct StellarPreconditionerPreparationChanges final {
bool discretization{false};
bool geometry{false};
bool equationOfState{false};
bool linearization{false};
[[nodiscard]] constexpr bool Any() const noexcept {
return discretization || geometry || equationOfState || linearization;
}
[[nodiscard]] constexpr bool Contains(const PreparationDependency dependency) const noexcept {
switch (dependency) {
case PreparationDependency::discretization:
return discretization;
case PreparationDependency::geometry:
return geometry;
case PreparationDependency::equation_of_state:
return equationOfState;
case PreparationDependency::linearization:
return linearization;
}
return false;
}
};
[[nodiscard]] constexpr StellarPreconditionerPreparationChanges preparationChanges(
const StellarPreconditionerLifecycleSnapshot &prepared,
const StellarPreconditionerLifecycleSnapshot &current
) noexcept {
return {
.discretization = prepared.discretization != current.discretization,
.geometry = prepared.geometry != current.geometry,
.equationOfState = prepared.equationOfStateIdentity != current.equationOfStateIdentity,
.linearization = prepared.linearization != current.linearization
};
}
struct StellarPreconditionerPreparationReport final {
StellarPreconditionerPreparationChanges changes;
std::uint64_t refreshedComponents{0};
[[nodiscard]] constexpr bool DidAnyWork() const noexcept {
return refreshedComponents != 0;
}
};
struct StellarPreconditionerStatistics final {
std::uint64_t setups{0};
std::uint64_t refreshChecks{0};
std::uint64_t refreshes{0};
std::uint64_t noOpRefreshes{0};
std::uint64_t componentSetups{0};
std::uint64_t componentRefreshes{0};
std::uint64_t operatorBindings{0};
std::uint64_t applications{0};
std::uint64_t backendApplications{0};
std::uint64_t innerIterations{0};
double setupSeconds{0.0};
double refreshSeconds{0.0};
double applicationSeconds{0.0};
double maximumApplicationSeconds{0.0};
};
template <typename Candidate> struct StellarEquilibriumProblemTraits {
static constexpr bool registered = false;
};
template <equilibrium::StellarEquilibriumModel Model>
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model>> {
using Problem = equilibrium::StellarEquilibriumProblem<Model>;
using Form = typename Problem::FormType;
using JacobianForm = typename Problem::JacobianFormType;
using Manifest = typename Problem::ManifestType;
static constexpr bool registered = true;
[[nodiscard]] static bool IsPrepared(const Problem &problem) noexcept {
return problem.IsPrepared();
}
[[nodiscard]] static int StateSize(const Problem &problem) noexcept {
return problem.StateSize();
}
[[nodiscard]] static int EquationSize(const Problem &problem) noexcept {
return problem.EquationSize();
}
[[nodiscard]] static const Manifest &ManifestOf(const Problem &problem) noexcept {
return problem.GetManifest();
}
[[nodiscard]] static const mfem::Operator &LinearizationOperator(const Problem &problem) noexcept {
return problem.GetLinearizationOperator();
}
[[nodiscard]] static StellarPreconditionerLifecycleSnapshot Snapshot(const Problem &problem) {
const operators::StellarEquilibriumDependencies &dependencies = problem.GetLinearizationDependencies();
return {
.discretization = dependencies.discretization,
.geometry = problem.GetGeometryDependency(),
.equationOfStateIdentity =
std::addressof(problem.GetStellarModel().template specification<eos::Polytrope>()),
.linearization = dependencies
};
}
};
template <typename Candidate>
concept StellarPreconditionerProblem = StellarEquilibriumProblemTraits<std::remove_cvref_t<Candidate>>::registered;
namespace backend {
template <typename Component, typename Problem, typename Backend = typename Component::BackendType>
class PreparedComponent;
template <typename Component, StellarPreconditionerProblem Problem>
class PreparedComponent<Component, Problem, Identity> final {
public:
void Setup(
const Problem &,
const Component &
) noexcept {
}
[[nodiscard]] bool Refresh(
const Problem &,
const Component &,
const StellarPreconditionerPreparationChanges &
) noexcept {
return true;
}
};
template <typename Component, typename Problem>
concept PreparedComponentFor = requires(
PreparedComponent<Component, Problem> &prepared,
const Problem &problem,
const Component &component,
const StellarPreconditionerPreparationChanges &changes
) {
prepared.Setup(problem, component);
{ prepared.Refresh(problem, component, changes) } -> std::same_as<bool>;
};
} // namespace backend
namespace detail {
using DensityIdentity =
IdentityBlock<utils::blocks::density::mass::value, utils::blocks::density::mass::residual>;
using SurfaceIdentity = IdentityBlock<
utils::blocks::surface_deformation::parameters::value,
utils::blocks::surface_deformation::shape_equilibrium::residual>;
using GravityGradientIdentity =
IdentityBlock<utils::blocks::gravity::gradient::value, utils::blocks::gravity::gradient::residual>;
using GravityPotentialIdentity =
IdentityBlock<utils::blocks::gravity::poisson::value, utils::blocks::gravity::poisson::residual>;
using EnthalpyIdentity =
IdentityBlock<utils::blocks::enthalpy::specific::value, utils::blocks::enthalpy::specific::residual>;
using FixedMassIdentity = IdentityBlock<
utils::blocks::fixed_total_mass::mass_normalization::value,
utils::blocks::fixed_total_mass::mass_normalization::residual>;
using FixedCentralDensityIdentity = IdentityBlock<
utils::blocks::fixed_central_density::central_value::value,
utils::blocks::fixed_central_density::central_value::residual>;
template <typename Form> struct IdentityPlanForForm;
template <> struct IdentityPlanForForm<utils::blocks::surface_deformed_stellar_equilibrium_form> {
using Type = PreconditionerPlan<
DensityIdentity,
SurfaceIdentity,
GravityGradientIdentity,
GravityPotentialIdentity,
EnthalpyIdentity,
FixedMassIdentity>;
[[nodiscard]] static constexpr Type Make() {
return Type{DensityIdentity{}, SurfaceIdentity{}, GravityGradientIdentity{},
GravityPotentialIdentity{}, EnthalpyIdentity{}, FixedMassIdentity{}};
}
};
template <> struct IdentityPlanForForm<utils::blocks::central_density_bordered_stellar_equilibrium_form> {
using Type = PreconditionerPlan<
DensityIdentity,
SurfaceIdentity,
GravityGradientIdentity,
GravityPotentialIdentity,
EnthalpyIdentity,
FixedMassIdentity,
FixedCentralDensityIdentity>;
[[nodiscard]] static constexpr Type Make() {
return Type{
DensityIdentity{}, SurfaceIdentity{}, GravityGradientIdentity{}, GravityPotentialIdentity{},
EnthalpyIdentity{}, FixedMassIdentity{}, FixedCentralDensityIdentity{}
};
}
};
template <typename ComponentList> struct UsesOnlyIdentityBackends : std::false_type { };
template <typename... Components>
struct UsesOnlyIdentityBackends<utils::blocks::type_list<Components...>>
: std::bool_constant<(std::same_as<typename Components::BackendType, backend::Identity> && ...)> { };
template <typename ComponentList, typename Problem> struct PreparedComponentTuple;
template <typename... Components, typename Problem>
struct PreparedComponentTuple<utils::blocks::type_list<Components...>, Problem> {
using Type = std::tuple<backend::PreparedComponent<Components, Problem>...>;
static constexpr bool available = (backend::PreparedComponentFor<Components, Problem> && ...);
};
template <typename Requirements>
[[nodiscard]] constexpr bool
componentRequiresRefresh(const StellarPreconditionerPreparationChanges &changes) noexcept {
return (Requirements::contains(PreparationDependency::discretization) && changes.discretization) ||
(Requirements::contains(PreparationDependency::geometry) && changes.geometry) ||
(Requirements::contains(PreparationDependency::equation_of_state) && changes.equationOfState) ||
(Requirements::contains(PreparationDependency::linearization) && changes.linearization);
}
} // namespace detail
template <typename Plan, typename Problem>
concept PreparedPreconditionerPlanFor =
StellarPreconditionerProblem<Problem> && PreconditionerPlanType<Plan> &&
CompletePreconditionerFor<Plan, typename StellarEquilibriumProblemTraits<Problem>::Form> &&
CompatiblePreconditionerFor<
Plan,
typename StellarEquilibriumProblemTraits<Problem>::Form,
typename StellarEquilibriumProblemTraits<Problem>::JacobianForm> &&
(!std::remove_cvref_t<Plan>::allowsOverlappingOwnership) &&
detail::UsesOnlyIdentityBackends<typename std::remove_cvref_t<Plan>::ComponentTypes>::value &&
detail::PreparedComponentTuple<
typename std::remove_cvref_t<Plan>::ComponentTypes,
std::remove_cvref_t<Problem>>::available;
template <StellarPreconditionerProblem Problem>
using IdentityPreconditionerPlanFor = typename detail::IdentityPlanForForm<
typename StellarEquilibriumProblemTraits<std::remove_cvref_t<Problem>>::Form>::Type;
template <StellarPreconditionerProblem Problem>
[[nodiscard]] constexpr IdentityPreconditionerPlanFor<Problem> makeIdentityPlan(const Problem &) {
using Form = typename StellarEquilibriumProblemTraits<std::remove_cvref_t<Problem>>::Form;
return detail::IdentityPlanForForm<Form>::Make();
}
template <StellarPreconditionerProblem Problem, typename Plan>
requires PreparedPreconditionerPlanFor<Plan, Problem>
class StellarEquilibriumPreconditioner final : public mfem::Solver {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using PlanType = std::remove_cvref_t<Plan>;
using Traits = StellarEquilibriumProblemTraits<ProblemType>;
using Components = typename PlanType::ComponentTypes;
using PreparedComponents = typename detail::PreparedComponentTuple<Components, ProblemType>::Type;
using Clock = std::chrono::steady_clock;
public:
using FormType = typename Traits::Form;
using JacobianFormType = typename Traits::JacobianForm;
StellarEquilibriumPreconditioner(
ProblemType &problem,
PlanType plan
)
: mfem::Solver(Traits::StateSize(problem)),
m_problem(std::addressof(problem)),
m_manifest(std::addressof(Traits::ManifestOf(problem))),
m_linearization(std::addressof(Traits::LinearizationOperator(problem))),
m_plan(std::move(plan)) {
const Clock::time_point start = Clock::now();
VerifyPreparedProblem();
SetupComponents(std::make_index_sequence<std::tuple_size_v<PreparedComponents>>{});
m_snapshot = Traits::Snapshot(*m_problem);
m_statistics.setups = 1;
m_statistics.setupSeconds = std::chrono::duration<double>(Clock::now() - start).count();
}
StellarEquilibriumPreconditioner(const StellarEquilibriumPreconditioner &) = delete;
StellarEquilibriumPreconditioner &operator=(const StellarEquilibriumPreconditioner &) = delete;
StellarEquilibriumPreconditioner(StellarEquilibriumPreconditioner &&) = delete;
StellarEquilibriumPreconditioner &operator=(StellarEquilibriumPreconditioner &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument(
"The stellar-equilibrium preconditioner received an operator with incompatible dimensions."
);
}
++m_statistics.operatorBindings;
}
void Mult(
const mfem::Vector &residual,
mfem::Vector &correction
) const override {
VerifyCurrent();
if (residual.Size() != Width()) {
throw std::invalid_argument(
"The stellar-equilibrium preconditioner received a residual with the wrong size."
);
}
if (correction.Size() != Height()) {
throw std::invalid_argument(
"The stellar-equilibrium preconditioner requires a preallocated correction of the correct size."
);
}
const Clock::time_point start = Clock::now();
correction = residual;
const double elapsed = std::chrono::duration<double>(Clock::now() - start).count();
++m_statistics.applications;
++m_statistics.backendApplications;
m_statistics.applicationSeconds += elapsed;
m_statistics.maximumApplicationSeconds = std::max(m_statistics.maximumApplicationSeconds, elapsed);
}
[[nodiscard]] StellarPreconditionerPreparationReport Refresh() {
const Clock::time_point start = Clock::now();
VerifyPreparedProblem();
const StellarPreconditionerLifecycleSnapshot current = Traits::Snapshot(*m_problem);
const StellarPreconditionerPreparationChanges changes = preparationChanges(m_snapshot, current);
++m_statistics.refreshChecks;
StellarPreconditionerPreparationReport report{.changes = changes};
if (!changes.Any()) {
++m_statistics.noOpRefreshes;
} else {
report.refreshedComponents =
RefreshComponents(changes, std::make_index_sequence<std::tuple_size_v<PreparedComponents>>{});
++m_statistics.refreshes;
m_statistics.componentRefreshes += report.refreshedComponents;
m_snapshot = current;
}
m_statistics.refreshSeconds += std::chrono::duration<double>(Clock::now() - start).count();
return report;
}
[[nodiscard]] bool IsCurrent() const {
return Traits::IsPrepared(*m_problem) && Traits::Snapshot(*m_problem) == m_snapshot;
}
[[nodiscard]] const ProblemType &GetProblem() const noexcept {
return *m_problem;
}
[[nodiscard]] const typename Traits::Manifest &GetManifest() const noexcept {
return *m_manifest;
}
[[nodiscard]] const mfem::Operator &GetLinearizationOperator() const noexcept {
return *m_linearization;
}
template <typename CorrectionBlock>
requires utils::blocks::contains_type_v<
CorrectionBlock,
typename FormType::value_blocks>
[[nodiscard]] mfem::Vector GetCorrectionBlock(mfem::Vector &correction) const {
if (correction.Size() != Height()) {
throw std::invalid_argument("A correction block view requires a complete correction vector.");
}
constexpr int index = utils::blocks::type_index_v<CorrectionBlock, typename FormType::value_blocks>;
return mfem::Vector(
correction.GetData() + m_manifest->layout().offset(utils::blocks::value_block<index>{}),
m_manifest->layout().size(utils::blocks::value_block<index>{})
);
}
template <typename ResidualBlock>
requires utils::blocks::contains_type_v<
ResidualBlock,
typename FormType::residual_blocks>
[[nodiscard]] mfem::Vector GetResidualBlock(const mfem::Vector &residual) const {
if (residual.Size() != Width()) {
throw std::invalid_argument("A residual block view requires a complete residual vector.");
}
constexpr int index = utils::blocks::type_index_v<ResidualBlock, typename FormType::residual_blocks>;
return mfem::Vector(
const_cast<mfem::real_t *>(residual.GetData()) +
m_manifest->layout().offset(utils::blocks::residual_block<index>{}),
m_manifest->layout().size(utils::blocks::residual_block<index>{})
);
}
[[nodiscard]] const PlanType &GetPlan() const noexcept {
return m_plan;
}
[[nodiscard]] const StellarPreconditionerLifecycleSnapshot &GetLifecycleSnapshot() const noexcept {
return m_snapshot;
}
[[nodiscard]] const StellarPreconditionerStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
void VerifyPreparedProblem() const {
if (!Traits::IsPrepared(*m_problem)) {
throw std::logic_error(
"The stellar-equilibrium problem must be prepared before its preconditioner is prepared or "
"refreshed."
);
}
if (Traits::StateSize(*m_problem) <= 0 ||
Traits::StateSize(*m_problem) != Traits::EquationSize(*m_problem)) {
throw std::logic_error("A stellar-equilibrium preconditioner requires a positive square problem.");
}
const auto &layout = Traits::ManifestOf(*m_problem).layout();
if (layout.value_offsets().Last() != Traits::StateSize(*m_problem) ||
layout.residual_offsets().Last() != Traits::EquationSize(*m_problem)) {
throw std::logic_error(
"The stellar-equilibrium manifest and discrete problem dimensions are inconsistent."
);
}
}
void VerifyCurrent() const {
if (!IsCurrent()) {
throw std::logic_error(
"The stellar-equilibrium preconditioner is stale; call Refresh after preparing a new linearization."
);
}
}
template <std::size_t... Indices> void SetupComponents(std::index_sequence<Indices...>) {
((std::get<Indices>(m_preparedComponents).Setup(*m_problem, std::get<Indices>(m_plan.components())),
++m_statistics.componentSetups),
...);
}
template <std::size_t Index>
[[nodiscard]] std::uint64_t RefreshComponent(const StellarPreconditionerPreparationChanges &changes) {
using ComponentTuple = std::remove_cvref_t<decltype(m_plan.components())>;
using Component = std::tuple_element_t<Index, ComponentTuple>;
if (!detail::componentRequiresRefresh<typename Component::PreparationDependencies>(changes)) {
return 0;
}
return std::get<Index>(m_preparedComponents)
.Refresh(*m_problem, std::get<Index>(m_plan.components()), changes)
? 1U
: 0U;
}
template <std::size_t... Indices>
[[nodiscard]] std::uint64_t RefreshComponents(
const StellarPreconditionerPreparationChanges &changes,
std::index_sequence<Indices...>
) {
return (std::uint64_t{0} + ... + RefreshComponent<Indices>(changes));
}
ProblemType *m_problem;
const typename Traits::Manifest *m_manifest;
const mfem::Operator *m_linearization;
PlanType m_plan;
PreparedComponents m_preparedComponents;
StellarPreconditionerLifecycleSnapshot m_snapshot;
mutable StellarPreconditionerStatistics m_statistics;
};
template <
StellarPreconditionerProblem Problem,
typename Plan>
requires PreparedPreconditionerPlanFor<
std::remove_cvref_t<Plan>,
std::remove_cvref_t<Problem>>
[[nodiscard]] auto prepare(
Problem &problem,
Plan &&plan
) {
using ProblemType = std::remove_cvref_t<Problem>;
using PlanType = std::remove_cvref_t<Plan>;
return StellarEquilibriumPreconditioner<ProblemType, PlanType>{problem, std::forward<Plan>(plan)};
}
} // namespace mean_field::preconditioning

View File

@@ -0,0 +1,830 @@
module;
#include <concepts>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:preconditioning.stellar_structure;
export import :preconditioning.gravity_field;
export import :preconditioning.material_surface;
export namespace mean_field::preconditioning {
struct IndependentStellarSubsystems final { };
struct MaterialThenGravityTriangular final { };
struct GravityThenMaterialTriangular final { };
struct ApproximateStellarBlockLDU final { };
template <typename Candidate> struct IsStellarStructureFactorizationPolicy : std::false_type { };
template <> struct IsStellarStructureFactorizationPolicy<IndependentStellarSubsystems> : std::true_type { };
template <> struct IsStellarStructureFactorizationPolicy<MaterialThenGravityTriangular> : std::true_type { };
template <> struct IsStellarStructureFactorizationPolicy<GravityThenMaterialTriangular> : std::true_type { };
template <> struct IsStellarStructureFactorizationPolicy<ApproximateStellarBlockLDU> : std::true_type { };
template <typename Candidate>
concept StellarStructureFactorizationPolicy =
IsStellarStructureFactorizationPolicy<std::remove_cvref_t<Candidate>>::value;
namespace detail {
template <typename... Lists> struct StellarStructureConcatenate;
template <> struct StellarStructureConcatenate<> {
using Type = utils::blocks::type_list<>;
};
template <typename... Types> struct StellarStructureConcatenate<utils::blocks::type_list<Types...>> {
using Type = utils::blocks::type_list<Types...>;
};
template <typename... Left, typename... Right, typename... Remaining>
struct StellarStructureConcatenate<
utils::blocks::type_list<Left...>,
utils::blocks::type_list<Right...>,
Remaining...> {
using Type =
typename StellarStructureConcatenate<utils::blocks::type_list<Left..., Right...>, Remaining...>::Type;
};
template <typename... Lists>
using StellarStructureConcatenateT = typename StellarStructureConcatenate<Lists...>::Type;
template <typename Residual, typename Corrections, typename JacobianForm>
struct StellarStructureCouplingsForResidual;
template <typename Residual, typename JacobianForm>
struct StellarStructureCouplingsForResidual<Residual, utils::blocks::type_list<>, JacobianForm> {
using Type = utils::blocks::type_list<>;
};
template <typename Residual, typename First, typename... Remaining, typename JacobianForm>
struct StellarStructureCouplingsForResidual<
Residual,
utils::blocks::type_list<First, Remaining...>,
JacobianForm> {
private:
using Tail = typename StellarStructureCouplingsForResidual<
Residual,
utils::blocks::type_list<Remaining...>,
JacobianForm>::Type;
public:
using Type = std::conditional_t<
utils::blocks::has_jacobian_coupling_v<Residual, First, JacobianForm>,
StellarStructureConcatenateT<utils::blocks::type_list<Coupling<Residual, First>>, Tail>,
Tail>;
};
template <typename Residuals, typename Corrections, typename JacobianForm>
struct StellarStructureInducedCouplings;
template <typename Corrections, typename JacobianForm>
struct StellarStructureInducedCouplings<utils::blocks::type_list<>, Corrections, JacobianForm> {
using Type = utils::blocks::type_list<>;
};
template <typename First, typename... Remaining, typename Corrections, typename JacobianForm>
struct StellarStructureInducedCouplings<
utils::blocks::type_list<First, Remaining...>,
Corrections,
JacobianForm> {
using Type = StellarStructureConcatenateT<
typename StellarStructureCouplingsForResidual<First, Corrections, JacobianForm>::Type,
typename StellarStructureInducedCouplings<
utils::blocks::type_list<Remaining...>,
Corrections,
JacobianForm>::Type>;
};
template <typename Left, typename Right> struct StellarStructureListsAreDisjoint;
template <typename... Left, typename Right>
struct StellarStructureListsAreDisjoint<utils::blocks::type_list<Left...>, Right>
: std::bool_constant<(!utils::blocks::contains_type_v<Left, Right> && ...)> { };
template <typename Candidate, typename Universe> struct StellarStructureListIsSubset;
template <typename... Candidates, typename Universe>
struct StellarStructureListIsSubset<utils::blocks::type_list<Candidates...>, Universe>
: std::bool_constant<(utils::blocks::contains_type_v<Candidates, Universe> && ...)> { };
} // namespace detail
using CoupledStellarStructureCharacteristics = OperatorCharacteristics<
OperatorCategory::mixed,
OperatorValueStructure::block,
OperatorSymmetry::nonsymmetric,
OperatorDefiniteness::unspecified,
OperatorRepresentation::matrix_free,
OperatorDistribution::distributed_true_dof,
OperatorFESpace::product>;
namespace backend {
template <
Registered MaterialSurfaceBackend,
Registered GravityBackend,
StellarStructureFactorizationPolicy Policy>
struct CoupledStellarStructure final {
using MaterialSurfaceBackendType = MaterialSurfaceBackend;
using GravityBackendType = GravityBackend;
using FactorizationPolicyType = Policy;
};
template <
Registered MaterialSurfaceBackend,
Registered GravityBackend,
StellarStructureFactorizationPolicy Policy>
struct Traits<CoupledStellarStructure<MaterialSurfaceBackend, GravityBackend, Policy>> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract =
::mean_field::preconditioning::backend::applicationContract<MaterialSurfaceBackend> ==
ApplicationContract::stationary_linear &&
::mean_field::preconditioning::backend::applicationContract<GravityBackend> ==
ApplicationContract::stationary_linear
? ApplicationContract::stationary_linear
: ApplicationContract::flexible;
static constexpr bool supportsSerialExecution = Traits<MaterialSurfaceBackend>::supportsSerialExecution &&
Traits<GravityBackend>::supportsSerialExecution;
static constexpr bool supportsDistributedExecution =
Traits<MaterialSurfaceBackend>::supportsDistributedExecution &&
Traits<GravityBackend>::supportsDistributedExecution;
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none;
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::constant_mode_supported;
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_sparse;
static constexpr bool requiresAssembledSparseSurrogate =
Traits<MaterialSurfaceBackend>::requiresAssembledSparseSurrogate ||
Traits<GravityBackend>::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 <
PreconditionerComponent MaterialSurfaceComponentT,
PreconditionerComponent GravityComponentT,
typename FormT,
typename JacobianFormT,
StellarStructureFactorizationPolicy PolicyT>
requires utils::blocks::valid_jacobian_form<FormT, JacobianFormT> &&
detail::StellarStructureListsAreDisjoint<
typename MaterialSurfaceComponentT::CorrectionBlocks,
typename GravityComponentT::CorrectionBlocks>::value &&
detail::StellarStructureListsAreDisjoint<
typename MaterialSurfaceComponentT::ResidualBlocks,
typename GravityComponentT::ResidualBlocks>::value &&
detail::StellarStructureListIsSubset<
typename MaterialSurfaceComponentT::CorrectionBlocks,
typename FormT::value_blocks>::value &&
detail::StellarStructureListIsSubset<
typename GravityComponentT::CorrectionBlocks,
typename FormT::value_blocks>::value &&
detail::StellarStructureListIsSubset<
typename MaterialSurfaceComponentT::ResidualBlocks,
typename FormT::residual_blocks>::value &&
detail::StellarStructureListIsSubset<
typename GravityComponentT::ResidualBlocks,
typename FormT::residual_blocks>::value
class StellarStructureBlock final {
public:
using MaterialSurfaceComponent = MaterialSurfaceComponentT;
using GravityComponent = GravityComponentT;
using Form = FormT;
using JacobianForm = JacobianFormT;
using Factorization = PolicyT;
using CorrectionBlocks = detail::StellarStructureConcatenateT<
typename MaterialSurfaceComponent::CorrectionBlocks,
typename GravityComponent::CorrectionBlocks>;
using ResidualBlocks = detail::StellarStructureConcatenateT<
typename MaterialSurfaceComponent::ResidualBlocks,
typename GravityComponent::ResidualBlocks>;
using MaterialToGravityCouplings = typename detail::StellarStructureInducedCouplings<
typename GravityComponent::ResidualBlocks,
typename MaterialSurfaceComponent::CorrectionBlocks,
JacobianForm>::Type;
using GravityToMaterialCouplings = typename detail::StellarStructureInducedCouplings<
typename MaterialSurfaceComponent::ResidualBlocks,
typename GravityComponent::CorrectionBlocks,
JacobianForm>::Type;
using RequiredCouplings = detail::StellarStructureConcatenateT<
typename MaterialSurfaceComponent::RequiredCouplings,
typename GravityComponent::RequiredCouplings,
MaterialToGravityCouplings,
GravityToMaterialCouplings>;
using OperatorDescription = CoupledStellarStructureCharacteristics;
using BackendType = backend::CoupledStellarStructure<
typename MaterialSurfaceComponent::BackendType,
typename GravityComponent::BackendType,
Factorization>;
using PreparationDependencies = typename backend::Traits<BackendType>::PreparationDependencies;
constexpr StellarStructureBlock(
MaterialSurfaceComponent materialSurfaceComponent,
GravityComponent gravityComponent,
Factorization factorization = {}
)
: m_materialSurfaceComponent(std::move(materialSurfaceComponent)),
m_gravityComponent(std::move(gravityComponent)),
m_factorization(std::move(factorization)) {
}
[[nodiscard]] constexpr const MaterialSurfaceComponent &materialSurfaceComponent() const noexcept {
return m_materialSurfaceComponent;
}
[[nodiscard]] constexpr const GravityComponent &gravityComponent() const noexcept {
return m_gravityComponent;
}
[[nodiscard]] constexpr const Factorization &factorizationPolicy() const noexcept {
return m_factorization;
}
private:
MaterialSurfaceComponent m_materialSurfaceComponent;
GravityComponent m_gravityComponent;
Factorization m_factorization;
};
template <typename Candidate>
concept StellarStructureCrossCouplingOperator = requires(
const Candidate &couplings,
const mfem::Vector &materialDirection,
const mfem::Vector &gravityDirection,
mfem::Vector &materialAction,
mfem::Vector &gravityAction
) {
{ couplings.MaterialSize() } -> std::same_as<int>;
{ couplings.GravitySize() } -> std::same_as<int>;
couplings.ApplyMaterialToGravity(materialDirection, gravityAction);
couplings.ApplyGravityToMaterial(gravityDirection, materialAction);
};
struct StellarStructureFactorizationStatistics final {
std::uint64_t applications{0};
std::uint64_t materialSurfaceInverseApplications{0};
std::uint64_t gravityInverseApplications{0};
std::uint64_t materialToGravityApplications{0};
std::uint64_t gravityToMaterialApplications{0};
};
template <StellarStructureFactorizationPolicy Policy, StellarStructureCrossCouplingOperator CouplingOperator>
class StellarStructureFactorizationOperator final : public mfem::Solver {
public:
StellarStructureFactorizationOperator(
Policy policy,
const mfem::Solver &materialSurfaceInverse,
const mfem::Solver &gravityInverse,
const CouplingOperator &couplings
)
: mfem::Solver(materialSurfaceInverse.Height() + gravityInverse.Height()),
m_policy(std::move(policy)),
m_materialSurfaceInverse(std::addressof(materialSurfaceInverse)),
m_gravityInverse(std::addressof(gravityInverse)),
m_couplings(std::addressof(couplings)),
m_materialWorkspace(materialSurfaceInverse.Height()),
m_gravityWorkspace(gravityInverse.Height()) {
if (materialSurfaceInverse.Height() <= 0 ||
materialSurfaceInverse.Height() != materialSurfaceInverse.Width() || gravityInverse.Height() <= 0 ||
gravityInverse.Height() != gravityInverse.Width() ||
materialSurfaceInverse.Height() != couplings.MaterialSize() ||
gravityInverse.Height() != couplings.GravitySize()) {
throw std::invalid_argument(
"The stellar-structure inverse blocks do not match the cross-coupling operator."
);
}
}
StellarStructureFactorizationOperator(const StellarStructureFactorizationOperator &) = delete;
StellarStructureFactorizationOperator &operator=(const StellarStructureFactorizationOperator &) = delete;
StellarStructureFactorizationOperator(StellarStructureFactorizationOperator &&) = delete;
StellarStructureFactorizationOperator &operator=(StellarStructureFactorizationOperator &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument("The stellar-structure 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 stellar-structure factorization requires compatible, preallocated vectors."
);
}
action = 0.0;
const mfem::Vector materialRightHandSide(
const_cast<mfem::real_t *>(rightHandSide.GetData()), m_materialSurfaceInverse->Width()
);
const mfem::Vector gravityRightHandSide(
const_cast<mfem::real_t *>(rightHandSide.GetData()) + m_materialSurfaceInverse->Width(),
m_gravityInverse->Width()
);
mfem::Vector materialAction(action, 0, m_materialSurfaceInverse->Height());
mfem::Vector gravityAction(action, m_materialSurfaceInverse->Height(), m_gravityInverse->Height());
if constexpr (std::same_as<Policy, IndependentStellarSubsystems>) {
m_materialSurfaceInverse->Mult(materialRightHandSide, materialAction);
m_gravityInverse->Mult(gravityRightHandSide, gravityAction);
++m_statistics.materialSurfaceInverseApplications;
++m_statistics.gravityInverseApplications;
} else if constexpr (std::same_as<Policy, MaterialThenGravityTriangular>) {
m_materialSurfaceInverse->Mult(materialRightHandSide, materialAction);
m_couplings->ApplyMaterialToGravity(materialAction, m_gravityWorkspace);
m_gravityWorkspace *= -1.0;
m_gravityWorkspace += gravityRightHandSide;
m_gravityInverse->Mult(m_gravityWorkspace, gravityAction);
++m_statistics.materialSurfaceInverseApplications;
++m_statistics.materialToGravityApplications;
++m_statistics.gravityInverseApplications;
} else if constexpr (std::same_as<Policy, GravityThenMaterialTriangular>) {
m_gravityInverse->Mult(gravityRightHandSide, gravityAction);
m_couplings->ApplyGravityToMaterial(gravityAction, m_materialWorkspace);
m_materialWorkspace *= -1.0;
m_materialWorkspace += materialRightHandSide;
m_materialSurfaceInverse->Mult(m_materialWorkspace, materialAction);
++m_statistics.gravityInverseApplications;
++m_statistics.gravityToMaterialApplications;
++m_statistics.materialSurfaceInverseApplications;
} else {
static_assert(std::same_as<Policy, ApproximateStellarBlockLDU>);
m_materialSurfaceInverse->Mult(materialRightHandSide, materialAction);
m_couplings->ApplyMaterialToGravity(materialAction, m_gravityWorkspace);
m_gravityWorkspace *= -1.0;
m_gravityWorkspace += gravityRightHandSide;
m_gravityInverse->Mult(m_gravityWorkspace, gravityAction);
m_couplings->ApplyGravityToMaterial(gravityAction, m_materialWorkspace);
m_materialWorkspace *= -1.0;
m_materialWorkspace += materialRightHandSide;
m_materialSurfaceInverse->Mult(m_materialWorkspace, materialAction);
m_statistics.materialSurfaceInverseApplications += 2;
++m_statistics.materialToGravityApplications;
++m_statistics.gravityInverseApplications;
++m_statistics.gravityToMaterialApplications;
}
materialAction.SyncAliasMemory(action);
gravityAction.SyncAliasMemory(action);
++m_statistics.applications;
}
[[nodiscard]] const StellarStructureFactorizationStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
Policy m_policy;
const mfem::Solver *m_materialSurfaceInverse;
const mfem::Solver *m_gravityInverse;
const CouplingOperator *m_couplings;
mutable mfem::Vector m_materialWorkspace;
mutable mfem::Vector m_gravityWorkspace;
mutable StellarStructureFactorizationStatistics m_statistics;
};
class StellarStructureCrossJacobianOperator final : public mfem::Operator {
public:
explicit StellarStructureCrossJacobianOperator(const operators::PreparedStellarEquilibriumOperator &operation)
: mfem::Operator(MaterialSizeOf(operation) + GravitySizeOf(operation)),
m_operation(std::addressof(operation)),
m_materialOffsets(4),
m_gravityOffsets(3),
m_combinedOffsets(3),
m_gravityDirection(operation.GetGravityJacobianOperator().Width()),
m_volumeDisplacement(operation.GetDomainDeformation().volumeDisplacementSize()),
m_mechanicalAction(operation.GetDomainDeformation().volumeDisplacementSize()),
m_zeroEnthalpy(operation.GetBarotropicClosureOperator().GetEnthalpySize()) {
const auto &context = operation.GetGravityContext();
m_materialOffsets[0] = 0;
m_materialOffsets[1] = context.GetDensityMap().reduced_size();
m_materialOffsets[2] = m_materialOffsets[1] + operation.GetDomainDeformation().parameterCount();
m_materialOffsets[3] = MaterialSizeOf(operation);
m_gravityOffsets[0] = 0;
m_gravityOffsets[1] = context.GetGravityGradientMap().reduced_size();
m_gravityOffsets[2] = GravitySizeOf(operation);
m_combinedOffsets[0] = 0;
m_combinedOffsets[1] = MaterialSize();
m_combinedOffsets[2] = Height();
m_zeroEnthalpy = 0.0;
}
void Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const override {
VerifyCombined(direction, action);
action = 0.0;
const mfem::Vector materialDirection(const_cast<mfem::real_t *>(direction.GetData()), MaterialSize());
const mfem::Vector gravityDirection(
const_cast<mfem::real_t *>(direction.GetData()) + MaterialSize(), GravitySize()
);
mfem::Vector materialAction(action, 0, MaterialSize());
mfem::Vector gravityAction(action, MaterialSize(), GravitySize());
ApplyMaterialToGravity(materialDirection, gravityAction);
ApplyGravityToMaterial(gravityDirection, materialAction);
materialAction.SyncAliasMemory(action);
gravityAction.SyncAliasMemory(action);
}
void ApplyMaterialToGravity(
const mfem::Vector &materialDirection,
mfem::Vector &gravityAction
) const {
VerifyMaterial(materialDirection, "direction");
VerifyGravity(gravityAction, "action");
const auto densityDirection = MaterialBlock(materialDirection, 0);
const auto surfaceDirection = MaterialBlock(materialDirection, 1);
const auto &gravityOffsets = m_operation->GetGravityOperator().GetStateOffsets();
using GravityForm = utils::blocks::gravity_field_form;
constexpr auto densityBlock =
utils::blocks::get_value_block<GravityForm>(utils::blocks::density_field.mass_term);
constexpr auto displacementBlock =
utils::blocks::get_value_block<GravityForm>(utils::blocks::displacement_field.geometry_term);
m_gravityDirection = 0.0;
auto packedDensityDirection = MutableBlock(m_gravityDirection, gravityOffsets, densityBlock.index);
packedDensityDirection = densityDirection;
packedDensityDirection.SyncAliasMemory(m_gravityDirection);
m_operation->GetDomainDeformation().applyJacobian(
m_operation->GetSurfaceDeformationParameters(), surfaceDirection, m_volumeDisplacement
);
auto packedDisplacementDirection =
MutableBlock(m_gravityDirection, gravityOffsets, displacementBlock.index);
packedDisplacementDirection = m_volumeDisplacement;
packedDisplacementDirection.SyncAliasMemory(m_gravityDirection);
m_operation->GetGravityJacobianOperator().Mult(m_gravityDirection, gravityAction);
}
void ApplyGravityToMaterial(
const mfem::Vector &gravityDirection,
mfem::Vector &materialAction
) const {
VerifyGravity(gravityDirection, "direction");
VerifyMaterial(materialAction, "action");
const auto gravityGradientDirection = GravityBlock(gravityDirection, 0);
const auto gravityPotentialDirection = GravityBlock(gravityDirection, 1);
auto densityAction = MaterialBlock(materialAction, 0);
auto surfaceAction = MaterialBlock(materialAction, 1);
auto enthalpyAction = MaterialBlock(materialAction, 2);
densityAction = 0.0;
m_operation->GetDisplacementOperator().ApplyGravityGradientJacobianAction(
gravityGradientDirection, m_mechanicalAction
);
m_operation->GetDomainDeformation().applyJacobianTranspose(
m_operation->GetSurfaceDeformationParameters(), m_mechanicalAction, surfaceAction
);
m_operation->GetHydrostaticOperator().ApplyGravityPotentialJacobianAction(
gravityPotentialDirection, enthalpyAction
);
m_operation->GetSurfaceConstraintOperator().ApplyJacobianRows(m_zeroEnthalpy, enthalpyAction);
densityAction.SyncAliasMemory(materialAction);
surfaceAction.SyncAliasMemory(materialAction);
enthalpyAction.SyncAliasMemory(materialAction);
}
[[nodiscard]] int MaterialSize() const noexcept {
return m_materialOffsets.Last();
}
[[nodiscard]] int GravitySize() const noexcept {
return m_gravityOffsets.Last();
}
[[nodiscard]] const mfem::Array<int> &GetMaterialOffsets() const noexcept {
return m_materialOffsets;
}
[[nodiscard]] const mfem::Array<int> &GetGravityOffsets() const noexcept {
return m_gravityOffsets;
}
[[nodiscard]] const mfem::Array<int> &GetCombinedOffsets() const noexcept {
return m_combinedOffsets;
}
private:
[[nodiscard]] static int MaterialSizeOf(const operators::PreparedStellarEquilibriumOperator &operation) {
if (!operation.IsPrepared()) {
throw std::logic_error("The stellar-structure cross Jacobian requires a prepared operator.");
}
return operation.GetGravityContext().GetDensityMap().reduced_size() +
operation.GetDomainDeformation().parameterCount() +
operation.GetBarotropicClosureOperator().GetEnthalpySize();
}
[[nodiscard]] static int GravitySizeOf(const operators::PreparedStellarEquilibriumOperator &operation) {
return operation.GetGravityContext().GetGravityGradientMap().reduced_size() +
operation.GetGravityContext().GetGravityPotentialMap().reduced_size();
}
[[nodiscard]] static mfem::Vector MutableBlock(
mfem::Vector &vector,
const mfem::Array<int> &offsets,
const int block
) {
return mfem::Vector(vector, offsets[block], offsets[block + 1] - offsets[block]);
}
[[nodiscard]] mfem::Vector MaterialBlock(
const mfem::Vector &vector,
const int block
) const {
return mfem::Vector(
const_cast<mfem::real_t *>(vector.GetData()) + m_materialOffsets[block],
m_materialOffsets[block + 1] - m_materialOffsets[block]
);
}
[[nodiscard]] mfem::Vector MaterialBlock(
mfem::Vector &vector,
const int block
) const {
return mfem::Vector(
vector, m_materialOffsets[block], m_materialOffsets[block + 1] - m_materialOffsets[block]
);
}
[[nodiscard]] mfem::Vector GravityBlock(
const mfem::Vector &vector,
const int block
) const {
return mfem::Vector(
const_cast<mfem::real_t *>(vector.GetData()) + m_gravityOffsets[block],
m_gravityOffsets[block + 1] - m_gravityOffsets[block]
);
}
void VerifyCombined(
const mfem::Vector &direction,
const mfem::Vector &action
) const {
if (direction.Size() != Width() || action.Size() != Height()) {
throw std::invalid_argument(
"The stellar-structure cross Jacobian requires compatible, preallocated vectors."
);
}
}
void VerifyMaterial(
const mfem::Vector &vector,
const char *role
) const {
if (vector.Size() != MaterialSize()) {
throw std::invalid_argument(
std::string("The stellar-structure material ") + role + " has the wrong size."
);
}
}
void VerifyGravity(
const mfem::Vector &vector,
const char *role
) const {
if (vector.Size() != GravitySize()) {
throw std::invalid_argument(
std::string("The stellar-structure gravity ") + role + " has the wrong size."
);
}
}
const operators::PreparedStellarEquilibriumOperator *m_operation;
mfem::Array<int> m_materialOffsets;
mfem::Array<int> m_gravityOffsets;
mfem::Array<int> m_combinedOffsets;
mutable mfem::Vector m_gravityDirection;
mutable mfem::Vector m_volumeDisplacement;
mutable mfem::Vector m_mechanicalAction;
mfem::Vector m_zeroEnthalpy;
};
struct StellarStructureBlockPreparationReport final {
MaterialSurfaceBlockPreparationReport materialSurface;
GravityFieldBlockPreparationReport gravity;
[[nodiscard]] bool DidAnyWork() const noexcept {
return materialSurface.DidAnyWork() || gravity.DidAnyWork();
}
};
namespace detail {
template <equilibrium::StellarEquilibriumModel Model>
[[nodiscard]] const operators::PreparedStellarEquilibriumOperator &
physicalOperator(const equilibrium::StellarEquilibriumProblem<Model> &problem) {
if constexpr (equilibrium::StellarEquilibriumProblem<Model>::hasFixedCentralDensity) {
return problem.GetPreparedOperator().GetPhysicalOperator();
} else {
return problem.GetPreparedOperator();
}
}
} // namespace detail
template <
equilibrium::StellarEquilibriumModel Model,
typename MaterialComponent,
backend::Registered GravityMassBackend,
backend::ApplicationMode Mode,
GravityFactorizationPolicy GravityPolicy,
StellarStructureFactorizationPolicy StructurePolicy>
class PreparedStellarStructureBlock final : public mfem::Solver {
private:
using Problem = equilibrium::StellarEquilibriumProblem<Model>;
using GravityComponent = GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>;
using Structure = StellarStructureBlock<
MaterialComponent,
GravityComponent,
typename Problem::FormType,
typename Problem::JacobianFormType,
StructurePolicy>;
using MaterialPrepared =
decltype(preconditioning::prepare(std::declval<const Problem &>(), std::declval<MaterialComponent>()));
using GravityPrepared = decltype(preconditioning::prepare(
std::declval<const fem::FEM &>(),
std::declval<const operators::context::gravity_field::GravityFieldGeometryContext &>(),
std::declval<GravityComponent>()
));
public:
PreparedStellarStructureBlock(
const Problem &problem,
Structure structure
)
: mfem::Solver(StructureSize(problem)),
m_problem(std::addressof(problem)),
m_structure(std::move(structure)),
m_materialSurface(
preconditioning::prepare(
problem,
m_structure.materialSurfaceComponent()
)
),
m_gravity(
preconditioning::prepare(
detail::physicalOperator(problem).GetHydrostaticOperator().GetFEM(),
detail::physicalOperator(problem).GetGravityContext().GetGeometryContext(),
m_structure.gravityComponent()
)
),
m_crossCouplings(detail::physicalOperator(problem)),
m_factorization(
m_structure.factorizationPolicy(),
m_materialSurface,
m_gravity,
m_crossCouplings
) {
}
PreparedStellarStructureBlock(const PreparedStellarStructureBlock &) = delete;
PreparedStellarStructureBlock &operator=(const PreparedStellarStructureBlock &) = delete;
PreparedStellarStructureBlock(PreparedStellarStructureBlock &&) = delete;
PreparedStellarStructureBlock &operator=(PreparedStellarStructureBlock &&) = 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 stellar-structure block is stale; refresh it before application.");
}
m_factorization.Mult(rightHandSide, action);
}
[[nodiscard]] bool IsCurrent() const noexcept {
return m_materialSurface.IsCurrent() && m_gravity.IsCurrent() &&
detail::physicalOperator(*m_problem).IsPrepared();
}
[[nodiscard]] StellarStructureBlockPreparationReport Refresh() {
const auto &physical = detail::physicalOperator(*m_problem);
return {
.materialSurface = m_materialSurface.Refresh(physical),
.gravity = m_gravity.Refresh(
physical.GetHydrostaticOperator().GetFEM(), physical.GetGravityContext().GetGeometryContext()
)
};
}
[[nodiscard]] const Structure &GetBlock() const noexcept {
return m_structure;
}
[[nodiscard]] const MaterialPrepared &GetMaterialSurfacePreconditioner() const noexcept {
return m_materialSurface;
}
[[nodiscard]] const GravityPrepared &GetGravityPreconditioner() const noexcept {
return m_gravity;
}
[[nodiscard]] const StellarStructureCrossJacobianOperator &GetCrossCouplings() const noexcept {
return m_crossCouplings;
}
[[nodiscard]] const StellarStructureFactorizationOperator<
StructurePolicy,
StellarStructureCrossJacobianOperator> &
GetFactorization() const noexcept {
return m_factorization;
}
private:
[[nodiscard]] static int StructureSize(const Problem &problem) {
const auto &physical = detail::physicalOperator(problem);
return physical.GetGravityContext().GetDensityMap().reduced_size() +
physical.GetDomainDeformation().parameterCount() +
physical.GetBarotropicClosureOperator().GetEnthalpySize() +
physical.GetGravityContext().GetGravityGradientMap().reduced_size() +
physical.GetGravityContext().GetGravityPotentialMap().reduced_size();
}
const Problem *m_problem;
Structure m_structure;
MaterialPrepared m_materialSurface;
GravityPrepared m_gravity;
StellarStructureCrossJacobianOperator m_crossCouplings;
StellarStructureFactorizationOperator<StructurePolicy, StellarStructureCrossJacobianOperator> m_factorization;
};
template <
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
typename MaterialComponent,
backend::Registered GravityMassBackend,
backend::ApplicationMode Mode,
GravityFactorizationPolicy GravityPolicy,
StellarStructureFactorizationPolicy StructurePolicy>
[[nodiscard]] constexpr auto stellarStructureBlock(
const Problem &,
MaterialComponent materialComponent,
GravityFieldBlock<
GravityMassBackend,
backend::HypreBoomerAMG<Mode>,
GravityPolicy> gravityComponent,
StructurePolicy policy
) {
using ProblemType = std::remove_cvref_t<Problem>;
return StellarStructureBlock<
MaterialComponent, GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>,
typename ProblemType::FormType, typename ProblemType::JacobianFormType, StructurePolicy>{
std::move(materialComponent), std::move(gravityComponent), std::move(policy)
};
}
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
[[nodiscard]] constexpr auto stellarStructureBlock(const Problem &problem) {
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
auto material = materialSurfaceBlock(problem);
auto gravity = GravityFieldBlock(
backend::MatrixFreeChebyshev{.order = 5, .powerIterations = 20},
FixedAMG{backend::FixedCycles{.cycles = 3}}, GravityApproximateLDU{}
);
return stellarStructureBlock(problem, std::move(material), std::move(gravity), IndependentStellarSubsystems{});
}
template <
equilibrium::StellarEquilibriumModel Model,
typename MaterialComponent,
backend::Registered GravityMassBackend,
backend::ApplicationMode Mode,
GravityFactorizationPolicy GravityPolicy,
StellarStructureFactorizationPolicy StructurePolicy>
[[nodiscard]] auto prepare(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
StellarStructureBlock<
MaterialComponent,
GravityFieldBlock<
GravityMassBackend,
backend::HypreBoomerAMG<Mode>,
GravityPolicy>,
typename equilibrium::StellarEquilibriumProblem<Model>::FormType,
typename equilibrium::StellarEquilibriumProblem<Model>::JacobianFormType,
StructurePolicy> structure
) {
return PreparedStellarStructureBlock<
Model, MaterialComponent, GravityMassBackend, Mode, GravityPolicy, StructurePolicy>{
problem, std::move(structure)
};
}
} // namespace mean_field::preconditioning

View File

@@ -29,6 +29,25 @@ export namespace mean_field::surface {
InputQuantities>)) &&
...)> { };
template <typename RelationType, typename Bindings, typename EquationOfState>
struct PressureSurfaceRelationMatchesBindings : std::false_type { };
template <typename OutputQuantity, typename... InputQuantities, typename Bindings, typename EquationOfState>
struct PressureSurfaceRelationMatchesBindings<
eos::Relation<OutputQuantity, InputQuantities...>,
Bindings,
EquationOfState>
: std::bool_constant<
(surfaceBindingCount<Bindings, OutputQuantity> == 1) &&
(std::same_as<eos::quantity::Pressure, InputQuantities> || ...) &&
((std::same_as<eos::quantity::Pressure, InputQuantities> ||
(surfaceBindingCount<Bindings, InputQuantities> == 1 &&
eos::SupportsPartialDerivative<
EquationOfState,
eos::Relation<OutputQuantity, InputQuantities...>,
InputQuantities>)) &&
...)> { };
template <typename Catalog, typename Formulation, typename EquationOfState>
struct MatchingPressureSurfaceRelations;
@@ -44,6 +63,24 @@ export namespace mean_field::surface {
static constexpr std::size_t count = std::tuple_size_v<Tuple>;
};
template <typename Catalog, typename Bindings, typename EquationOfState>
struct MatchingPressureSurfaceRelationsForBindings;
template <typename... Relations, typename Bindings, typename EquationOfState>
struct MatchingPressureSurfaceRelationsForBindings<
eos::RelationCatalog<Relations...>,
Bindings,
EquationOfState> {
using Tuple = decltype(std::tuple_cat(
std::conditional_t<
PressureSurfaceRelationMatchesBindings<Relations, Bindings, EquationOfState>::value,
std::tuple<Relations>,
std::tuple<>>{}...
));
static constexpr std::size_t count = std::tuple_size_v<Tuple>;
};
template <std::size_t Count, typename Tuple> struct UniquePressureSurfaceRelation {
using Type = void;
};
@@ -52,6 +89,17 @@ export namespace mean_field::surface {
using Type = std::tuple_element_t<0, Tuple>;
};
template <std::size_t Count, typename Tuple, typename Bindings> struct UniquePressureSurfaceFormulation {
using Type = void;
};
template <typename Tuple, typename Bindings> struct UniquePressureSurfaceFormulation<1, Tuple, Bindings> {
using Relation = std::tuple_element_t<0, Tuple>;
using CarrierQuantity = eos::RelationOutputT<Relation>;
using CarrierField = SurfaceFieldForQuantityT<Bindings, CarrierQuantity>;
using Type = SurfaceConstraintFormulation<CarrierQuantity, CarrierField, Bindings>;
};
template <typename Dependencies, typename Field> struct AppendSurfaceDependency;
template <typename RowField, typename... StateFields, typename Field>
@@ -103,6 +151,16 @@ export namespace mean_field::surface {
using Relation = typename UniquePressureSurfaceRelation<Matches::count, typename Matches::Tuple>::Type;
};
template <ValidSurfaceStateBindings Bindings, eos::EquationOfStateModel EquationOfState>
struct PressureSurfaceFormulationCompilation {
using Matches = MatchingPressureSurfaceRelationsForBindings<
typename EquationOfState::Relations,
Bindings,
EquationOfState>;
using Formulation =
typename UniquePressureSurfaceFormulation<Matches::count, typename Matches::Tuple, Bindings>::Type;
};
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
requires(PressureSurfaceCompilation<Formulation, EquationOfState>::Matches::count == 1)
struct CompiledPressureSurfaceConstraintType {
@@ -119,6 +177,19 @@ export namespace mean_field::surface {
(detail::PressureSurfaceCompilation<std::remove_cvref_t<Formulation>, std::remove_cvref_t<EquationOfState>>::
Matches::count == 1);
template <typename Bindings, typename EquationOfState>
concept PressureSurfaceFormulationCompilable =
ValidSurfaceStateBindings<Bindings> && eos::EquationOfStateModel<EquationOfState> &&
(detail::PressureSurfaceFormulationCompilation<
std::remove_cvref_t<Bindings>,
std::remove_cvref_t<EquationOfState>>::Matches::count == 1);
template <ValidSurfaceStateBindings Bindings, eos::EquationOfStateModel EquationOfState>
requires PressureSurfaceFormulationCompilable<Bindings, EquationOfState>
using CompiledPressureSurfaceFormulationT = typename detail::PressureSurfaceFormulationCompilation<
std::remove_cvref_t<Bindings>,
std::remove_cvref_t<EquationOfState>>::Formulation;
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
requires PressureSurfaceCompilable<Formulation, EquationOfState>
using CompiledPressureSurfaceConstraintT = typename detail::CompiledPressureSurfaceConstraintType<