feat(libmeanfield): variadic refactor
also added normaliztion operator
This commit is contained in:
6
libmeanfield/interface/normalization/normalization.cppm
Normal file
6
libmeanfield/interface/normalization/normalization.cppm
Normal file
@@ -0,0 +1,6 @@
|
||||
export module mean_field:normalization;
|
||||
|
||||
export import :normalization.plan;
|
||||
export import :normalization.physical_riesz;
|
||||
export import :normalization.operators;
|
||||
export import :normalization.stellar_equilibrium;
|
||||
621
libmeanfield/interface/normalization/operators.cppm
Normal file
621
libmeanfield/interface/normalization/operators.cppm
Normal file
@@ -0,0 +1,621 @@
|
||||
module;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:normalization.operators;
|
||||
|
||||
export import :normalization.physical_riesz;
|
||||
|
||||
export namespace mean_field::normalization {
|
||||
class DiagonalNormalization final {
|
||||
public:
|
||||
DiagonalNormalization(
|
||||
mfem::Vector stateToNormalized,
|
||||
mfem::Vector residualToNormalized
|
||||
)
|
||||
: m_stateToNormalized(std::move(stateToNormalized)),
|
||||
m_residualToNormalized(std::move(residualToNormalized)) {
|
||||
ValidateFactors(m_stateToNormalized, "state");
|
||||
ValidateFactors(m_residualToNormalized, "residual");
|
||||
}
|
||||
|
||||
[[nodiscard]] static DiagonalNormalization Identity(
|
||||
const int stateSize,
|
||||
const int residualSize
|
||||
) {
|
||||
if (stateSize < 0 || residualSize < 0) {
|
||||
throw std::invalid_argument("Normalization dimensions cannot be negative.");
|
||||
}
|
||||
mfem::Vector state(stateSize);
|
||||
mfem::Vector residual(residualSize);
|
||||
state = 1.0;
|
||||
residual = 1.0;
|
||||
return {std::move(state), std::move(residual)};
|
||||
}
|
||||
|
||||
[[nodiscard]] int StateSize() const noexcept {
|
||||
return m_stateToNormalized.Size();
|
||||
}
|
||||
|
||||
[[nodiscard]] int ResidualSize() const noexcept {
|
||||
return m_residualToNormalized.Size();
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector &StateFactors() const noexcept {
|
||||
return m_stateToNormalized;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector &ResidualFactors() const noexcept {
|
||||
return m_residualToNormalized;
|
||||
}
|
||||
|
||||
void NormalizeState(
|
||||
const mfem::Vector &physical,
|
||||
mfem::Vector &normalized
|
||||
) const {
|
||||
Apply(m_stateToNormalized, physical, normalized, false, "state");
|
||||
}
|
||||
|
||||
void DenormalizeState(
|
||||
const mfem::Vector &normalized,
|
||||
mfem::Vector &physical
|
||||
) const {
|
||||
Apply(m_stateToNormalized, normalized, physical, true, "state");
|
||||
}
|
||||
|
||||
void NormalizeResidual(
|
||||
const mfem::Vector &physical,
|
||||
mfem::Vector &normalized
|
||||
) const {
|
||||
Apply(m_residualToNormalized, physical, normalized, false, "residual");
|
||||
}
|
||||
|
||||
void DenormalizeResidual(
|
||||
const mfem::Vector &normalized,
|
||||
mfem::Vector &physical
|
||||
) const {
|
||||
Apply(m_residualToNormalized, normalized, physical, true, "residual");
|
||||
}
|
||||
|
||||
[[nodiscard]] double LocalStateNormSquared(const mfem::Vector &physical) const {
|
||||
return LocalNormSquared(m_stateToNormalized, physical, "state");
|
||||
}
|
||||
|
||||
[[nodiscard]] double LocalResidualNormSquared(const mfem::Vector &physical) const {
|
||||
return LocalNormSquared(m_residualToNormalized, physical, "residual");
|
||||
}
|
||||
|
||||
private:
|
||||
static void ValidateFactors(
|
||||
const mfem::Vector &factors,
|
||||
const char *role
|
||||
) {
|
||||
for (int index = 0; index < factors.Size(); ++index) {
|
||||
if (!std::isfinite(factors(index)) || factors(index) <= 0.0) {
|
||||
throw std::invalid_argument(
|
||||
std::string("The ") + role + " normalization factors must be finite and positive."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Apply(
|
||||
const mfem::Vector &factors,
|
||||
const mfem::Vector &input,
|
||||
mfem::Vector &output,
|
||||
const bool inverse,
|
||||
const char *role
|
||||
) {
|
||||
if (input.Size() != factors.Size()) {
|
||||
throw std::invalid_argument(std::string("The ") + role + " vector has the wrong size.");
|
||||
}
|
||||
const bool exactAlias = input.GetData() == output.GetData() && input.Size() == output.Size();
|
||||
if (!exactAlias) {
|
||||
output.SetSize(input.Size());
|
||||
}
|
||||
for (int index = 0; index < input.Size(); ++index) {
|
||||
const double value = input(index);
|
||||
output(index) = inverse ? value / factors(index) : factors(index) * value;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] static double LocalNormSquared(
|
||||
const mfem::Vector &factors,
|
||||
const mfem::Vector &physical,
|
||||
const char *role
|
||||
) {
|
||||
if (physical.Size() != factors.Size()) {
|
||||
throw std::invalid_argument(std::string("The ") + role + " vector has the wrong size.");
|
||||
}
|
||||
double normSquared = 0.0;
|
||||
for (int index = 0; index < physical.Size(); ++index) {
|
||||
const double normalized = factors(index) * physical(index);
|
||||
normSquared += normalized * normalized;
|
||||
}
|
||||
return normSquared;
|
||||
}
|
||||
|
||||
mfem::Vector m_stateToNormalized;
|
||||
mfem::Vector m_residualToNormalized;
|
||||
};
|
||||
|
||||
/* Detection-safe public operation for an ordinary third-party runtime
|
||||
* policy. The exact policy is recovered from the problem type and must own
|
||||
* every method in its compiled plan. Its implementation remains beside
|
||||
* the policy and is found by ADL, so adding a normalization family does
|
||||
* not edit a library registry or switch. */
|
||||
template <typename Problem>
|
||||
concept RuntimePreparedNormalizationOperation =
|
||||
requires(const std::remove_cvref_t<Problem> &problem) {
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
|
||||
typename std::remove_cvref_t<Problem>::FormType;
|
||||
requires RuntimePreparedNormalizationFor<
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||
typename std::remove_cvref_t<Problem>::FormType>;
|
||||
{
|
||||
problem.GetNormalizationPrescription()
|
||||
} -> std::same_as<const typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType &>;
|
||||
{
|
||||
prepareStellarNormalization(
|
||||
problem.GetNormalizationPrescription(),
|
||||
problem)
|
||||
} -> std::same_as<DiagonalNormalization>;
|
||||
};
|
||||
|
||||
template <typename Form>
|
||||
requires utils::blocks::block_form_is_valid_v<Form>
|
||||
class DiagonalNormalizationBuilder final {
|
||||
public:
|
||||
explicit DiagonalNormalizationBuilder(const utils::blocks::form_layout<Form> &layout)
|
||||
: m_layout(&layout),
|
||||
m_stateFactors(layout.value_offsets().Last()),
|
||||
m_residualFactors(layout.residual_offsets().Last()) {
|
||||
}
|
||||
|
||||
explicit DiagonalNormalizationBuilder(
|
||||
utils::blocks::form_layout<Form> &&
|
||||
) = delete;
|
||||
|
||||
explicit DiagonalNormalizationBuilder(
|
||||
const utils::blocks::form_layout<Form> &&
|
||||
) = delete;
|
||||
|
||||
template <typename Block>
|
||||
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
|
||||
void SetValueBlock(
|
||||
const double physicalScale,
|
||||
const mfem::Vector &primalGramDiagonal
|
||||
) {
|
||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
|
||||
RequireUnassigned(m_valueAssigned[block], "value");
|
||||
AssignBlock(
|
||||
m_stateFactors,
|
||||
m_layout->value_offsets()[block],
|
||||
m_layout->value_offsets()[block + 1] - m_layout->value_offsets()[block],
|
||||
physicalScale,
|
||||
primalGramDiagonal,
|
||||
false
|
||||
);
|
||||
m_valueAssigned[block] = true;
|
||||
}
|
||||
|
||||
template <typename Block>
|
||||
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
||||
void SetResidualBlock(
|
||||
const double physicalScale,
|
||||
const mfem::Vector &primalGramDiagonal
|
||||
) {
|
||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||
RequireUnassigned(m_residualAssigned[block], "residual");
|
||||
AssignBlock(
|
||||
m_residualFactors,
|
||||
m_layout->residual_offsets()[block],
|
||||
m_layout->residual_offsets()[block + 1] - m_layout->residual_offsets()[block],
|
||||
physicalScale,
|
||||
primalGramDiagonal,
|
||||
true
|
||||
);
|
||||
m_residualAssigned[block] = true;
|
||||
}
|
||||
|
||||
template <typename Block>
|
||||
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
|
||||
void SetValueGlobal(const double physicalScale) {
|
||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
|
||||
SetConstantMetricValueBlock<Block>(physicalScale, BlockSize(m_layout->value_offsets(), block));
|
||||
}
|
||||
|
||||
template <typename Block>
|
||||
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
||||
void SetResidualGlobal(const double physicalScale) {
|
||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||
mfem::Vector metric(BlockSize(m_layout->residual_offsets(), block));
|
||||
metric = 1.0;
|
||||
SetResidualBlock<Block>(physicalScale, metric);
|
||||
}
|
||||
|
||||
template <typename Block>
|
||||
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
||||
void SetHybridResidualBlock(
|
||||
const double physicalScale,
|
||||
const mfem::Vector &bulkPrimalGramDiagonal,
|
||||
const std::span<const int> pointRows,
|
||||
const double pointMetric = 1.0
|
||||
) {
|
||||
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||
const int size = BlockSize(m_layout->residual_offsets(), block);
|
||||
if (bulkPrimalGramDiagonal.Size() != size) {
|
||||
throw std::invalid_argument("The hybrid residual Gram diagonal has the wrong size.");
|
||||
}
|
||||
ValidateMetric(pointMetric);
|
||||
|
||||
std::vector<bool> isPointRow(static_cast<std::size_t>(size), false);
|
||||
for (const int row : pointRows) {
|
||||
if (row < 0 || row >= size) {
|
||||
throw std::out_of_range("A hybrid point row lies outside its residual block.");
|
||||
}
|
||||
if (isPointRow[static_cast<std::size_t>(row)]) {
|
||||
throw std::invalid_argument("A hybrid point row was supplied more than once.");
|
||||
}
|
||||
isPointRow[static_cast<std::size_t>(row)] = true;
|
||||
}
|
||||
|
||||
mfem::Vector metric(size);
|
||||
for (int row = 0; row < size; ++row) {
|
||||
metric(row) = isPointRow[static_cast<std::size_t>(row)]
|
||||
? pointMetric
|
||||
: bulkPrimalGramDiagonal(row);
|
||||
}
|
||||
SetResidualBlock<Block>(physicalScale, metric);
|
||||
}
|
||||
|
||||
[[nodiscard]] DiagonalNormalization Build() && {
|
||||
for (const bool assigned : m_valueAssigned) {
|
||||
if (!assigned) {
|
||||
throw std::logic_error("The normalization is missing a value block.");
|
||||
}
|
||||
}
|
||||
for (const bool assigned : m_residualAssigned) {
|
||||
if (!assigned) {
|
||||
throw std::logic_error("The normalization is missing a residual block.");
|
||||
}
|
||||
}
|
||||
return {std::move(m_stateFactors), std::move(m_residualFactors)};
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Block>
|
||||
void SetConstantMetricValueBlock(
|
||||
const double physicalScale,
|
||||
const int size
|
||||
) {
|
||||
mfem::Vector metric(size);
|
||||
metric = 1.0;
|
||||
SetValueBlock<Block>(physicalScale, metric);
|
||||
}
|
||||
|
||||
[[nodiscard]] static int BlockSize(
|
||||
const mfem::Array<int> &offsets,
|
||||
const int block
|
||||
) noexcept {
|
||||
return offsets[block + 1] - offsets[block];
|
||||
}
|
||||
|
||||
static void RequireUnassigned(
|
||||
const bool assigned,
|
||||
const char *role
|
||||
) {
|
||||
if (assigned) {
|
||||
throw std::logic_error(std::string("The ") + role + " block normalization was assigned twice.");
|
||||
}
|
||||
}
|
||||
|
||||
static void ValidateMetric(const double metric) {
|
||||
if (!std::isfinite(metric) || metric <= 0.0) {
|
||||
throw std::invalid_argument("Every Riesz Gram diagonal entry must be finite and positive.");
|
||||
}
|
||||
}
|
||||
|
||||
static void AssignBlock(
|
||||
mfem::Vector &factors,
|
||||
const int offset,
|
||||
const int size,
|
||||
const double physicalScale,
|
||||
const mfem::Vector &primalGramDiagonal,
|
||||
const bool dual
|
||||
) {
|
||||
if (!std::isfinite(physicalScale) || physicalScale <= 0.0) {
|
||||
throw std::invalid_argument("A physical normalization scale must be finite and positive.");
|
||||
}
|
||||
if (primalGramDiagonal.Size() != size) {
|
||||
throw std::invalid_argument("A Riesz Gram diagonal has the wrong block size.");
|
||||
}
|
||||
for (int index = 0; index < size; ++index) {
|
||||
const double metric = primalGramDiagonal(index);
|
||||
ValidateMetric(metric);
|
||||
const double rieszFactor = std::sqrt(metric);
|
||||
const double factor = dual
|
||||
? 1.0 / (physicalScale * rieszFactor)
|
||||
: rieszFactor / physicalScale;
|
||||
if (!std::isfinite(factor) || factor <= 0.0) {
|
||||
throw std::overflow_error("A normalization factor is not finite and positive.");
|
||||
}
|
||||
factors(offset + index) = factor;
|
||||
}
|
||||
}
|
||||
|
||||
const utils::blocks::form_layout<Form> *m_layout;
|
||||
mfem::Vector m_stateFactors;
|
||||
mfem::Vector m_residualFactors;
|
||||
std::array<bool, Form::value_block_count> m_valueAssigned{};
|
||||
std::array<bool, Form::residual_block_count> m_residualAssigned{};
|
||||
};
|
||||
|
||||
class ScaledJacobianOperator final : public mfem::Operator {
|
||||
public:
|
||||
ScaledJacobianOperator(
|
||||
const mfem::Operator &physicalJacobian,
|
||||
const DiagonalNormalization &normalization
|
||||
)
|
||||
: mfem::Operator(normalization.ResidualSize(), normalization.StateSize()),
|
||||
m_physicalJacobian(&physicalJacobian),
|
||||
m_normalization(&normalization),
|
||||
m_physicalDirection(normalization.StateSize()),
|
||||
m_physicalAction(normalization.ResidualSize()) {
|
||||
if (physicalJacobian.Width() != normalization.StateSize() ||
|
||||
physicalJacobian.Height() != normalization.ResidualSize()) {
|
||||
throw std::invalid_argument("The physical Jacobian and normalization dimensions do not agree.");
|
||||
}
|
||||
}
|
||||
|
||||
ScaledJacobianOperator(
|
||||
mfem::Operator &&,
|
||||
const DiagonalNormalization &
|
||||
) = delete;
|
||||
|
||||
ScaledJacobianOperator(
|
||||
const mfem::Operator &&,
|
||||
const DiagonalNormalization &
|
||||
) = delete;
|
||||
|
||||
ScaledJacobianOperator(
|
||||
const mfem::Operator &,
|
||||
DiagonalNormalization &&
|
||||
) = delete;
|
||||
|
||||
ScaledJacobianOperator(
|
||||
const mfem::Operator &,
|
||||
const DiagonalNormalization &&
|
||||
) = delete;
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &normalizedDirection,
|
||||
mfem::Vector &normalizedAction
|
||||
) const override {
|
||||
m_normalization->DenormalizeState(normalizedDirection, m_physicalDirection);
|
||||
m_physicalJacobian->Mult(m_physicalDirection, m_physicalAction);
|
||||
m_normalization->NormalizeResidual(m_physicalAction, normalizedAction);
|
||||
}
|
||||
|
||||
private:
|
||||
const mfem::Operator *m_physicalJacobian;
|
||||
const DiagonalNormalization *m_normalization;
|
||||
mutable mfem::Vector m_physicalDirection;
|
||||
mutable mfem::Vector m_physicalAction;
|
||||
};
|
||||
|
||||
class ScaledInverseOperator final : public mfem::Operator {
|
||||
public:
|
||||
ScaledInverseOperator(
|
||||
const mfem::Operator &physicalInverse,
|
||||
const DiagonalNormalization &normalization
|
||||
)
|
||||
: mfem::Operator(normalization.StateSize(), normalization.ResidualSize()),
|
||||
m_physicalInverse(&physicalInverse),
|
||||
m_normalization(&normalization),
|
||||
m_physicalResidual(normalization.ResidualSize()),
|
||||
m_physicalCorrection(normalization.StateSize()) {
|
||||
if (physicalInverse.Width() != normalization.ResidualSize() ||
|
||||
physicalInverse.Height() != normalization.StateSize()) {
|
||||
throw std::invalid_argument("The physical inverse and normalization dimensions do not agree.");
|
||||
}
|
||||
}
|
||||
|
||||
ScaledInverseOperator(
|
||||
mfem::Operator &&,
|
||||
const DiagonalNormalization &
|
||||
) = delete;
|
||||
|
||||
ScaledInverseOperator(
|
||||
const mfem::Operator &&,
|
||||
const DiagonalNormalization &
|
||||
) = delete;
|
||||
|
||||
ScaledInverseOperator(
|
||||
const mfem::Operator &,
|
||||
DiagonalNormalization &&
|
||||
) = delete;
|
||||
|
||||
ScaledInverseOperator(
|
||||
const mfem::Operator &,
|
||||
const DiagonalNormalization &&
|
||||
) = delete;
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &normalizedResidual,
|
||||
mfem::Vector &normalizedCorrection
|
||||
) const override {
|
||||
m_normalization->DenormalizeResidual(normalizedResidual, m_physicalResidual);
|
||||
m_physicalInverse->Mult(m_physicalResidual, m_physicalCorrection);
|
||||
m_normalization->NormalizeState(m_physicalCorrection, normalizedCorrection);
|
||||
}
|
||||
|
||||
private:
|
||||
const mfem::Operator *m_physicalInverse;
|
||||
const DiagonalNormalization *m_normalization;
|
||||
mutable mfem::Vector m_physicalResidual;
|
||||
mutable mfem::Vector m_physicalCorrection;
|
||||
};
|
||||
|
||||
struct ScaledPreconditionerStatistics final {
|
||||
std::uint64_t operatorBindings{0};
|
||||
std::uint64_t applications{0};
|
||||
};
|
||||
|
||||
/*
|
||||
* Solver-compatible realization of R^{-1} M^{-1} L^{-1}. The wrapped
|
||||
* inverse always sees the dimensional Jacobian, even when an MFEM Krylov
|
||||
* solver binds this object to the normalized Jacobian L J R.
|
||||
*/
|
||||
class ScaledPreconditioner final : public mfem::Solver {
|
||||
public:
|
||||
ScaledPreconditioner(
|
||||
mfem::Solver &physicalInverse,
|
||||
const mfem::Operator &physicalJacobian,
|
||||
const mfem::Operator &normalizedJacobian,
|
||||
const DiagonalNormalization &normalization
|
||||
)
|
||||
: mfem::Solver(
|
||||
normalization.StateSize(),
|
||||
normalization.ResidualSize(),
|
||||
physicalInverse.iterative_mode
|
||||
),
|
||||
m_physicalInverse(&physicalInverse),
|
||||
m_physicalJacobian(&physicalJacobian),
|
||||
m_expectedNormalizedJacobian(&normalizedJacobian),
|
||||
m_normalization(&normalization),
|
||||
m_physicalResidual(normalization.ResidualSize()),
|
||||
m_physicalCorrection(normalization.StateSize()) {
|
||||
if (physicalInverse.Width() != normalization.ResidualSize() ||
|
||||
physicalInverse.Height() != normalization.StateSize() ||
|
||||
physicalJacobian.Width() != normalization.StateSize() ||
|
||||
physicalJacobian.Height() != normalization.ResidualSize()) {
|
||||
throw std::invalid_argument(
|
||||
"The physical preconditioner, Jacobian, and normalization dimensions do not agree."
|
||||
);
|
||||
}
|
||||
SetOperator(normalizedJacobian);
|
||||
}
|
||||
|
||||
ScaledPreconditioner(
|
||||
mfem::Solver &,
|
||||
mfem::Operator &&,
|
||||
const mfem::Operator &,
|
||||
const DiagonalNormalization &
|
||||
) = delete;
|
||||
|
||||
ScaledPreconditioner(
|
||||
mfem::Solver &,
|
||||
const mfem::Operator &&,
|
||||
const mfem::Operator &,
|
||||
const DiagonalNormalization &
|
||||
) = delete;
|
||||
|
||||
ScaledPreconditioner(
|
||||
mfem::Solver &,
|
||||
const mfem::Operator &,
|
||||
mfem::Operator &&,
|
||||
const DiagonalNormalization &
|
||||
) = delete;
|
||||
|
||||
ScaledPreconditioner(
|
||||
mfem::Solver &,
|
||||
const mfem::Operator &,
|
||||
const mfem::Operator &&,
|
||||
const DiagonalNormalization &
|
||||
) = delete;
|
||||
|
||||
ScaledPreconditioner(
|
||||
mfem::Solver &,
|
||||
const mfem::Operator &,
|
||||
const mfem::Operator &,
|
||||
DiagonalNormalization &&
|
||||
) = delete;
|
||||
|
||||
ScaledPreconditioner(
|
||||
mfem::Solver &,
|
||||
const mfem::Operator &,
|
||||
const mfem::Operator &,
|
||||
const DiagonalNormalization &&
|
||||
) = delete;
|
||||
|
||||
ScaledPreconditioner(const ScaledPreconditioner &) = delete;
|
||||
ScaledPreconditioner &operator=(const ScaledPreconditioner &) = delete;
|
||||
ScaledPreconditioner(ScaledPreconditioner &&) = delete;
|
||||
ScaledPreconditioner &operator=(ScaledPreconditioner &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &normalizedJacobian) override {
|
||||
if (normalizedJacobian.Width() != Width() || normalizedJacobian.Height() != Height()) {
|
||||
throw std::invalid_argument(
|
||||
"The scaled preconditioner received an incompatible normalized Jacobian."
|
||||
);
|
||||
}
|
||||
if (&normalizedJacobian != m_expectedNormalizedJacobian) {
|
||||
throw std::invalid_argument(
|
||||
"The scaled preconditioner cannot be rebound to a different normalized Jacobian."
|
||||
);
|
||||
}
|
||||
m_physicalInverse->SetOperator(*m_physicalJacobian);
|
||||
m_normalizedJacobian = &normalizedJacobian;
|
||||
++m_statistics.operatorBindings;
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &normalizedResidual,
|
||||
mfem::Vector &normalizedCorrection
|
||||
) const override {
|
||||
if (m_normalizedJacobian == nullptr) {
|
||||
throw std::logic_error("The scaled preconditioner has not been bound to a normalized Jacobian.");
|
||||
}
|
||||
if (normalizedResidual.Size() != Width() || normalizedCorrection.Size() != Height()) {
|
||||
throw std::invalid_argument(
|
||||
"The scaled preconditioner requires compatible, preallocated normalized vectors."
|
||||
);
|
||||
}
|
||||
m_normalization->DenormalizeResidual(normalizedResidual, m_physicalResidual);
|
||||
m_physicalInverse->Mult(m_physicalResidual, m_physicalCorrection);
|
||||
m_normalization->NormalizeState(m_physicalCorrection, normalizedCorrection);
|
||||
++m_statistics.applications;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Solver &GetPhysicalInverse() const noexcept {
|
||||
return *m_physicalInverse;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
|
||||
return *m_physicalJacobian;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator &GetNormalizedJacobian() const {
|
||||
if (m_normalizedJacobian == nullptr) {
|
||||
throw std::logic_error("The scaled preconditioner has not been bound to a normalized Jacobian.");
|
||||
}
|
||||
return *m_normalizedJacobian;
|
||||
}
|
||||
|
||||
[[nodiscard]] const ScaledPreconditionerStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
mfem::Solver *m_physicalInverse;
|
||||
const mfem::Operator *m_physicalJacobian;
|
||||
const mfem::Operator *m_expectedNormalizedJacobian;
|
||||
const mfem::Operator *m_normalizedJacobian{nullptr};
|
||||
const DiagonalNormalization *m_normalization;
|
||||
mutable mfem::Vector m_physicalResidual;
|
||||
mutable mfem::Vector m_physicalCorrection;
|
||||
mutable ScaledPreconditionerStatistics m_statistics;
|
||||
};
|
||||
} // namespace mean_field::normalization
|
||||
728
libmeanfield/interface/normalization/physical_riesz.cppm
Normal file
728
libmeanfield/interface/normalization/physical_riesz.cppm
Normal file
@@ -0,0 +1,728 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
export module mean_field:normalization.physical_riesz;
|
||||
|
||||
export import :dimensions.quantities;
|
||||
export import :field.mfem;
|
||||
export import :model.specifications;
|
||||
export import :normalization.plan;
|
||||
|
||||
export namespace mean_field::normalization {
|
||||
struct Unnormalized final : NormalizationPrescriptionTag { };
|
||||
|
||||
struct ReferenceGeometry final { };
|
||||
|
||||
struct FixedMassBranchReference final { };
|
||||
|
||||
template <typename Candidate>
|
||||
concept RieszGeometryPolicy = std::same_as<std::remove_cvref_t<Candidate>, ReferenceGeometry>;
|
||||
|
||||
template <typename Candidate>
|
||||
concept ReferenceScalePolicy = std::same_as<std::remove_cvref_t<Candidate>, FixedMassBranchReference>;
|
||||
|
||||
template <
|
||||
RieszGeometryPolicy GeometryPolicy = ReferenceGeometry,
|
||||
ReferenceScalePolicy ScalePolicy = FixedMassBranchReference>
|
||||
class PhysicalRieszDiagonal final : public NormalizationPrescriptionTag {
|
||||
public:
|
||||
using Geometry = GeometryPolicy;
|
||||
using ScaleSource = ScalePolicy;
|
||||
|
||||
explicit PhysicalRieszDiagonal(
|
||||
const dimensions::LengthValue referenceRadius,
|
||||
const double gravitationalConstant = 1.0
|
||||
)
|
||||
: m_referenceRadius(referenceRadius),
|
||||
m_gravitationalConstant(gravitationalConstant) {
|
||||
if (!std::isfinite(referenceRadius.value()) || referenceRadius.value() <= 0.0) {
|
||||
throw std::invalid_argument("Physical Riesz normalization requires a finite, positive branch radius.");
|
||||
}
|
||||
if (!std::isfinite(gravitationalConstant) || gravitationalConstant <= 0.0) {
|
||||
throw std::invalid_argument(
|
||||
"Physical Riesz normalization requires a finite, positive gravitational constant."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] dimensions::LengthValue referenceRadius() const noexcept {
|
||||
return m_referenceRadius;
|
||||
}
|
||||
|
||||
[[nodiscard]] double gravitationalConstant() const noexcept {
|
||||
return m_gravitationalConstant;
|
||||
}
|
||||
|
||||
private:
|
||||
dimensions::LengthValue m_referenceRadius;
|
||||
double m_gravitationalConstant;
|
||||
};
|
||||
|
||||
PhysicalRieszDiagonal(dimensions::LengthValue, double = 1.0)
|
||||
-> PhysicalRieszDiagonal<ReferenceGeometry, FixedMassBranchReference>;
|
||||
|
||||
template <typename Candidate> struct IsPhysicalRieszDiagonal : std::false_type { };
|
||||
|
||||
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource>
|
||||
struct IsPhysicalRieszDiagonal<PhysicalRieszDiagonal<Geometry, ScaleSource>> : std::true_type { };
|
||||
|
||||
template <typename Candidate>
|
||||
concept PhysicalRieszDiagonalPrescription =
|
||||
IsPhysicalRieszDiagonal<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
struct StellarCharacteristicScales final {
|
||||
dimensions::MassValue mass;
|
||||
dimensions::LengthValue radius;
|
||||
double gravitationalConstant;
|
||||
double density;
|
||||
double acceleration;
|
||||
double inverseTimeSquared;
|
||||
double specificEnergy;
|
||||
double pressure;
|
||||
double angularVelocity;
|
||||
double angularMomentum;
|
||||
double force;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline StellarCharacteristicScales deriveStellarCharacteristicScales(
|
||||
const dimensions::MassValue mass,
|
||||
const dimensions::LengthValue radius,
|
||||
const double gravitationalConstant = 1.0
|
||||
) {
|
||||
const double massValue = mass.value();
|
||||
const double radiusValue = radius.value();
|
||||
if (!std::isfinite(massValue) || massValue <= 0.0) {
|
||||
throw std::invalid_argument("Characteristic stellar scales require a finite, positive mass.");
|
||||
}
|
||||
if (!std::isfinite(radiusValue) || radiusValue <= 0.0) {
|
||||
throw std::invalid_argument("Characteristic stellar scales require a finite, positive radius.");
|
||||
}
|
||||
if (!std::isfinite(gravitationalConstant) || gravitationalConstant <= 0.0) {
|
||||
throw std::invalid_argument(
|
||||
"Characteristic stellar scales require a finite, positive gravitational constant."
|
||||
);
|
||||
}
|
||||
|
||||
const double radiusSquared = radiusValue * radiusValue;
|
||||
const double radiusCubed = radiusSquared * radiusValue;
|
||||
const double density = massValue / radiusCubed;
|
||||
const double acceleration = gravitationalConstant * massValue / radiusSquared;
|
||||
const double inverseTimeSquared = gravitationalConstant * massValue / radiusCubed;
|
||||
const double specificEnergy = gravitationalConstant * massValue / radiusValue;
|
||||
const double pressure = gravitationalConstant * massValue * massValue /
|
||||
(radiusSquared * radiusSquared);
|
||||
const double angularVelocity = std::sqrt(inverseTimeSquared);
|
||||
const double angularMomentum = massValue * std::sqrt(gravitationalConstant * massValue * radiusValue);
|
||||
const double force = gravitationalConstant * massValue * massValue / radiusSquared;
|
||||
|
||||
const double derived[] = {
|
||||
density,
|
||||
acceleration,
|
||||
inverseTimeSquared,
|
||||
specificEnergy,
|
||||
pressure,
|
||||
angularVelocity,
|
||||
angularMomentum,
|
||||
force
|
||||
};
|
||||
for (const double value : derived) {
|
||||
if (!std::isfinite(value) || value <= 0.0) {
|
||||
throw std::overflow_error("A derived characteristic stellar scale is not finite and positive.");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
.mass = mass,
|
||||
.radius = radius,
|
||||
.gravitationalConstant = gravitationalConstant,
|
||||
.density = density,
|
||||
.acceleration = acceleration,
|
||||
.inverseTimeSquared = inverseTimeSquared,
|
||||
.specificEnergy = specificEnergy,
|
||||
.pressure = pressure,
|
||||
.angularVelocity = angularVelocity,
|
||||
.angularMomentum = angularMomentum,
|
||||
.force = force
|
||||
};
|
||||
}
|
||||
|
||||
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Model>
|
||||
requires requires(const Model &model) {
|
||||
{
|
||||
model.template specification<models::FixedTotalMass>()
|
||||
} -> std::same_as<const models::FixedTotalMass &>;
|
||||
{
|
||||
model.template specification<models::FixedTotalMass>().targetMass()
|
||||
} -> std::same_as<dimensions::MassValue>;
|
||||
}
|
||||
[[nodiscard]] StellarCharacteristicScales deriveStellarCharacteristicScales(
|
||||
const PhysicalRieszDiagonal<Geometry, ScaleSource> &prescription,
|
||||
const Model &model
|
||||
) {
|
||||
return deriveStellarCharacteristicScales(
|
||||
model.template specification<models::FixedTotalMass>().targetMass(),
|
||||
prescription.referenceRadius(),
|
||||
prescription.gravitationalConstant()
|
||||
);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
/*
|
||||
* Model definitions live below the numerical normalization layer so
|
||||
* that a physics component can describe its generated coordinates
|
||||
* without importing solver machinery. These two translations are the
|
||||
* deliberately small boundary between that neutral declaration and the
|
||||
* normalization plan used by the discretization.
|
||||
*/
|
||||
template <models::RieszTopology Topology> struct DeclaredRieszTopology {
|
||||
static constexpr bool available = false;
|
||||
static constexpr RieszTopology value = RieszTopology::identity;
|
||||
};
|
||||
|
||||
#define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \
|
||||
template <> struct DeclaredRieszTopology<models::RieszTopology::Name> { \
|
||||
static constexpr bool available = true; \
|
||||
static constexpr RieszTopology value = RieszTopology::Name; \
|
||||
}
|
||||
|
||||
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(identity);
|
||||
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(scalar_volume_l2);
|
||||
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(vector_volume_l2);
|
||||
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(scalar_boundary_l2);
|
||||
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(hybrid_scalar_volume_point_rows);
|
||||
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(global_scalar);
|
||||
|
||||
#undef MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY
|
||||
|
||||
template <models::PhysicalScaleLaw Scale> struct DeclaredPhysicalScale {
|
||||
static constexpr bool available = false;
|
||||
static constexpr PhysicalScaleKind value = PhysicalScaleKind::dimensionless;
|
||||
};
|
||||
|
||||
#define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \
|
||||
template <> struct DeclaredPhysicalScale<models::PhysicalScaleLaw::Name> { \
|
||||
static constexpr bool available = true; \
|
||||
static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \
|
||||
}
|
||||
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(dimensionless);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(density);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(length);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(acceleration);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(inverse_time_squared);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(specific_energy);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(pressure);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(mass);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(force);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(angular_velocity);
|
||||
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(angular_momentum);
|
||||
|
||||
#undef MEAN_FIELD_DECLARED_PHYSICAL_SCALE
|
||||
|
||||
template <typename Declaration, typename = void>
|
||||
struct CompileDeclaredPhysicalRieszCoordinate {
|
||||
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||
static constexpr bool registered = false;
|
||||
};
|
||||
|
||||
template <typename Declaration>
|
||||
struct CompileDeclaredPhysicalRieszCoordinate<
|
||||
Declaration,
|
||||
std::void_t<
|
||||
decltype(std::integral_constant<
|
||||
models::RieszTopology,
|
||||
static_cast<models::RieszTopology>(Declaration::topology)>{}),
|
||||
decltype(std::integral_constant<
|
||||
models::PhysicalScaleLaw,
|
||||
static_cast<models::PhysicalScaleLaw>(Declaration::scale)>{}),
|
||||
decltype(std::bool_constant<static_cast<bool>(Declaration::available)>{})>> {
|
||||
private:
|
||||
static constexpr models::RieszTopology declaredTopology =
|
||||
static_cast<models::RieszTopology>(Declaration::topology);
|
||||
static constexpr models::PhysicalScaleLaw declaredScale =
|
||||
static_cast<models::PhysicalScaleLaw>(Declaration::scale);
|
||||
using Topology = DeclaredRieszTopology<declaredTopology>;
|
||||
using Scale = DeclaredPhysicalScale<declaredScale>;
|
||||
|
||||
public:
|
||||
static constexpr bool registered = static_cast<bool>(Declaration::available) &&
|
||||
Topology::available && Scale::available;
|
||||
using Method = std::conditional_t<
|
||||
registered,
|
||||
PhysicalRieszCoordinate<Topology::value, Scale::value>,
|
||||
UnsupportedPhysicalRieszCoordinate>;
|
||||
};
|
||||
|
||||
template <typename Generated, CoordinateKind Kind, typename = void>
|
||||
struct DeclaredGeneratedPhysicalRieszCoordinate {
|
||||
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||
static constexpr bool registered = false;
|
||||
};
|
||||
|
||||
template <typename Generated>
|
||||
struct DeclaredGeneratedPhysicalRieszCoordinate<
|
||||
Generated,
|
||||
CoordinateKind::value,
|
||||
std::void_t<
|
||||
typename Generated::SpecificationType,
|
||||
typename models::SpecificationContribution<
|
||||
typename Generated::SpecificationType>::Normalization::Value>>
|
||||
: CompileDeclaredPhysicalRieszCoordinate<
|
||||
typename models::SpecificationContribution<
|
||||
typename Generated::SpecificationType>::Normalization::Value> { };
|
||||
|
||||
template <typename Generated>
|
||||
struct DeclaredGeneratedPhysicalRieszCoordinate<
|
||||
Generated,
|
||||
CoordinateKind::residual,
|
||||
std::void_t<
|
||||
typename Generated::SpecificationType,
|
||||
typename models::SpecificationContribution<
|
||||
typename Generated::SpecificationType>::Normalization::Residual>>
|
||||
: CompileDeclaredPhysicalRieszCoordinate<
|
||||
typename models::SpecificationContribution<
|
||||
typename Generated::SpecificationType>::Normalization::Residual> { };
|
||||
|
||||
template <typename GeneratedValues, typename GeneratedResiduals>
|
||||
struct GeneratedPhysicalRieszCoverage {
|
||||
static constexpr bool complete = false;
|
||||
};
|
||||
|
||||
template <typename... GeneratedValues, typename... GeneratedResiduals>
|
||||
struct GeneratedPhysicalRieszCoverage<
|
||||
models::ModelTypeList<GeneratedValues...>,
|
||||
models::ModelTypeList<GeneratedResiduals...>> {
|
||||
static constexpr bool complete =
|
||||
(DeclaredGeneratedPhysicalRieszCoordinate<
|
||||
GeneratedValues,
|
||||
CoordinateKind::value>::registered && ...) &&
|
||||
(DeclaredGeneratedPhysicalRieszCoordinate<
|
||||
GeneratedResiduals,
|
||||
CoordinateKind::residual>::registered && ...);
|
||||
};
|
||||
|
||||
template <typename Specification, typename = void>
|
||||
struct SpecificationPhysicalRieszCoverage {
|
||||
static constexpr bool complete = false;
|
||||
};
|
||||
|
||||
template <models::ModelSpecification Specification>
|
||||
struct SpecificationPhysicalRieszCoverage<
|
||||
Specification,
|
||||
std::void_t<
|
||||
typename models::SpecificationContribution<Specification>::GeneratedValues,
|
||||
typename models::SpecificationContribution<Specification>::GeneratedResiduals>>
|
||||
: GeneratedPhysicalRieszCoverage<
|
||||
typename models::SpecificationContribution<Specification>::GeneratedValues,
|
||||
typename models::SpecificationContribution<Specification>::GeneratedResiduals> { };
|
||||
} // namespace detail
|
||||
|
||||
/*
|
||||
* All generated blocks are normalized from their generating physics
|
||||
* specification. Adding another constraint therefore does not add a
|
||||
* normalization specialization: its public ModelDefinition is the single
|
||||
* source of both the value and residual Riesz laws.
|
||||
*/
|
||||
template <typename Generated>
|
||||
struct PhysicalRieszBlockTraits<utils::blocks::generated_value_block<Generated>>
|
||||
: detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::value> { };
|
||||
|
||||
template <typename Generated>
|
||||
struct PhysicalRieszBlockTraits<utils::blocks::generated_residual_block<Generated>>
|
||||
: detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::residual> { };
|
||||
|
||||
template <typename Generated>
|
||||
concept GeneratedValuePhysicalRieszNormalizable =
|
||||
detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::value>::registered;
|
||||
|
||||
template <typename Generated>
|
||||
concept GeneratedResidualPhysicalRieszNormalizable =
|
||||
detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::residual>::registered;
|
||||
|
||||
template <typename Specification>
|
||||
concept CompleteGeneratedPhysicalRieszNormalizationFor =
|
||||
detail::SpecificationPhysicalRieszCoverage<std::remove_cvref_t<Specification>>::complete;
|
||||
|
||||
/*
|
||||
* Runtime Physical Riesz assembly needs more than a symbolically complete
|
||||
* plan: it must be able to recover the finite-element maps owned by the
|
||||
* selected physical core. Keep that structural capability in this low
|
||||
* normalization module so both problem formation and the solver-facing
|
||||
* adapter can consult the same authority without importing one another.
|
||||
*/
|
||||
template <typename Candidate>
|
||||
concept PhysicalRieszCoreRuntime =
|
||||
requires(const std::remove_cvref_t<Candidate> &core) {
|
||||
{
|
||||
core.GetGravityContext().GetDensityMap()
|
||||
} -> std::same_as<const field::FieldDofMap &>;
|
||||
{
|
||||
core.GetGravityContext().GetGravityGradientMap()
|
||||
} -> std::same_as<const field::FieldDofMap &>;
|
||||
{
|
||||
core.GetGravityContext().GetGravityPotentialMap()
|
||||
} -> std::same_as<const field::FieldDofMap &>;
|
||||
{
|
||||
core.GetHydrostaticOperator().GetEnthalpyMap()
|
||||
} -> std::same_as<const field::FieldDofMap &>;
|
||||
{
|
||||
core.GetDomainDeformation().parameterCount()
|
||||
} -> std::same_as<int>;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename Generated, CoordinateKind Kind>
|
||||
using GeneratedPhysicalRieszMethod =
|
||||
typename DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::Method;
|
||||
|
||||
template <typename Generated, CoordinateKind Kind, typename = void>
|
||||
struct GeneratedPhysicalRieszRuntimeCoordinate : std::false_type { };
|
||||
|
||||
template <typename Generated, CoordinateKind Kind>
|
||||
struct GeneratedPhysicalRieszRuntimeCoordinate<
|
||||
Generated,
|
||||
Kind,
|
||||
std::void_t<decltype(GeneratedPhysicalRieszMethod<Generated, Kind>::topology)>>
|
||||
: std::bool_constant<
|
||||
DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::registered &&
|
||||
GeneratedPhysicalRieszMethod<Generated, Kind>::topology ==
|
||||
RieszTopology::global_scalar> { };
|
||||
|
||||
template <typename Specification, typename = void>
|
||||
struct SpecificationPhysicalRieszRuntimeCoverage : std::false_type { };
|
||||
|
||||
template <typename Values, typename Residuals>
|
||||
struct GeneratedPhysicalRieszRuntimeCoverage : std::false_type { };
|
||||
|
||||
template <typename... Values, typename... Residuals>
|
||||
struct GeneratedPhysicalRieszRuntimeCoverage<
|
||||
models::ModelTypeList<Values...>,
|
||||
models::ModelTypeList<Residuals...>>
|
||||
: std::bool_constant<
|
||||
(GeneratedPhysicalRieszRuntimeCoordinate<Values, CoordinateKind::value>::value && ...) &&
|
||||
(GeneratedPhysicalRieszRuntimeCoordinate<Residuals, CoordinateKind::residual>::value && ...)> { };
|
||||
|
||||
template <models::ModelSpecification Specification>
|
||||
struct SpecificationPhysicalRieszRuntimeCoverage<
|
||||
Specification,
|
||||
std::void_t<
|
||||
typename models::SpecificationContribution<Specification>::GeneratedValues,
|
||||
typename models::SpecificationContribution<Specification>::GeneratedResiduals>>
|
||||
: GeneratedPhysicalRieszRuntimeCoverage<
|
||||
typename models::SpecificationContribution<Specification>::GeneratedValues,
|
||||
typename models::SpecificationContribution<Specification>::GeneratedResiduals> { };
|
||||
|
||||
template <typename SpecificationTypes>
|
||||
struct SpecificationSetPhysicalRieszRuntimeCoverage : std::false_type { };
|
||||
|
||||
template <models::ModelSpecification... Specifications>
|
||||
struct SpecificationSetPhysicalRieszRuntimeCoverage<
|
||||
models::detail::SpecificationSetStorage<Specifications...>>
|
||||
: std::bool_constant<
|
||||
(SpecificationPhysicalRieszRuntimeCoverage<Specifications>::value && ...)> { };
|
||||
} // namespace detail
|
||||
|
||||
template <typename Specification>
|
||||
concept CompleteGeneratedPhysicalRieszRuntimeNormalizationFor =
|
||||
detail::SpecificationPhysicalRieszRuntimeCoverage<
|
||||
std::remove_cvref_t<Specification>>::value;
|
||||
|
||||
#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \
|
||||
template <> struct PhysicalRieszBlockTraits<BlockType> { \
|
||||
using Method = PhysicalRieszCoordinate<RieszTopology::TopologyValue, PhysicalScaleKind::ScaleValue>; \
|
||||
static constexpr bool registered = true; \
|
||||
}
|
||||
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::density::mass::value,
|
||||
scalar_volume_l2,
|
||||
density
|
||||
);
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::surface_deformation::parameters::value,
|
||||
scalar_boundary_l2,
|
||||
length
|
||||
);
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::gravity::gradient::value,
|
||||
vector_volume_l2,
|
||||
acceleration
|
||||
);
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::gravity::poisson::value,
|
||||
scalar_volume_l2,
|
||||
specific_energy
|
||||
);
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::enthalpy::specific::value,
|
||||
scalar_volume_l2,
|
||||
specific_energy
|
||||
);
|
||||
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::gravity::gradient::residual,
|
||||
vector_volume_l2,
|
||||
acceleration
|
||||
);
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::gravity::poisson::residual,
|
||||
scalar_volume_l2,
|
||||
inverse_time_squared
|
||||
);
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::density::mass::residual,
|
||||
scalar_volume_l2,
|
||||
density
|
||||
);
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::surface_deformation::shape_equilibrium::residual,
|
||||
scalar_boundary_l2,
|
||||
force
|
||||
);
|
||||
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||
utils::blocks::enthalpy::specific::residual,
|
||||
hybrid_scalar_volume_point_rows,
|
||||
specific_energy
|
||||
);
|
||||
#undef MEAN_FIELD_PHYSICAL_RIESZ_TRAIT
|
||||
|
||||
template <typename Block>
|
||||
[[nodiscard]] double physicalScale(
|
||||
const StellarCharacteristicScales &scales
|
||||
) {
|
||||
static_assert(PhysicalRieszBlockTraits<Block>::registered, "The block has no Physical Riesz normalization.");
|
||||
using Method = typename PhysicalRieszBlockTraits<Block>::Method;
|
||||
constexpr PhysicalScaleKind scale = Method::scale;
|
||||
if constexpr (scale == PhysicalScaleKind::dimensionless) {
|
||||
return 1.0;
|
||||
} else if constexpr (scale == PhysicalScaleKind::density) {
|
||||
return scales.density;
|
||||
} else if constexpr (scale == PhysicalScaleKind::length) {
|
||||
return scales.radius.value();
|
||||
} else if constexpr (scale == PhysicalScaleKind::acceleration) {
|
||||
return scales.acceleration;
|
||||
} else if constexpr (scale == PhysicalScaleKind::inverse_time_squared) {
|
||||
return scales.inverseTimeSquared;
|
||||
} else if constexpr (scale == PhysicalScaleKind::specific_energy) {
|
||||
return scales.specificEnergy;
|
||||
} else if constexpr (scale == PhysicalScaleKind::pressure) {
|
||||
return scales.pressure;
|
||||
} else if constexpr (scale == PhysicalScaleKind::mass) {
|
||||
return scales.mass.value();
|
||||
} else if constexpr (scale == PhysicalScaleKind::force) {
|
||||
return scales.force;
|
||||
} else if constexpr (scale == PhysicalScaleKind::angular_velocity) {
|
||||
return scales.angularVelocity;
|
||||
} else {
|
||||
static_assert(scale == PhysicalScaleKind::angular_momentum);
|
||||
return scales.angularMomentum;
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
template <typename Values, typename Residuals> struct MakePhysicalRieszPlan;
|
||||
|
||||
template <typename... Values, typename... Residuals>
|
||||
struct MakePhysicalRieszPlan<
|
||||
utils::blocks::type_list<Values...>,
|
||||
utils::blocks::type_list<Residuals...>> {
|
||||
using Type = NormalizationPlan<
|
||||
CoordinateComponent<
|
||||
CoordinateKind::value,
|
||||
utils::blocks::type_list<Values>,
|
||||
typename PhysicalRieszBlockTraits<Values>::Method>...,
|
||||
CoordinateComponent<
|
||||
CoordinateKind::residual,
|
||||
utils::blocks::type_list<Residuals>,
|
||||
typename PhysicalRieszBlockTraits<Residuals>::Method>...>;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename Form>
|
||||
requires utils::blocks::block_form_is_valid_v<Form>
|
||||
using PhysicalRieszNormalizationPlanFor = typename detail::MakePhysicalRieszPlan<
|
||||
typename Form::value_blocks,
|
||||
typename Form::residual_blocks>::Type;
|
||||
|
||||
/*
|
||||
* Public compile-time extension point for a normalization prescription.
|
||||
* A specialization owns both the complete coordinate plan and the
|
||||
* low-level runtime compatibility predicate used before a discretized
|
||||
* problem type is formed. Keeping those declarations together prevents a
|
||||
* policy from compiling a plan which the selected stellar core cannot
|
||||
* actually prepare.
|
||||
*/
|
||||
template <typename Prescription, typename Form> struct NormalizationCompilation {
|
||||
using Plan = NormalizationPlan<>;
|
||||
static constexpr bool registered = false;
|
||||
|
||||
template <typename PhysicalCore, typename SpecificationTypes>
|
||||
static constexpr bool runtimeAvailableFor = false;
|
||||
};
|
||||
|
||||
/* Astronomy/numerics-facing package for a policy which prepares one
|
||||
* runtime diagonal over the complete inferred form and needs no private
|
||||
* facility of a particular stellar core. The generated plan truthfully
|
||||
* labels every coordinate as runtime-prepared by this exact policy. */
|
||||
template <NormalizationPrescription Prescription, typename Form>
|
||||
requires utils::blocks::block_form_is_valid_v<Form>
|
||||
struct RuntimePreparedNormalizationCompilation {
|
||||
using Plan = RuntimePreparedNormalizationPlanFor<Prescription, Form>;
|
||||
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||
|
||||
template <typename PhysicalCore, typename SpecificationTypes>
|
||||
static constexpr bool runtimeAvailableFor = registered;
|
||||
};
|
||||
|
||||
template <typename Form>
|
||||
requires utils::blocks::block_form_is_valid_v<Form>
|
||||
struct NormalizationCompilation<Unnormalized, Form> {
|
||||
using Plan = IdentityNormalizationPlanFor<Form>;
|
||||
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||
|
||||
template <typename PhysicalCore, typename SpecificationTypes>
|
||||
static constexpr bool runtimeAvailableFor = registered;
|
||||
};
|
||||
|
||||
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Form>
|
||||
requires utils::blocks::block_form_is_valid_v<Form>
|
||||
struct NormalizationCompilation<PhysicalRieszDiagonal<Geometry, ScaleSource>, Form> {
|
||||
using Plan = PhysicalRieszNormalizationPlanFor<Form>;
|
||||
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||
|
||||
template <typename PhysicalCore, typename SpecificationTypes>
|
||||
static constexpr bool runtimeAvailableFor =
|
||||
registered &&
|
||||
PhysicalRieszCoreRuntime<std::remove_cvref_t<PhysicalCore>> &&
|
||||
detail::SpecificationSetPhysicalRieszRuntimeCoverage<
|
||||
std::remove_cvref_t<SpecificationTypes>>::value;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename Prescription, typename Form, typename = void>
|
||||
struct NormalizationCompilationAudit {
|
||||
using Plan = NormalizationPlan<>;
|
||||
static constexpr bool registered = false;
|
||||
};
|
||||
|
||||
template <typename Prescription, typename Form>
|
||||
requires NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
|
||||
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>>
|
||||
struct NormalizationCompilationAudit<
|
||||
Prescription,
|
||||
Form,
|
||||
std::void_t<
|
||||
typename NormalizationCompilation<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
std::remove_cvref_t<Form>>::Plan,
|
||||
decltype(std::bool_constant<static_cast<bool>(
|
||||
NormalizationCompilation<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
std::remove_cvref_t<Form>>::registered)>{})>> {
|
||||
using Compilation = NormalizationCompilation<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
std::remove_cvref_t<Form>>;
|
||||
using Plan = typename Compilation::Plan;
|
||||
|
||||
static constexpr bool registered =
|
||||
static_cast<bool>(Compilation::registered) &&
|
||||
CompleteNormalizationFor<Plan, std::remove_cvref_t<Form>>;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <NormalizationPrescription Prescription, typename Form>
|
||||
using NormalizationPlanFor = typename detail::NormalizationCompilationAudit<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
Form>::Plan;
|
||||
|
||||
template <typename Prescription, typename Form>
|
||||
concept CompilableNormalizationFor =
|
||||
detail::NormalizationCompilationAudit<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
std::remove_cvref_t<Form>>::registered;
|
||||
|
||||
/* The public runtime-preparation adapter is intentionally narrower than
|
||||
* an arbitrary complete plan: every coordinate must name the exact policy
|
||||
* which supplies its runtime factor. This prevents a custom policy from
|
||||
* advertising IdentityCoordinate (or another policy's method) while
|
||||
* silently installing a different diagonal at runtime. */
|
||||
template <typename Prescription, typename Form>
|
||||
concept RuntimePreparedNormalizationFor =
|
||||
NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
|
||||
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>> &&
|
||||
CompilableNormalizationFor<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
std::remove_cvref_t<Form>> &&
|
||||
std::same_as<
|
||||
NormalizationPlanFor<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
std::remove_cvref_t<Form>>,
|
||||
RuntimePreparedNormalizationPlanFor<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
std::remove_cvref_t<Form>>>;
|
||||
|
||||
namespace detail {
|
||||
template <
|
||||
typename Prescription,
|
||||
typename Form,
|
||||
typename PhysicalCore,
|
||||
typename SpecificationTypes,
|
||||
typename = void>
|
||||
struct StellarNormalizationRuntimeAudit : std::false_type { };
|
||||
|
||||
template <
|
||||
typename Prescription,
|
||||
typename Form,
|
||||
typename PhysicalCore,
|
||||
typename SpecificationTypes>
|
||||
struct StellarNormalizationRuntimeAudit<
|
||||
Prescription,
|
||||
Form,
|
||||
PhysicalCore,
|
||||
SpecificationTypes,
|
||||
std::void_t<
|
||||
std::enable_if_t<NormalizationCompilationAudit<
|
||||
Prescription,
|
||||
Form>::registered>,
|
||||
decltype(std::bool_constant<static_cast<bool>(
|
||||
NormalizationCompilation<
|
||||
Prescription,
|
||||
Form>::template runtimeAvailableFor<
|
||||
PhysicalCore,
|
||||
SpecificationTypes>)>{})>>
|
||||
: std::bool_constant<
|
||||
(std::same_as<Prescription, Unnormalized> ||
|
||||
PhysicalRieszDiagonalPrescription<Prescription> ||
|
||||
RuntimePreparedNormalizationFor<Prescription, Form>) &&
|
||||
static_cast<bool>(NormalizationCompilation<
|
||||
Prescription,
|
||||
Form>::template runtimeAvailableFor<
|
||||
PhysicalCore,
|
||||
SpecificationTypes>)> { };
|
||||
} // namespace detail
|
||||
|
||||
/*
|
||||
* Single detection-safe authority for pairing a compiled stellar form,
|
||||
* its selected physical core, and a runtime normalization prescription.
|
||||
* Each public NormalizationCompilation specialization declares this
|
||||
* compatibility alongside its plan. The identity policy needs only a
|
||||
* complete plan. Physical Riesz also requires every map consumed during
|
||||
* assembly and global-scalar runtime preparation for every generated
|
||||
* coordinate in the specification pack.
|
||||
*/
|
||||
template <
|
||||
typename Prescription,
|
||||
typename Form,
|
||||
typename PhysicalCore,
|
||||
typename SpecificationTypes>
|
||||
concept StellarNormalizationRuntimeAvailableFor =
|
||||
detail::StellarNormalizationRuntimeAudit<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
std::remove_cvref_t<Form>,
|
||||
std::remove_cvref_t<PhysicalCore>,
|
||||
std::remove_cvref_t<SpecificationTypes>>::value;
|
||||
} // namespace mean_field::normalization
|
||||
376
libmeanfield/interface/normalization/plan.cppm
Normal file
376
libmeanfield/interface/normalization/plan.cppm
Normal file
@@ -0,0 +1,376 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
export module mean_field:normalization.plan;
|
||||
|
||||
export import :utils.blocks;
|
||||
|
||||
export namespace mean_field::normalization {
|
||||
struct NormalizationPrescriptionTag { };
|
||||
|
||||
template <typename Candidate>
|
||||
concept NormalizationPrescription =
|
||||
std::derived_from<
|
||||
std::remove_cvref_t<Candidate>,
|
||||
NormalizationPrescriptionTag>;
|
||||
|
||||
enum class CoordinateKind { value, residual };
|
||||
|
||||
enum class RieszTopology {
|
||||
identity,
|
||||
scalar_volume_l2,
|
||||
vector_volume_l2,
|
||||
scalar_boundary_l2,
|
||||
hybrid_scalar_volume_point_rows,
|
||||
global_scalar
|
||||
};
|
||||
|
||||
enum class PhysicalScaleKind {
|
||||
dimensionless,
|
||||
density,
|
||||
length,
|
||||
acceleration,
|
||||
inverse_time_squared,
|
||||
specific_energy,
|
||||
pressure,
|
||||
mass,
|
||||
force,
|
||||
angular_velocity,
|
||||
angular_momentum
|
||||
};
|
||||
|
||||
struct IdentityCoordinate final { };
|
||||
|
||||
/*
|
||||
* Honest compile-time method for a coordinate whose positive diagonal
|
||||
* factor is supplied at runtime by one exact normalization prescription.
|
||||
* Unlike IdentityCoordinate, this category makes no claim about the
|
||||
* numerical value of that factor. The owner type prevents one policy from
|
||||
* silently presenting another policy's runtime map as its own plan.
|
||||
*/
|
||||
template <NormalizationPrescription Prescription>
|
||||
struct RuntimePreparedCoordinate final {
|
||||
using PrescriptionType = std::remove_cvref_t<Prescription>;
|
||||
};
|
||||
|
||||
template <RieszTopology Topology, PhysicalScaleKind Scale> struct PhysicalRieszCoordinate final {
|
||||
static constexpr RieszTopology topology = Topology;
|
||||
static constexpr PhysicalScaleKind scale = Scale;
|
||||
};
|
||||
|
||||
struct UnsupportedPhysicalRieszCoordinate final { };
|
||||
|
||||
template <typename Block> struct PhysicalRieszBlockTraits {
|
||||
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||
static constexpr bool registered = false;
|
||||
};
|
||||
|
||||
template <CoordinateKind Kind, typename BlockList, typename MethodType>
|
||||
struct CoordinateComponent final {
|
||||
using Blocks = BlockList;
|
||||
using Method = MethodType;
|
||||
static constexpr CoordinateKind kind = Kind;
|
||||
|
||||
using ValueBlocks = std::conditional_t<
|
||||
Kind == CoordinateKind::value,
|
||||
BlockList,
|
||||
utils::blocks::type_list<>>;
|
||||
using ResidualBlocks = std::conditional_t<
|
||||
Kind == CoordinateKind::residual,
|
||||
BlockList,
|
||||
utils::blocks::type_list<>>;
|
||||
};
|
||||
|
||||
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 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 Method> struct IsCoordinateMethod : std::false_type { };
|
||||
|
||||
template <> struct IsCoordinateMethod<IdentityCoordinate> : std::true_type { };
|
||||
|
||||
template <NormalizationPrescription Prescription>
|
||||
struct IsCoordinateMethod<RuntimePreparedCoordinate<Prescription>>
|
||||
: std::true_type { };
|
||||
|
||||
template <RieszTopology Topology, PhysicalScaleKind Scale>
|
||||
struct IsCoordinateMethod<PhysicalRieszCoordinate<Topology, Scale>> : std::true_type { };
|
||||
|
||||
template <typename Method, typename Block> struct MethodSupportsBlock : std::false_type { };
|
||||
|
||||
template <typename Block>
|
||||
struct MethodSupportsBlock<IdentityCoordinate, Block>
|
||||
: std::bool_constant<std::derived_from<Block, utils::blocks::block>> { };
|
||||
|
||||
template <NormalizationPrescription Prescription, typename Block>
|
||||
struct MethodSupportsBlock<RuntimePreparedCoordinate<Prescription>, Block>
|
||||
: std::bool_constant<std::derived_from<Block, utils::blocks::block>> { };
|
||||
|
||||
template <RieszTopology Topology, PhysicalScaleKind Scale, typename Block>
|
||||
struct MethodSupportsBlock<PhysicalRieszCoordinate<Topology, Scale>, Block>
|
||||
: std::bool_constant<
|
||||
PhysicalRieszBlockTraits<Block>::registered &&
|
||||
std::same_as<
|
||||
typename PhysicalRieszBlockTraits<Block>::Method,
|
||||
PhysicalRieszCoordinate<Topology, Scale>>> { };
|
||||
|
||||
template <typename Method, typename List> struct MethodSupportsEveryBlock : std::false_type { };
|
||||
|
||||
template <typename Method, typename... Blocks>
|
||||
struct MethodSupportsEveryBlock<Method, utils::blocks::type_list<Blocks...>>
|
||||
: std::bool_constant<(MethodSupportsBlock<Method, Blocks>::value && ...)> { };
|
||||
|
||||
template <typename Candidate, typename = void> struct ComponentTraits {
|
||||
static constexpr bool valid = false;
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
struct ComponentTraits<
|
||||
Candidate,
|
||||
std::void_t<
|
||||
typename Candidate::Blocks,
|
||||
typename Candidate::Method,
|
||||
typename Candidate::ValueBlocks,
|
||||
typename Candidate::ResidualBlocks,
|
||||
decltype(Candidate::kind)>> {
|
||||
using Blocks = typename Candidate::Blocks;
|
||||
using Method = typename Candidate::Method;
|
||||
using ValueBlocks = typename Candidate::ValueBlocks;
|
||||
using ResidualBlocks = typename Candidate::ResidualBlocks;
|
||||
|
||||
static constexpr bool hasValidKind =
|
||||
std::same_as<std::remove_cv_t<decltype(Candidate::kind)>, CoordinateKind>;
|
||||
|
||||
static constexpr bool hasValidBlockList = [] {
|
||||
if constexpr (!hasValidKind || !IsTypeList<Blocks>::value) {
|
||||
return false;
|
||||
} else if constexpr (Candidate::kind == CoordinateKind::value) {
|
||||
return IsUniqueDerivedBlockList<Blocks, utils::blocks::value_block_base>::value;
|
||||
} else if constexpr (Candidate::kind == CoordinateKind::residual) {
|
||||
return IsUniqueDerivedBlockList<Blocks, utils::blocks::residual_block_base>::value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
static constexpr bool hasCoherentCoordinateLists = [] {
|
||||
if constexpr (!hasValidKind || !IsTypeList<ValueBlocks>::value ||
|
||||
!IsTypeList<ResidualBlocks>::value) {
|
||||
return false;
|
||||
} else if constexpr (Candidate::kind == CoordinateKind::value) {
|
||||
return std::same_as<ValueBlocks, Blocks> &&
|
||||
std::same_as<ResidualBlocks, utils::blocks::type_list<>>;
|
||||
} else if constexpr (Candidate::kind == CoordinateKind::residual) {
|
||||
return std::same_as<ValueBlocks, utils::blocks::type_list<>> &&
|
||||
std::same_as<ResidualBlocks, Blocks>;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
static constexpr bool valid = hasValidKind && IsTypeList<Blocks>::value &&
|
||||
IsCoordinateMethod<Method>::value && hasValidBlockList &&
|
||||
hasCoherentCoordinateLists &&
|
||||
MethodSupportsEveryBlock<Method, Blocks>::value;
|
||||
};
|
||||
|
||||
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 Appended>
|
||||
struct Append<utils::blocks::type_list<Types...>, Appended> {
|
||||
using Type = utils::blocks::type_list<Types..., Appended>;
|
||||
};
|
||||
|
||||
template <typename List, typename Type> using AppendT = typename Append<List, Type>::Type;
|
||||
|
||||
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 <typename Candidate, typename = void> struct PlanTraits {
|
||||
static constexpr bool valid = false;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename Candidate>
|
||||
concept NormalizationComponent = detail::ComponentTraits<std::remove_cvref_t<Candidate>>::valid;
|
||||
|
||||
template <typename... Components> struct NormalizationPlan final {
|
||||
using ComponentTypes = utils::blocks::type_list<Components...>;
|
||||
using ValueBlocks = detail::ConcatenateT<typename Components::ValueBlocks...>;
|
||||
using ResidualBlocks = detail::ConcatenateT<typename Components::ResidualBlocks...>;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename... Components>
|
||||
struct PlanTraits<NormalizationPlan<Components...>> {
|
||||
static constexpr bool valid = (ComponentTraits<Components>::valid && ...);
|
||||
};
|
||||
|
||||
template <typename Values, typename Residuals> struct MakeIdentityPlan;
|
||||
|
||||
template <typename... Values, typename... Residuals>
|
||||
struct MakeIdentityPlan<
|
||||
utils::blocks::type_list<Values...>,
|
||||
utils::blocks::type_list<Residuals...>> {
|
||||
using Type = NormalizationPlan<
|
||||
CoordinateComponent<CoordinateKind::value, utils::blocks::type_list<Values>, IdentityCoordinate>...,
|
||||
CoordinateComponent<
|
||||
CoordinateKind::residual,
|
||||
utils::blocks::type_list<Residuals>,
|
||||
IdentityCoordinate>...>;
|
||||
};
|
||||
|
||||
template <
|
||||
NormalizationPrescription Prescription,
|
||||
typename Values,
|
||||
typename Residuals>
|
||||
struct MakeRuntimePreparedPlan;
|
||||
|
||||
template <
|
||||
NormalizationPrescription Prescription,
|
||||
typename... Values,
|
||||
typename... Residuals>
|
||||
struct MakeRuntimePreparedPlan<
|
||||
Prescription,
|
||||
utils::blocks::type_list<Values...>,
|
||||
utils::blocks::type_list<Residuals...>> {
|
||||
using Method = RuntimePreparedCoordinate<Prescription>;
|
||||
using Type = NormalizationPlan<
|
||||
CoordinateComponent<
|
||||
CoordinateKind::value,
|
||||
utils::blocks::type_list<Values>,
|
||||
Method>...,
|
||||
CoordinateComponent<
|
||||
CoordinateKind::residual,
|
||||
utils::blocks::type_list<Residuals>,
|
||||
Method>...>;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename Candidate>
|
||||
concept NormalizationPlanType = detail::PlanTraits<std::remove_cvref_t<Candidate>>::valid;
|
||||
|
||||
template <typename Form>
|
||||
requires utils::blocks::block_form_is_valid_v<Form>
|
||||
using IdentityNormalizationPlanFor = typename detail::MakeIdentityPlan<
|
||||
typename Form::value_blocks,
|
||||
typename Form::residual_blocks>::Type;
|
||||
|
||||
template <NormalizationPrescription Prescription, typename Form>
|
||||
requires utils::blocks::block_form_is_valid_v<Form>
|
||||
using RuntimePreparedNormalizationPlanFor =
|
||||
typename detail::MakeRuntimePreparedPlan<
|
||||
std::remove_cvref_t<Prescription>,
|
||||
typename Form::value_blocks,
|
||||
typename Form::residual_blocks>::Type;
|
||||
|
||||
template <typename Form, typename Plan>
|
||||
requires utils::blocks::block_form_is_valid_v<Form>
|
||||
struct NormalizationCoverage final {
|
||||
using DeclaredValueBlocks = typename Plan::ValueBlocks;
|
||||
using DeclaredResidualBlocks = typename Plan::ResidualBlocks;
|
||||
|
||||
using MissingValueBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredValueBlocks>;
|
||||
using UnexpectedValueBlocks = detail::ListDifferenceT<DeclaredValueBlocks, typename Form::value_blocks>;
|
||||
using RepeatedValueBlocks = detail::RepeatedTypesT<DeclaredValueBlocks>;
|
||||
|
||||
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 hasEveryValueBlock = MissingValueBlocks::size == 0;
|
||||
static constexpr bool hasOnlyValueBlocks = UnexpectedValueBlocks::size == 0;
|
||||
static constexpr bool hasUniqueValueOwners = RepeatedValueBlocks::size == 0;
|
||||
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
|
||||
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
|
||||
static constexpr bool hasUniqueResidualOwners = RepeatedResidualBlocks::size == 0;
|
||||
|
||||
static constexpr bool complete = hasEveryValueBlock && hasOnlyValueBlocks && hasUniqueValueOwners &&
|
||||
hasEveryResidualBlock && hasOnlyResidualBlocks &&
|
||||
hasUniqueResidualOwners;
|
||||
};
|
||||
|
||||
template <typename Plan, typename Form>
|
||||
concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v<Form> &&
|
||||
NormalizationPlanType<Plan> &&
|
||||
NormalizationCoverage<Form, std::remove_cvref_t<Plan>>::complete;
|
||||
} // namespace mean_field::normalization
|
||||
921
libmeanfield/interface/normalization/stellar_equilibrium.cppm
Normal file
921
libmeanfield/interface/normalization/stellar_equilibrium.cppm
Normal file
@@ -0,0 +1,921 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:normalization.stellar_equilibrium;
|
||||
|
||||
export import :normalization.operators;
|
||||
export import :operators.stellar_equilibrium_compiler;
|
||||
export import :operators.stellar_equilibrium_problem;
|
||||
export import :utils.domain;
|
||||
|
||||
namespace mean_field::normalization::detail {
|
||||
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleScalarMassDiagonal(
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
mfem::Array<int> *domainMarker = nullptr
|
||||
) {
|
||||
mfem::ParBilinearForm mass(&space);
|
||||
if (domainMarker == nullptr) {
|
||||
mass.AddDomainIntegrator(new mfem::MassIntegrator());
|
||||
} else {
|
||||
mass.AddDomainIntegrator(new mfem::MassIntegrator(), *domainMarker);
|
||||
}
|
||||
mass.Assemble();
|
||||
mass.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||
if (matrix == nullptr) {
|
||||
throw std::runtime_error("Reference scalar Riesz mass assembly failed.");
|
||||
}
|
||||
mfem::Vector diagonal;
|
||||
matrix->GetDiag(diagonal);
|
||||
return diagonal;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleHDivMassDiagonal(mfem::ParFiniteElementSpace &space) {
|
||||
mfem::ParBilinearForm mass(&space);
|
||||
mass.AddDomainIntegrator(new mfem::VectorFEMassIntegrator());
|
||||
mass.Assemble();
|
||||
mass.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||
if (matrix == nullptr) {
|
||||
throw std::runtime_error("Reference H(div) Riesz mass assembly failed.");
|
||||
}
|
||||
mfem::Vector diagonal;
|
||||
matrix->GetDiag(diagonal);
|
||||
return diagonal;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleSurfaceMassDiagonal(
|
||||
const fem::FEM &finiteElements,
|
||||
const field::ScalarBoundaryDofMap &surfaceMap
|
||||
) {
|
||||
mfem::Array<int> marker(finiteElements.mesh->bdr_attributes.Max());
|
||||
marker = 0;
|
||||
constexpr int attribute = DomainSchema::template boundary_attribute<utils::domain::StellarSurface>();
|
||||
if (attribute <= 0 || attribute > marker.Size()) {
|
||||
throw std::invalid_argument("The reference mesh does not contain the stellar-surface boundary.");
|
||||
}
|
||||
marker[attribute - 1] = 1;
|
||||
|
||||
mfem::ParBilinearForm mass(finiteElements.surfaceDeformationFes.get());
|
||||
mass.AddBoundaryIntegrator(new mfem::MassIntegrator(), marker);
|
||||
mass.Assemble();
|
||||
mass.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||
if (matrix == nullptr) {
|
||||
throw std::runtime_error("Reference surface Riesz mass assembly failed.");
|
||||
}
|
||||
mfem::Vector ambientDiagonal;
|
||||
matrix->GetDiag(ambientDiagonal);
|
||||
return surfaceMap.gather(ambientDiagonal);
|
||||
}
|
||||
|
||||
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
||||
[[nodiscard]] const auto &PhysicalOperator(const Problem &problem) {
|
||||
return problem.GetPhysicalOperator();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector GatherDiagonal(
|
||||
const mfem::Vector &fullDiagonal,
|
||||
const field::FieldDofMap &map,
|
||||
const char *role
|
||||
) {
|
||||
if (fullDiagonal.Size() != map.full_size()) {
|
||||
throw std::logic_error(std::string("The reference ") + role + " Gram diagonal has an incompatible map.");
|
||||
}
|
||||
return map.gather(fullDiagonal);
|
||||
}
|
||||
} // namespace mean_field::normalization::detail
|
||||
|
||||
export namespace mean_field::normalization {
|
||||
/*
|
||||
* Runtime preparation paired with the compile-time normalization plan.
|
||||
* The operator compiler is the authority for which blocks a specification
|
||||
* generated, and PhysicalRieszBlockTraits is the authority for their
|
||||
* declared physical laws. Keeping those responsibilities separate means
|
||||
* this layer never names a concrete integral or phase constraint.
|
||||
*/
|
||||
namespace detail {
|
||||
template <typename Block>
|
||||
using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits<Block>::Method;
|
||||
|
||||
template <typename Block, typename = void>
|
||||
struct IsGlobalGeneratedValueNormalization : std::false_type { };
|
||||
|
||||
template <typename Generated>
|
||||
struct IsGlobalGeneratedValueNormalization<
|
||||
utils::blocks::generated_value_block<Generated>,
|
||||
std::void_t<
|
||||
decltype(PhysicalRieszMethodFor<
|
||||
utils::blocks::generated_value_block<Generated>>::topology),
|
||||
decltype(PhysicalRieszMethodFor<
|
||||
utils::blocks::generated_value_block<Generated>>::scale)>>
|
||||
: std::bool_constant<
|
||||
PhysicalRieszBlockTraits<
|
||||
utils::blocks::generated_value_block<Generated>>::registered &&
|
||||
PhysicalRieszMethodFor<
|
||||
utils::blocks::generated_value_block<Generated>>::topology ==
|
||||
RieszTopology::global_scalar> { };
|
||||
|
||||
template <typename Blocks, typename Specification>
|
||||
struct GeneratedValueBlocksBelongToSpecification : std::false_type { };
|
||||
|
||||
template <typename Generated, typename Specification, typename = void>
|
||||
struct GeneratedCoordinateBelongsToSpecification : std::false_type { };
|
||||
|
||||
template <typename Generated, typename Specification>
|
||||
struct GeneratedCoordinateBelongsToSpecification<
|
||||
Generated,
|
||||
Specification,
|
||||
std::void_t<typename Generated::SpecificationType>>
|
||||
: std::bool_constant<
|
||||
std::same_as<typename Generated::SpecificationType, Specification>> { };
|
||||
|
||||
template <typename Specification, typename... Generated>
|
||||
struct GeneratedValueBlocksBelongToSpecification<
|
||||
utils::blocks::type_list<utils::blocks::generated_value_block<Generated>...>,
|
||||
Specification>
|
||||
: std::bool_constant<
|
||||
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
|
||||
|
||||
template <typename Block, typename = void>
|
||||
struct IsGlobalGeneratedResidualNormalization : std::false_type { };
|
||||
|
||||
template <typename Generated>
|
||||
struct IsGlobalGeneratedResidualNormalization<
|
||||
utils::blocks::generated_residual_block<Generated>,
|
||||
std::void_t<
|
||||
decltype(PhysicalRieszMethodFor<
|
||||
utils::blocks::generated_residual_block<Generated>>::topology),
|
||||
decltype(PhysicalRieszMethodFor<
|
||||
utils::blocks::generated_residual_block<Generated>>::scale)>>
|
||||
: std::bool_constant<
|
||||
PhysicalRieszBlockTraits<
|
||||
utils::blocks::generated_residual_block<Generated>>::registered &&
|
||||
PhysicalRieszMethodFor<
|
||||
utils::blocks::generated_residual_block<Generated>>::topology ==
|
||||
RieszTopology::global_scalar> { };
|
||||
|
||||
template <typename Blocks, typename Specification>
|
||||
struct GeneratedResidualBlocksBelongToSpecification : std::false_type { };
|
||||
|
||||
template <typename Specification, typename... Generated>
|
||||
struct GeneratedResidualBlocksBelongToSpecification<
|
||||
utils::blocks::type_list<utils::blocks::generated_residual_block<Generated>...>,
|
||||
Specification>
|
||||
: std::bool_constant<
|
||||
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
|
||||
|
||||
template <typename Blocks> struct PrepareGeneratedValueNormalizations {
|
||||
static constexpr bool registered = false;
|
||||
|
||||
template <typename Form>
|
||||
static constexpr bool completeFor = false;
|
||||
|
||||
template <typename Form>
|
||||
static void Apply(
|
||||
DiagonalNormalizationBuilder<Form> &,
|
||||
const StellarCharacteristicScales &
|
||||
) {
|
||||
static_assert(registered, "Generated value-block normalization metadata is malformed.");
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Blocks>
|
||||
struct PrepareGeneratedValueNormalizations<utils::blocks::type_list<Blocks...>> {
|
||||
static constexpr bool registered =
|
||||
(IsGlobalGeneratedValueNormalization<Blocks>::value && ...);
|
||||
|
||||
template <typename Form>
|
||||
static constexpr bool completeFor = registered &&
|
||||
utils::blocks::block_form_is_valid_v<Form> &&
|
||||
(utils::blocks::contains_type_v<Blocks, typename Form::value_blocks> && ...);
|
||||
|
||||
template <typename Form>
|
||||
static void Apply(
|
||||
DiagonalNormalizationBuilder<Form> &builder,
|
||||
const StellarCharacteristicScales &scales
|
||||
) {
|
||||
if constexpr (completeFor<Form>) {
|
||||
(builder.template SetValueGlobal<Blocks>(physicalScale<Blocks>(scales)), ...);
|
||||
} else {
|
||||
static_assert(
|
||||
completeFor<Form>,
|
||||
"Every generated value block must have a declared global-scalar Physical Riesz law "
|
||||
"and belong to the compiled equilibrium form."
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Blocks> struct PrepareGeneratedResidualNormalizations {
|
||||
static constexpr bool registered = false;
|
||||
|
||||
template <typename Form>
|
||||
static constexpr bool completeFor = false;
|
||||
|
||||
template <typename Form>
|
||||
static void Apply(
|
||||
DiagonalNormalizationBuilder<Form> &,
|
||||
const StellarCharacteristicScales &
|
||||
) {
|
||||
static_assert(registered, "Generated residual-block normalization metadata is malformed.");
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Blocks>
|
||||
struct PrepareGeneratedResidualNormalizations<utils::blocks::type_list<Blocks...>> {
|
||||
static constexpr bool registered =
|
||||
(IsGlobalGeneratedResidualNormalization<Blocks>::value && ...);
|
||||
|
||||
template <typename Form>
|
||||
static constexpr bool completeFor = registered &&
|
||||
utils::blocks::block_form_is_valid_v<Form> &&
|
||||
(utils::blocks::contains_type_v<Blocks, typename Form::residual_blocks> && ...);
|
||||
|
||||
template <typename Form>
|
||||
static void Apply(
|
||||
DiagonalNormalizationBuilder<Form> &builder,
|
||||
const StellarCharacteristicScales &scales
|
||||
) {
|
||||
if constexpr (completeFor<Form>) {
|
||||
(builder.template SetResidualGlobal<Blocks>(physicalScale<Blocks>(scales)), ...);
|
||||
} else {
|
||||
static_assert(
|
||||
completeFor<Form>,
|
||||
"Every generated residual block must have a declared global-scalar Physical Riesz law "
|
||||
"and belong to the compiled equilibrium form."
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Specification, typename = void>
|
||||
struct CompileStellarSpecificationNormalization {
|
||||
using ValuePreparation = PrepareGeneratedValueNormalizations<void>;
|
||||
using ResidualPreparation = PrepareGeneratedResidualNormalizations<void>;
|
||||
|
||||
static constexpr bool registered = false;
|
||||
|
||||
template <typename Form>
|
||||
static constexpr bool completeFor = false;
|
||||
|
||||
template <typename Form>
|
||||
static void Apply(
|
||||
DiagonalNormalizationBuilder<Form> &,
|
||||
const StellarCharacteristicScales &
|
||||
) {
|
||||
static_assert(
|
||||
completeFor<Form>,
|
||||
"The specification has no complete generated-coordinate normalization."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <models::ModelSpecification Specification>
|
||||
struct CompileStellarSpecificationNormalization<
|
||||
Specification,
|
||||
std::void_t<
|
||||
typename operators::StellarEquilibriumSpecificationCompilation<
|
||||
Specification>::GeneratedValueBlocks,
|
||||
typename operators::StellarEquilibriumSpecificationCompilation<
|
||||
Specification>::GeneratedResidualBlocks>> {
|
||||
using OperatorCompilation =
|
||||
operators::StellarEquilibriumSpecificationCompilation<Specification>;
|
||||
using ValuePreparation = PrepareGeneratedValueNormalizations<
|
||||
typename OperatorCompilation::GeneratedValueBlocks>;
|
||||
using ResidualPreparation = PrepareGeneratedResidualNormalizations<
|
||||
typename OperatorCompilation::GeneratedResidualBlocks>;
|
||||
|
||||
static constexpr bool registered = OperatorCompilation::complete &&
|
||||
models::CompleteGeneratedNormalizationFor<
|
||||
Specification> &&
|
||||
GeneratedValueBlocksBelongToSpecification<
|
||||
typename OperatorCompilation::GeneratedValueBlocks,
|
||||
Specification>::value &&
|
||||
GeneratedResidualBlocksBelongToSpecification<
|
||||
typename OperatorCompilation::GeneratedResidualBlocks,
|
||||
Specification>::value &&
|
||||
ValuePreparation::registered &&
|
||||
ResidualPreparation::registered;
|
||||
|
||||
template <typename Form>
|
||||
static constexpr bool completeFor = registered &&
|
||||
ValuePreparation::template completeFor<Form> &&
|
||||
ResidualPreparation::template completeFor<Form>;
|
||||
|
||||
template <typename Form>
|
||||
static void Apply(
|
||||
DiagonalNormalizationBuilder<Form> &builder,
|
||||
const StellarCharacteristicScales &scales
|
||||
) {
|
||||
if constexpr (completeFor<Form>) {
|
||||
ValuePreparation::template Apply<Form>(builder, scales);
|
||||
ResidualPreparation::template Apply<Form>(builder, scales);
|
||||
} else {
|
||||
static_assert(
|
||||
completeFor<Form>,
|
||||
"The specification's generated blocks do not have a complete runtime normalization."
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename SpecificationSet> struct PrepareSpecificationNormalizations;
|
||||
|
||||
template <models::ModelSpecification... Specifications>
|
||||
struct PrepareSpecificationNormalizations<models::detail::SpecificationSetStorage<Specifications...>> {
|
||||
static constexpr bool registered =
|
||||
(CompileStellarSpecificationNormalization<Specifications>::registered && ...);
|
||||
|
||||
template <typename Form>
|
||||
static constexpr bool completeFor =
|
||||
(CompileStellarSpecificationNormalization<Specifications>::template completeFor<Form> && ...);
|
||||
|
||||
template <typename Form>
|
||||
static void Apply(
|
||||
DiagonalNormalizationBuilder<Form> &builder,
|
||||
const StellarCharacteristicScales &scales
|
||||
) {
|
||||
static_assert(
|
||||
completeFor<Form>,
|
||||
"Every generated stellar-equilibrium coordinate requires a declared global-scalar "
|
||||
"Physical Riesz normalization and compiler-owned root block."
|
||||
);
|
||||
(CompileStellarSpecificationNormalization<Specifications>::template Apply<Form>(builder, scales), ...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Model, typename Form, typename = void>
|
||||
struct StellarModelNormalizationCoverage : std::false_type { };
|
||||
|
||||
template <typename Model, typename Form>
|
||||
requires model::StellarModelType<Model> && utils::blocks::block_form_is_valid_v<Form>
|
||||
struct StellarModelNormalizationCoverage<
|
||||
Model,
|
||||
Form,
|
||||
std::void_t<typename std::remove_cvref_t<Model>::SpecificationTypes>>
|
||||
: std::bool_constant<
|
||||
PrepareSpecificationNormalizations<
|
||||
typename std::remove_cvref_t<Model>::SpecificationTypes>::template completeFor<Form>> { };
|
||||
} // namespace detail
|
||||
|
||||
template <typename Specification>
|
||||
struct StellarSpecificationNormalizationContribution
|
||||
: detail::CompileStellarSpecificationNormalization<std::remove_cvref_t<Specification>> {
|
||||
using Base = detail::CompileStellarSpecificationNormalization<std::remove_cvref_t<Specification>>;
|
||||
|
||||
template <typename Form>
|
||||
static void Apply(
|
||||
DiagonalNormalizationBuilder<Form> &builder,
|
||||
const StellarCharacteristicScales &scales
|
||||
) {
|
||||
static_assert(
|
||||
Base::template completeFor<Form>,
|
||||
"The specification's generated blocks do not have a complete runtime normalization."
|
||||
);
|
||||
Base::template Apply<Form>(builder, scales);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Specification>
|
||||
concept RegisteredStellarSpecificationNormalization =
|
||||
StellarSpecificationNormalizationContribution<Specification>::registered;
|
||||
|
||||
template <typename Specification, typename Form>
|
||||
concept CompleteStellarSpecificationNormalizationFor =
|
||||
utils::blocks::block_form_is_valid_v<Form> &&
|
||||
StellarSpecificationNormalizationContribution<Specification>::template completeFor<Form>;
|
||||
|
||||
template <typename Model, typename Form>
|
||||
concept CompleteStellarNormalizationFor =
|
||||
detail::StellarModelNormalizationCoverage<
|
||||
std::remove_cvref_t<Model>,
|
||||
std::remove_cvref_t<Form>>::value;
|
||||
|
||||
/*
|
||||
* Physical Riesz preparation is an optional capability of a physical
|
||||
* core, not part of the protocol needed by the variadic equilibrium root.
|
||||
* Keeping this boundary structural lets a new EOS core opt in by exposing
|
||||
* the same discretization maps without inheriting from, or otherwise
|
||||
* naming, the Polytrope implementation.
|
||||
*/
|
||||
template <typename Candidate>
|
||||
concept PhysicalRieszStellarEquilibriumCore =
|
||||
operators::PreparedStellarEquilibriumPhysicalCore<std::remove_cvref_t<Candidate>> &&
|
||||
PhysicalRieszCoreRuntime<std::remove_cvref_t<Candidate>>;
|
||||
|
||||
template <typename Problem>
|
||||
concept PhysicalRieszStellarEquilibriumProblem =
|
||||
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||
requires {
|
||||
typename std::remove_cvref_t<Problem>::ModelType;
|
||||
typename std::remove_cvref_t<Problem>::FormType;
|
||||
typename std::remove_cvref_t<Problem>::PhysicalCoreType;
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
|
||||
requires PhysicalRieszDiagonalPrescription<
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType>;
|
||||
requires CompilableNormalizationFor<
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||
typename std::remove_cvref_t<Problem>::FormType>;
|
||||
requires CompleteStellarNormalizationFor<
|
||||
typename std::remove_cvref_t<Problem>::ModelType,
|
||||
typename std::remove_cvref_t<Problem>::FormType>;
|
||||
requires StellarNormalizationRuntimeAvailableFor<
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||
typename std::remove_cvref_t<Problem>::FormType,
|
||||
typename std::remove_cvref_t<Problem>::PhysicalCoreType,
|
||||
typename std::remove_cvref_t<Problem>::ModelType::SpecificationTypes>;
|
||||
};
|
||||
|
||||
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
||||
requires std::same_as<
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||
Unnormalized>
|
||||
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
|
||||
return DiagonalNormalization::Identity(problem.StateSize(), problem.EquationSize());
|
||||
}
|
||||
|
||||
template <PhysicalRieszStellarEquilibriumProblem Problem>
|
||||
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
|
||||
using ProblemType = std::remove_cvref_t<Problem>;
|
||||
using Form = typename ProblemType::FormType;
|
||||
|
||||
const fem::FEM &finiteElements = problem.GetDiscretization().finiteElementModel();
|
||||
if (!finiteElements.okay()) {
|
||||
throw std::invalid_argument("Physical Riesz preparation requires a current finite-element model.");
|
||||
}
|
||||
|
||||
const auto &physical = detail::PhysicalOperator(problem);
|
||||
const auto &gravityContext = physical.GetGravityContext();
|
||||
const auto &enthalpyMap = physical.GetHydrostaticOperator().GetEnthalpyMap();
|
||||
const auto scales = deriveStellarCharacteristicScales(
|
||||
problem.GetNormalizationPrescription(),
|
||||
problem.GetStellarModel()
|
||||
);
|
||||
|
||||
mfem::Array<int> stellarMarker =
|
||||
utils::domain::make_attribute_marker<utils::domain::Stellar, detail::DomainSchema>(*finiteElements.mesh);
|
||||
const mfem::Vector densityDiagonal = detail::GatherDiagonal(
|
||||
detail::AssembleScalarMassDiagonal(*finiteElements.densityFes, &stellarMarker),
|
||||
gravityContext.GetDensityMap(),
|
||||
"density"
|
||||
);
|
||||
const mfem::Vector enthalpyDiagonal = detail::GatherDiagonal(
|
||||
detail::AssembleScalarMassDiagonal(*finiteElements.enthalpyFes, &stellarMarker),
|
||||
enthalpyMap,
|
||||
"enthalpy"
|
||||
);
|
||||
const mfem::Vector gravityGradientDiagonal = detail::GatherDiagonal(
|
||||
detail::AssembleHDivMassDiagonal(*finiteElements.gravityFluxFes),
|
||||
gravityContext.GetGravityGradientMap(),
|
||||
"gravity-gradient"
|
||||
);
|
||||
const mfem::Vector gravityPotentialDiagonal = detail::GatherDiagonal(
|
||||
detail::AssembleScalarMassDiagonal(*finiteElements.gravityPotentialFes),
|
||||
gravityContext.GetGravityPotentialMap(),
|
||||
"gravity-potential"
|
||||
);
|
||||
const field::ScalarBoundaryDofMap surfaceMap =
|
||||
field::make_stellar_surface_scalar_dof_map<detail::DomainSchema>(*finiteElements.surfaceDeformationFes);
|
||||
const mfem::Vector surfaceDiagonal = detail::AssembleSurfaceMassDiagonal(finiteElements, surfaceMap);
|
||||
if (surfaceDiagonal.Size() != physical.GetDomainDeformation().parameterCount()) {
|
||||
throw std::logic_error("The reference surface Gram diagonal does not match the root surface block.");
|
||||
}
|
||||
|
||||
DiagonalNormalizationBuilder<Form> builder(problem.GetManifest().layout());
|
||||
builder.template SetValueBlock<utils::blocks::density::mass::value>(
|
||||
physicalScale<utils::blocks::density::mass::value>(scales), densityDiagonal
|
||||
);
|
||||
builder.template SetValueBlock<utils::blocks::surface_deformation::parameters::value>(
|
||||
physicalScale<utils::blocks::surface_deformation::parameters::value>(scales), surfaceDiagonal
|
||||
);
|
||||
builder.template SetValueBlock<utils::blocks::gravity::gradient::value>(
|
||||
physicalScale<utils::blocks::gravity::gradient::value>(scales), gravityGradientDiagonal
|
||||
);
|
||||
builder.template SetValueBlock<utils::blocks::gravity::poisson::value>(
|
||||
physicalScale<utils::blocks::gravity::poisson::value>(scales), gravityPotentialDiagonal
|
||||
);
|
||||
builder.template SetValueBlock<utils::blocks::enthalpy::specific::value>(
|
||||
physicalScale<utils::blocks::enthalpy::specific::value>(scales), enthalpyDiagonal
|
||||
);
|
||||
builder.template SetResidualBlock<utils::blocks::gravity::gradient::residual>(
|
||||
physicalScale<utils::blocks::gravity::gradient::residual>(scales), gravityGradientDiagonal
|
||||
);
|
||||
builder.template SetResidualBlock<utils::blocks::gravity::poisson::residual>(
|
||||
physicalScale<utils::blocks::gravity::poisson::residual>(scales), gravityPotentialDiagonal
|
||||
);
|
||||
builder.template SetResidualBlock<utils::blocks::density::mass::residual>(
|
||||
physicalScale<utils::blocks::density::mass::residual>(scales), densityDiagonal
|
||||
);
|
||||
builder.template SetResidualBlock<utils::blocks::surface_deformation::shape_equilibrium::residual>(
|
||||
physicalScale<utils::blocks::surface_deformation::shape_equilibrium::residual>(scales), surfaceDiagonal
|
||||
);
|
||||
const mfem::Array<int> &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
|
||||
builder.template SetHybridResidualBlock<utils::blocks::enthalpy::specific::residual>(
|
||||
physicalScale<utils::blocks::enthalpy::specific::residual>(scales),
|
||||
enthalpyDiagonal,
|
||||
std::span<const int>{surfaceRows.GetData(), static_cast<std::size_t>(surfaceRows.Size())}
|
||||
);
|
||||
detail::PrepareSpecificationNormalizations<typename ProblemType::ModelType::SpecificationTypes>::Apply(
|
||||
builder,
|
||||
scales
|
||||
);
|
||||
|
||||
return std::move(builder).Build();
|
||||
}
|
||||
|
||||
/* Public adapter for a third-party prescription. The implementation stays
|
||||
* beside the policy and has the readable signature
|
||||
*
|
||||
* prepareStellarNormalization(policy, problem)
|
||||
*
|
||||
* while every solver-facing caller continues to use the uniform
|
||||
* prepareNormalization(problem) operation. */
|
||||
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
||||
requires(
|
||||
!std::same_as<
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||
Unnormalized> &&
|
||||
!PhysicalRieszDiagonalPrescription<
|
||||
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType> &&
|
||||
RuntimePreparedNormalizationOperation<Problem>)
|
||||
[[nodiscard]] DiagonalNormalization prepareNormalization(
|
||||
const Problem &problem
|
||||
) {
|
||||
return prepareStellarNormalization(
|
||||
problem.GetNormalizationPrescription(),
|
||||
problem
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Solver-facing normalization exists exactly when runtime preparation for
|
||||
* the problem's compile-time prescription is a valid operation. This
|
||||
* folds future policy-owned preparation hooks into the same public contract and
|
||||
* turns unsupported core/prescription pairs into ordinary constraint
|
||||
* failure instead of an error in a constructor body.
|
||||
*/
|
||||
template <typename Problem>
|
||||
concept NormalizableStellarEquilibriumProblem =
|
||||
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||
requires(const std::remove_cvref_t<Problem> &problem) {
|
||||
{
|
||||
prepareNormalization(problem)
|
||||
} -> std::same_as<DiagonalNormalization>;
|
||||
};
|
||||
|
||||
struct NormalizedStellarEquilibriumStatistics final {
|
||||
std::uint64_t normalizationPreparations{0};
|
||||
std::uint64_t physicalPreparations{0};
|
||||
std::uint64_t residualRetrievals{0};
|
||||
std::uint64_t jacobianApplications{0};
|
||||
};
|
||||
|
||||
/*
|
||||
* The high-level stellar adapter retains a pointer to a prepared inverse.
|
||||
* Consequently that inverse must identify the exact physical problem and
|
||||
* expose its lifecycle state. Generic MFEM solvers remain valid inputs to
|
||||
* the lower-level ScaledPreconditioner, where no stellar association is
|
||||
* implied.
|
||||
*/
|
||||
template <typename Candidate, typename Problem>
|
||||
concept ProblemBoundStellarInverseFor =
|
||||
NormalizableStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
|
||||
requires(const std::remove_cvref_t<Candidate> &inverse) {
|
||||
{
|
||||
inverse.GetProblem()
|
||||
} -> std::same_as<const std::remove_cvref_t<Problem> &>;
|
||||
{
|
||||
inverse.IsCurrent()
|
||||
} -> std::same_as<bool>;
|
||||
};
|
||||
|
||||
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
|
||||
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||
class NormalizedStellarPreconditioner;
|
||||
|
||||
/*
|
||||
* Solver-facing coordinates for a dimensional stellar problem. The
|
||||
* physical problem remains the sole source of residual and Jacobian
|
||||
* physics; this adapter performs only the coordinate maps
|
||||
*
|
||||
* x = R x_hat, F_hat = L F, J_hat = L J R.
|
||||
*
|
||||
* Its normalization is immutable during Prepare/BuildResidual/Mult and is
|
||||
* changed only by an explicit RefreshNormalization call.
|
||||
*/
|
||||
template <NormalizableStellarEquilibriumProblem Problem>
|
||||
class NormalizedStellarEquilibriumOperator final : public mfem::Operator {
|
||||
private:
|
||||
using ProblemType = std::remove_cvref_t<Problem>;
|
||||
|
||||
public:
|
||||
explicit NormalizedStellarEquilibriumOperator(ProblemType &problem)
|
||||
: mfem::Operator(problem.EquationSize(), problem.StateSize()),
|
||||
m_problem(&problem),
|
||||
m_normalization(prepareNormalization(problem)),
|
||||
m_scaledJacobian(problem.GetLinearizationOperator(), m_normalization),
|
||||
m_physicalState(problem.StateSize()),
|
||||
m_physicalResidual(problem.EquationSize()),
|
||||
m_normalizedResidual(problem.EquationSize()) {
|
||||
if (Width() != Height()) {
|
||||
throw std::invalid_argument("A normalized stellar-equilibrium operator must be square.");
|
||||
}
|
||||
m_statistics.normalizationPreparations = 1;
|
||||
}
|
||||
|
||||
NormalizedStellarEquilibriumOperator(const NormalizedStellarEquilibriumOperator &) = delete;
|
||||
NormalizedStellarEquilibriumOperator &operator=(const NormalizedStellarEquilibriumOperator &) = delete;
|
||||
NormalizedStellarEquilibriumOperator(NormalizedStellarEquilibriumOperator &&) = delete;
|
||||
NormalizedStellarEquilibriumOperator &operator=(NormalizedStellarEquilibriumOperator &&) = delete;
|
||||
|
||||
[[nodiscard]] auto Prepare(
|
||||
const mfem::Vector &normalizedState,
|
||||
const operators::StellarEquilibriumDependencies &dependencies,
|
||||
const physics::RigidRotation &rotation
|
||||
) requires(ProblemType::generatedRotationProviderCount == 0) {
|
||||
if (normalizedState.Size() != Width()) {
|
||||
throw std::invalid_argument("The normalized stellar state has the wrong size.");
|
||||
}
|
||||
|
||||
m_isPrepared = false;
|
||||
m_normalization.DenormalizeState(normalizedState, m_physicalState);
|
||||
auto report = m_problem->Prepare(m_physicalState, dependencies, rotation);
|
||||
m_problem->BuildResidual(m_physicalResidual);
|
||||
m_normalization.NormalizeResidual(m_physicalResidual, m_normalizedResidual);
|
||||
m_physicalPreparationGeneration = m_problem->GetPreparationGeneration();
|
||||
m_isPrepared = true;
|
||||
++m_statistics.physicalPreparations;
|
||||
return report;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Prepare(
|
||||
const mfem::Vector &normalizedState,
|
||||
const operators::StellarEquilibriumDependencies &dependencies
|
||||
) requires(ProblemType::generatedRotationProviderCount == 1) {
|
||||
if (normalizedState.Size() != Width()) {
|
||||
throw std::invalid_argument("The normalized stellar state has the wrong size.");
|
||||
}
|
||||
|
||||
m_isPrepared = false;
|
||||
m_normalization.DenormalizeState(normalizedState, m_physicalState);
|
||||
auto report = m_problem->Prepare(m_physicalState, dependencies);
|
||||
m_problem->BuildResidual(m_physicalResidual);
|
||||
m_normalization.NormalizeResidual(m_physicalResidual, m_normalizedResidual);
|
||||
m_physicalPreparationGeneration = m_problem->GetPreparationGeneration();
|
||||
m_isPrepared = true;
|
||||
++m_statistics.physicalPreparations;
|
||||
return report;
|
||||
}
|
||||
|
||||
void BuildResidual(mfem::Vector &normalizedResidual) const {
|
||||
VerifyPrepared();
|
||||
normalizedResidual = m_normalizedResidual;
|
||||
++m_statistics.residualRetrievals;
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &normalizedDirection,
|
||||
mfem::Vector &normalizedAction
|
||||
) const override {
|
||||
VerifyPrepared();
|
||||
if (normalizedDirection.Size() != Width()) {
|
||||
throw std::invalid_argument("The normalized stellar direction has the wrong size.");
|
||||
}
|
||||
m_scaledJacobian.Mult(normalizedDirection, normalizedAction);
|
||||
++m_statistics.jacobianApplications;
|
||||
}
|
||||
|
||||
void RefreshNormalization() {
|
||||
DiagonalNormalization refreshed = prepareNormalization(*m_problem);
|
||||
m_normalization = std::move(refreshed);
|
||||
m_isPrepared = false;
|
||||
++m_statistics.normalizationPreparations;
|
||||
}
|
||||
|
||||
void NormalizeState(
|
||||
const mfem::Vector &physicalState,
|
||||
mfem::Vector &normalizedState
|
||||
) const {
|
||||
m_normalization.NormalizeState(physicalState, normalizedState);
|
||||
}
|
||||
|
||||
void DenormalizeState(
|
||||
const mfem::Vector &normalizedState,
|
||||
mfem::Vector &physicalState
|
||||
) const {
|
||||
m_normalization.DenormalizeState(normalizedState, physicalState);
|
||||
}
|
||||
|
||||
void NormalizeResidual(
|
||||
const mfem::Vector &physicalResidual,
|
||||
mfem::Vector &normalizedResidual
|
||||
) const {
|
||||
m_normalization.NormalizeResidual(physicalResidual, normalizedResidual);
|
||||
}
|
||||
|
||||
void DenormalizeResidual(
|
||||
const mfem::Vector &normalizedResidual,
|
||||
mfem::Vector &physicalResidual
|
||||
) const {
|
||||
m_normalization.DenormalizeResidual(normalizedResidual, physicalResidual);
|
||||
}
|
||||
|
||||
template <typename PhysicalInverse>
|
||||
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||
[[nodiscard]] NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
|
||||
MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||
return m_isPrepared && m_problem->IsPrepared() &&
|
||||
m_physicalPreparationGeneration == m_problem->GetPreparationGeneration();
|
||||
}
|
||||
|
||||
[[nodiscard]] ProblemType &GetPhysicalProblem() noexcept {
|
||||
return *m_problem;
|
||||
}
|
||||
|
||||
[[nodiscard]] const ProblemType &GetPhysicalProblem() const noexcept {
|
||||
return *m_problem;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
|
||||
return m_problem->GetLinearizationOperator();
|
||||
}
|
||||
|
||||
[[nodiscard]] const ProblemType &GetProblem() const noexcept {
|
||||
return *m_problem;
|
||||
}
|
||||
|
||||
[[nodiscard]] const DiagonalNormalization &GetNormalization() const noexcept {
|
||||
return m_normalization;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetPhysicalState() const {
|
||||
VerifyPrepared();
|
||||
return m_physicalState;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetPhysicalResidual() const {
|
||||
VerifyPrepared();
|
||||
return m_physicalResidual;
|
||||
}
|
||||
|
||||
[[nodiscard]] const NormalizedStellarEquilibriumStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
void VerifyPrepared() const {
|
||||
if (!IsPrepared()) {
|
||||
throw std::logic_error(
|
||||
"The normalized stellar-equilibrium operator must be prepared and current before application."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProblemType *m_problem;
|
||||
DiagonalNormalization m_normalization;
|
||||
ScaledJacobianOperator m_scaledJacobian;
|
||||
mfem::Vector m_physicalState;
|
||||
mfem::Vector m_physicalResidual;
|
||||
mfem::Vector m_normalizedResidual;
|
||||
std::uint64_t m_physicalPreparationGeneration{0};
|
||||
mutable NormalizedStellarEquilibriumStatistics m_statistics;
|
||||
bool m_isPrepared{false};
|
||||
};
|
||||
|
||||
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
|
||||
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||
class NormalizedStellarPreconditioner final : public mfem::Solver {
|
||||
private:
|
||||
using ProblemType = std::remove_cvref_t<Problem>;
|
||||
using NormalizedOperator = NormalizedStellarEquilibriumOperator<ProblemType>;
|
||||
using PhysicalInverseType = std::remove_cvref_t<PhysicalInverse>;
|
||||
|
||||
[[nodiscard]] static PhysicalInverseType &RequireAssociatedPhysicalInverse(
|
||||
const NormalizedOperator &normalizedOperator,
|
||||
PhysicalInverseType &physicalInverse
|
||||
) {
|
||||
if (std::addressof(physicalInverse.GetProblem()) !=
|
||||
std::addressof(normalizedOperator.GetProblem())) {
|
||||
throw std::invalid_argument(
|
||||
"A normalized stellar preconditioner and its physical inverse must belong to the same problem."
|
||||
);
|
||||
}
|
||||
return physicalInverse;
|
||||
}
|
||||
|
||||
public:
|
||||
NormalizedStellarPreconditioner(
|
||||
const NormalizedOperator &normalizedOperator,
|
||||
PhysicalInverseType &physicalInverse
|
||||
)
|
||||
: mfem::Solver(
|
||||
normalizedOperator.Width(),
|
||||
normalizedOperator.Height(),
|
||||
physicalInverse.iterative_mode
|
||||
),
|
||||
m_normalizedOperator(&normalizedOperator),
|
||||
m_physicalInverse(&physicalInverse),
|
||||
m_scaled(
|
||||
RequireAssociatedPhysicalInverse(normalizedOperator, physicalInverse),
|
||||
normalizedOperator.GetPhysicalJacobian(),
|
||||
normalizedOperator,
|
||||
normalizedOperator.GetNormalization()
|
||||
) {
|
||||
}
|
||||
|
||||
NormalizedStellarPreconditioner(const NormalizedStellarPreconditioner &) = delete;
|
||||
NormalizedStellarPreconditioner &operator=(const NormalizedStellarPreconditioner &) = delete;
|
||||
NormalizedStellarPreconditioner(NormalizedStellarPreconditioner &&) = delete;
|
||||
NormalizedStellarPreconditioner &operator=(NormalizedStellarPreconditioner &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &normalizedJacobian) override {
|
||||
VerifyCurrent();
|
||||
if (&normalizedJacobian != m_normalizedOperator) {
|
||||
throw std::invalid_argument(
|
||||
"The normalized stellar preconditioner cannot be rebound to a different Jacobian."
|
||||
);
|
||||
}
|
||||
m_scaled.SetOperator(normalizedJacobian);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &normalizedResidual,
|
||||
mfem::Vector &normalizedCorrection
|
||||
) const override {
|
||||
VerifyCurrent();
|
||||
m_scaled.Mult(normalizedResidual, normalizedCorrection);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCurrent() const {
|
||||
return m_normalizedOperator->IsPrepared() &&
|
||||
m_physicalInverse->IsCurrent();
|
||||
}
|
||||
|
||||
[[nodiscard]] PhysicalInverseType &GetPhysicalInverse() noexcept {
|
||||
return *m_physicalInverse;
|
||||
}
|
||||
|
||||
[[nodiscard]] const PhysicalInverseType &GetPhysicalInverse() const noexcept {
|
||||
return *m_physicalInverse;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
|
||||
return m_scaled.GetPhysicalJacobian();
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator &GetNormalizedJacobian() const {
|
||||
return m_scaled.GetNormalizedJacobian();
|
||||
}
|
||||
|
||||
[[nodiscard]] const ScaledPreconditionerStatistics &GetStatistics() const noexcept {
|
||||
return m_scaled.GetStatistics();
|
||||
}
|
||||
|
||||
private:
|
||||
void VerifyCurrent() const {
|
||||
if (!IsCurrent()) {
|
||||
throw std::logic_error(
|
||||
"The normalized stellar preconditioner cannot be used while its normalized operator or physical "
|
||||
"inverse is stale."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const NormalizedOperator *m_normalizedOperator;
|
||||
PhysicalInverseType *m_physicalInverse;
|
||||
ScaledPreconditioner m_scaled;
|
||||
};
|
||||
|
||||
template <NormalizableStellarEquilibriumProblem Problem>
|
||||
template <typename PhysicalInverse>
|
||||
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||
NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
|
||||
NormalizedStellarEquilibriumOperator<Problem>::MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const {
|
||||
VerifyPrepared();
|
||||
return NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>{
|
||||
*this,
|
||||
physicalInverse
|
||||
};
|
||||
}
|
||||
|
||||
template <NormalizableStellarEquilibriumProblem Problem>
|
||||
[[nodiscard]] auto makeNormalizedStellarEquilibriumOperator(Problem &problem) {
|
||||
return NormalizedStellarEquilibriumOperator<Problem>{problem};
|
||||
}
|
||||
} // namespace mean_field::normalization
|
||||
Reference in New Issue
Block a user