Files
MeanField/libmeanfield/interface/preconditioning/specification_border.cppm
2026-09-06 10:15:00 -04:00

2773 lines
122 KiB
C++

module;
#include <algorithm>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:preconditioning.specification_border;
export import :operators.stellar_equilibrium_compiler;
export import :preconditioning.stellar_equilibrium;
export import :preconditioning.stellar_structure;
export namespace mean_field::preconditioning {
namespace detail {
template <typename OperatorCouplings> struct ToPreconditionerCouplings;
template <typename... OperatorCouplings>
struct ToPreconditionerCouplings<utils::blocks::type_list<OperatorCouplings...>> {
using Type = utils::blocks::type_list<Coupling<
typename OperatorCouplings::Residual,
typename OperatorCouplings::Value>...>;
};
template <typename Remaining, typename Accumulated>
struct CollectUniqueCouplings;
template <typename Accumulated>
struct CollectUniqueCouplings<utils::blocks::type_list<>, Accumulated> {
using Type = Accumulated;
};
template <typename Head, typename... Tail, typename Accumulated>
struct CollectUniqueCouplings<
utils::blocks::type_list<Head, Tail...>,
Accumulated> {
using Type = typename CollectUniqueCouplings<
utils::blocks::type_list<Tail...>,
AppendUniqueT<Accumulated, Head>>::Type;
};
template <typename... Lists>
using UniqueConcatenatedCouplingsT = typename CollectUniqueCouplings<
ConcatenateT<Lists...>,
utils::blocks::type_list<>>::Type;
} // namespace detail
/*
* Preconditioner topology is a projection of the authoritative operator
* compilation. It therefore cannot silently drift from the residual or
* Jacobian when a new specification is added.
*/
template <models::ModelSpecification Specification>
struct SpecificationBorderContribution {
private:
using OperatorCompilation =
operators::StellarEquilibriumSpecificationCompilation<Specification>;
public:
using CorrectionBlocks = typename OperatorCompilation::GeneratedCorrectionBlocks;
using ResidualBlocks = typename OperatorCompilation::GeneratedResidualBlocks;
using RequiredCouplings = typename detail::ToPreconditionerCouplings<
typename OperatorCompilation::IncidentJacobianCouplings>::Type;
static constexpr bool registered =
operators::stellarEquilibriumSpecificationCompilationComplete<Specification>;
};
namespace detail {
template <models::ModelSpecification Specification>
inline constexpr std::size_t generatedBorderValueArity =
models::specificationDescriptor<Specification>().generatedValueArity;
template <models::ModelSpecification Specification>
inline constexpr std::size_t generatedBorderResidualArity =
models::specificationDescriptor<Specification>().generatedResidualArity;
template <models::ModelSpecification Specification>
inline constexpr bool specificationGeneratesBorder =
generatedBorderValueArity<Specification> != 0 || generatedBorderResidualArity<Specification> != 0;
template <models::ModelSpecification Specification>
inline constexpr bool specificationBorderContributionIsComplete =
!specificationGeneratesBorder<Specification> ||
(SpecificationBorderContribution<Specification>::registered &&
generatedBorderValueArity<Specification> == generatedBorderResidualArity<Specification> &&
SpecificationBorderContribution<Specification>::CorrectionBlocks::size == 1 &&
SpecificationBorderContribution<Specification>::ResidualBlocks::size == 1);
template <typename SpecificationSet> struct CompiledSpecificationBorder;
template <models::ModelSpecification... Specifications>
struct CompiledSpecificationBorder<models::detail::SpecificationSetStorage<Specifications...>> {
static_assert(
(specificationBorderContributionIsComplete<Specifications> && ...),
"Every specification-generated border requires a registered preconditioning contribution with "
"balanced value and residual arity."
);
using SpecificationTypes = models::detail::SpecificationSetStorage<Specifications...>;
using CorrectionBlocks = preconditioning::detail::ConcatenateT<
typename SpecificationBorderContribution<Specifications>::CorrectionBlocks...>;
using ResidualBlocks = preconditioning::detail::ConcatenateT<
typename SpecificationBorderContribution<Specifications>::ResidualBlocks...>;
using RequiredCouplings = preconditioning::detail::UniqueConcatenatedCouplingsT<
typename SpecificationBorderContribution<Specifications>::RequiredCouplings...>;
static constexpr std::size_t valueArity =
(std::size_t{0} + ... + generatedBorderValueArity<Specifications>);
static constexpr std::size_t residualArity =
(std::size_t{0} + ... + generatedBorderResidualArity<Specifications>);
static constexpr std::size_t specificationCount =
(std::size_t{0} + ... + (specificationGeneratesBorder<Specifications> ? 1U : 0U));
static constexpr bool symbolicallySquare = valueArity == residualArity;
};
template <typename Query, typename SpecificationSet> struct SpecificationBorderValueOffset;
template <typename Query, models::ModelSpecification Head, models::ModelSpecification... Tail>
struct SpecificationBorderValueOffset<Query, models::detail::SpecificationSetStorage<Head, Tail...>> {
static constexpr std::size_t value = [] {
if constexpr (std::same_as<Query, Head>) {
return std::size_t{0};
} else {
static_assert(sizeof...(Tail) > 0, "The requested border specification is not in the model.");
return generatedBorderValueArity<Head> +
SpecificationBorderValueOffset<
Query, models::detail::SpecificationSetStorage<Tail...>>::value;
}
}();
};
template <typename Query, typename SpecificationSet> struct SpecificationBorderResidualOffset;
template <typename Query, models::ModelSpecification Head, models::ModelSpecification... Tail>
struct SpecificationBorderResidualOffset<Query, models::detail::SpecificationSetStorage<Head, Tail...>> {
static constexpr std::size_t value = [] {
if constexpr (std::same_as<Query, Head>) {
return std::size_t{0};
} else {
static_assert(sizeof...(Tail) > 0, "The requested border specification is not in the model.");
return generatedBorderResidualArity<Head> +
SpecificationBorderResidualOffset<
Query, models::detail::SpecificationSetStorage<Tail...>>::value;
}
}();
};
} // namespace detail
template <model::StellarModelType Model>
using CompiledSpecificationBorderFor =
detail::CompiledSpecificationBorder<typename std::remove_cvref_t<Model>::SpecificationTypes>;
template <models::ModelSpecification Specification, model::StellarModelType Model>
inline constexpr std::size_t specificationBorderValueOffset = detail::
SpecificationBorderValueOffset<Specification, typename std::remove_cvref_t<Model>::SpecificationTypes>::value;
template <models::ModelSpecification Specification, model::StellarModelType Model>
inline constexpr std::size_t specificationBorderResidualOffset = detail::SpecificationBorderResidualOffset<
Specification,
typename std::remove_cvref_t<Model>::SpecificationTypes>::value;
using SpecificationBorderCharacteristics = OperatorCharacteristics<
OperatorCategory::dense_border,
OperatorValueStructure::block,
OperatorSymmetry::nonsymmetric,
OperatorDefiniteness::indefinite,
OperatorRepresentation::assembled_dense,
OperatorDistribution::local>;
using BorderedStellarStructureCharacteristics = OperatorCharacteristics<
OperatorCategory::mixed,
OperatorValueStructure::block,
OperatorSymmetry::nonsymmetric,
OperatorDefiniteness::unspecified,
OperatorRepresentation::matrix_free,
OperatorDistribution::distributed_true_dof,
OperatorFESpace::product>;
namespace backend {
template <Registered StructureBackend, Registered BorderBackend = DenseDirect>
struct BorderedStellarStructure final {
using StructureBackendType = StructureBackend;
using BorderBackendType = BorderBackend;
};
template <Registered StructureBackend, Registered BorderBackend>
struct Traits<BorderedStellarStructure<StructureBackend, BorderBackend>> {
static constexpr bool registered = true;
static constexpr ApplicationContract applicationContract =
::mean_field::preconditioning::backend::applicationContract<StructureBackend> ==
ApplicationContract::stationary_linear &&
::mean_field::preconditioning::backend::applicationContract<BorderBackend> ==
ApplicationContract::stationary_linear
? ApplicationContract::stationary_linear
: ApplicationContract::flexible;
static constexpr bool supportsSerialExecution = Traits<StructureBackend>::supportsSerialExecution;
static constexpr bool supportsDistributedExecution =
Traits<StructureBackend>::supportsDistributedExecution &&
Traits<BorderBackend>::supportsSerialExecution;
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none;
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::constant_mode_supported;
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_sparse;
static constexpr bool requiresAssembledSparseSurrogate =
Traits<StructureBackend>::requiresAssembledSparseSurrogate;
using PreparationDependencies = preconditioning::PreparationDependencies<
PreparationDependency::discretization,
PreparationDependency::geometry,
PreparationDependency::equation_of_state,
PreparationDependency::linearization>;
template <OperatorCharacteristicsType Characteristics>
static constexpr bool supports =
Characteristics::category == OperatorCategory::mixed &&
Characteristics::valueStructure == OperatorValueStructure::block &&
Characteristics::symmetry == OperatorSymmetry::nonsymmetric &&
Characteristics::representation == OperatorRepresentation::matrix_free &&
Characteristics::distribution == OperatorDistribution::distributed_true_dof &&
Characteristics::finiteElementSpace == OperatorFESpace::product;
};
} // namespace backend
template <
PreconditionerComponent StructureComponentT,
model::StellarModelType ModelT,
typename FormT,
typename JacobianFormT>
requires utils::blocks::valid_jacobian_form<FormT, JacobianFormT>
class SpecificationBorderBlock final {
private:
using CompiledBorder = CompiledSpecificationBorderFor<ModelT>;
public:
using StructureComponent = StructureComponentT;
using Model = ModelT;
using Form = FormT;
using JacobianForm = JacobianFormT;
using CorrectionBlocks = preconditioning::detail::
ConcatenateT<typename StructureComponent::CorrectionBlocks, typename CompiledBorder::CorrectionBlocks>;
using ResidualBlocks = preconditioning::detail::
ConcatenateT<typename StructureComponent::ResidualBlocks, typename CompiledBorder::ResidualBlocks>;
using RequiredCouplings = preconditioning::detail::UniqueConcatenatedCouplingsT<
typename StructureComponent::RequiredCouplings,
typename CompiledBorder::RequiredCouplings>;
using OperatorDescription = BorderedStellarStructureCharacteristics;
using BackendType =
backend::BorderedStellarStructure<typename StructureComponent::BackendType, backend::DenseDirect>;
using PreparationDependencies = typename backend::Traits<BackendType>::PreparationDependencies;
static constexpr std::size_t borderValueArity = CompiledBorder::valueArity;
static constexpr std::size_t borderResidualArity = CompiledBorder::residualArity;
constexpr explicit SpecificationBorderBlock(
StructureComponent structureComponent,
backend::DenseDirect borderBackend = {}
)
: m_structureComponent(std::move(structureComponent)),
m_borderBackend(std::move(borderBackend)) {
static_assert(CompiledBorder::symbolicallySquare);
}
[[nodiscard]] constexpr const StructureComponent &structureComponent() const noexcept {
return m_structureComponent;
}
[[nodiscard]] constexpr const backend::DenseDirect &borderBackend() const noexcept {
return m_borderBackend;
}
private:
StructureComponent m_structureComponent;
backend::DenseDirect m_borderBackend;
};
template <typename Candidate> struct IsSpecificationBorderBlock : std::false_type { };
template <
PreconditionerComponent StructureComponent,
model::StellarModelType Model,
typename Form,
typename JacobianForm>
struct IsSpecificationBorderBlock<SpecificationBorderBlock<StructureComponent, Model, Form, JacobianForm>>
: std::true_type { };
template <typename Candidate>
concept SpecificationBorderBlockType = IsSpecificationBorderBlock<std::remove_cvref_t<Candidate>>::value;
struct StellarStructureDirectionView final {
const mfem::Vector &density;
const mfem::Vector &surface;
const mfem::Vector &enthalpy;
const mfem::Vector &gravityGradient;
const mfem::Vector &gravityPotential;
};
struct StellarStructureActionView final {
mfem::Vector &density;
mfem::Vector &surface;
mfem::Vector &enthalpy;
mfem::Vector &gravityGradient;
mfem::Vector &gravityPotential;
};
namespace detail {
template <typename Blocks> struct SingleSpecificationBorderBlock;
template <typename Block>
struct SingleSpecificationBorderBlock<utils::blocks::type_list<Block>> {
using Type = Block;
};
template <models::ModelSpecification Specification>
using GeneratedSpecificationValueBlock = typename SingleSpecificationBorderBlock<
typename SpecificationBorderContribution<Specification>::CorrectionBlocks>::Type;
template <models::ModelSpecification Specification>
using GeneratedSpecificationResidualBlock = typename SingleSpecificationBorderBlock<
typename SpecificationBorderContribution<Specification>::ResidualBlocks>::Type;
template <typename Block> struct GeneratedBorderValueOwner {
static constexpr bool available = false;
};
template <typename Generated>
requires requires { typename Generated::SpecificationType; }
struct GeneratedBorderValueOwner<utils::blocks::generated_value_block<Generated>> {
using Specification = typename Generated::SpecificationType;
static constexpr bool available = models::ModelSpecification<Specification>;
};
template <typename Block> struct GeneratedBorderResidualOwner {
static constexpr bool available = false;
};
template <typename Generated>
requires requires { typename Generated::SpecificationType; }
struct GeneratedBorderResidualOwner<utils::blocks::generated_residual_block<Generated>> {
using Specification = typename Generated::SpecificationType;
static constexpr bool available = models::ModelSpecification<Specification>;
};
template <typename Block>
inline constexpr bool isGeneratedBorderValue =
GeneratedBorderValueOwner<std::remove_cvref_t<Block>>::available;
template <typename Block>
inline constexpr bool isGeneratedBorderResidual =
GeneratedBorderResidualOwner<std::remove_cvref_t<Block>>::available;
template <typename Block>
inline constexpr bool isStellarStructureValue =
std::same_as<std::remove_cvref_t<Block>, utils::blocks::density::mass::value> ||
std::same_as<std::remove_cvref_t<Block>, utils::blocks::surface_deformation::parameters::value> ||
std::same_as<std::remove_cvref_t<Block>, utils::blocks::enthalpy::specific::value> ||
std::same_as<std::remove_cvref_t<Block>, utils::blocks::gravity::gradient::value> ||
std::same_as<std::remove_cvref_t<Block>, utils::blocks::gravity::poisson::value>;
template <typename Block>
inline constexpr bool isStellarStructureResidual =
std::same_as<std::remove_cvref_t<Block>, utils::blocks::density::mass::residual> ||
std::same_as<
std::remove_cvref_t<Block>,
utils::blocks::surface_deformation::shape_equilibrium::residual> ||
std::same_as<std::remove_cvref_t<Block>, utils::blocks::enthalpy::specific::residual> ||
std::same_as<std::remove_cvref_t<Block>, utils::blocks::gravity::gradient::residual> ||
std::same_as<std::remove_cvref_t<Block>, utils::blocks::gravity::poisson::residual>;
enum class SpecificationBorderOperation {
structure_to_border,
border_to_structure,
border_to_border
};
template <typename Query, typename Coupling, SpecificationBorderOperation Operation>
[[nodiscard]] consteval bool directionParticipatesInCoupling() {
if constexpr (!std::same_as<std::remove_cvref_t<Query>, typename Coupling::Correction>) {
return false;
} else if constexpr (Operation == SpecificationBorderOperation::structure_to_border) {
return isStellarStructureValue<Query> && isGeneratedBorderResidual<typename Coupling::Residual>;
} else if constexpr (Operation == SpecificationBorderOperation::border_to_structure) {
return isGeneratedBorderValue<Query> && isStellarStructureResidual<typename Coupling::Residual>;
} else {
return isGeneratedBorderValue<Query> && isGeneratedBorderResidual<typename Coupling::Residual>;
}
}
template <typename Query, typename Coupling, SpecificationBorderOperation Operation>
[[nodiscard]] consteval bool actionParticipatesInCoupling() {
if constexpr (!std::same_as<std::remove_cvref_t<Query>, typename Coupling::Residual>) {
return false;
} else if constexpr (Operation == SpecificationBorderOperation::structure_to_border) {
return isGeneratedBorderResidual<Query> && isStellarStructureValue<typename Coupling::Correction>;
} else if constexpr (Operation == SpecificationBorderOperation::border_to_structure) {
return isStellarStructureResidual<Query> && isGeneratedBorderValue<typename Coupling::Correction>;
} else {
return isGeneratedBorderResidual<Query> && isGeneratedBorderValue<typename Coupling::Correction>;
}
}
template <typename Block>
[[nodiscard]] const mfem::Vector &structureDirectionBlock(
const StellarStructureDirectionView &view
) {
using BlockType = std::remove_cvref_t<Block>;
static_assert(isStellarStructureValue<BlockType>);
if constexpr (std::same_as<BlockType, utils::blocks::density::mass::value>) {
return view.density;
} else if constexpr (
std::same_as<BlockType, utils::blocks::surface_deformation::parameters::value>) {
return view.surface;
} else if constexpr (std::same_as<BlockType, utils::blocks::enthalpy::specific::value>) {
return view.enthalpy;
} else if constexpr (std::same_as<BlockType, utils::blocks::gravity::gradient::value>) {
return view.gravityGradient;
} else {
return view.gravityPotential;
}
}
template <typename Block>
[[nodiscard]] mfem::Vector &structureActionBlock(StellarStructureActionView &view) {
using BlockType = std::remove_cvref_t<Block>;
static_assert(isStellarStructureResidual<BlockType>);
if constexpr (std::same_as<BlockType, utils::blocks::density::mass::residual>) {
return view.density;
} else if constexpr (
std::same_as<BlockType, utils::blocks::surface_deformation::shape_equilibrium::residual>) {
return view.surface;
} else if constexpr (std::same_as<BlockType, utils::blocks::enthalpy::specific::residual>) {
return view.enthalpy;
} else if constexpr (std::same_as<BlockType, utils::blocks::gravity::gradient::residual>) {
return view.gravityGradient;
} else {
return view.gravityPotential;
}
}
template <typename Specification, typename Problem>
concept SpecificationBelongsToProblem =
models::ModelSpecification<std::remove_cvref_t<Specification>> &&
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
std::remove_cvref_t<Problem>::ModelType::template containsSpecification<
std::remove_cvref_t<Specification>>;
} // namespace detail
/* A single callback sees one compiled Jacobian edge, never the union of all
* legal sources and rows for its specification. Binding both block types
* into these two tiny views prevents a callback from reading one direction
* block while claiming that its contribution differentiates another. */
template <
models::ModelSpecification Specification,
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
detail::SpecificationBorderOperation Operation,
typename ResidualBlock,
typename ValueBlock>
requires detail::SpecificationBelongsToProblem<Specification, Problem>
class SpecificationBorderCouplingDirectionView final {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using Model = typename ProblemType::ModelType;
using CouplingType = Coupling<
std::remove_cvref_t<ResidualBlock>,
std::remove_cvref_t<ValueBlock>>;
using Couplings =
typename SpecificationBorderContribution<Specification>::RequiredCouplings;
static constexpr bool permitted =
utils::blocks::contains_type_v<
CouplingType,
Couplings> &&
detail::directionParticipatesInCoupling<
std::remove_cvref_t<ValueBlock>,
CouplingType,
Operation>() &&
detail::actionParticipatesInCoupling<
std::remove_cvref_t<ResidualBlock>,
CouplingType,
Operation>();
public:
explicit SpecificationBorderCouplingDirectionView(
const StellarStructureDirectionView &structure
) noexcept
requires(
permitted &&
Operation == detail::SpecificationBorderOperation::structure_to_border
)
: m_structure(std::addressof(structure)) {
}
explicit SpecificationBorderCouplingDirectionView(
const mfem::Vector &border
) noexcept
requires(
permitted &&
Operation != detail::SpecificationBorderOperation::structure_to_border
)
: m_border(std::addressof(border)) {
}
[[nodiscard]] decltype(auto) values() const {
if constexpr (detail::isStellarStructureValue<ValueBlock>) {
return detail::structureDirectionBlock<ValueBlock>(*m_structure);
} else {
using Owner =
typename detail::GeneratedBorderValueOwner<ValueBlock>::Specification;
constexpr int offset = static_cast<int>(
specificationBorderValueOffset<Owner, Model>
);
return operators::ReadOnlyVectorView{
*m_border,
offset,
ValueBlock::static_block_size
};
}
}
template <typename Term>
requires requires { typename std::remove_cvref_t<Term>::value; } &&
std::same_as<
ValueBlock,
typename std::remove_cvref_t<Term>::value>
[[nodiscard]] decltype(auto) block(const Term &) const {
return values();
}
[[nodiscard]] int Size() const {
return values().Size();
}
[[nodiscard]] int size() const {
return Size();
}
[[nodiscard]] double operator()(const int index) const {
return values()(index);
}
[[nodiscard]] decltype(auto) density() const
requires std::same_as<ValueBlock, utils::blocks::density::mass::value> {
return values();
}
[[nodiscard]] decltype(auto) surfaceShape() const
requires std::same_as<
ValueBlock,
utils::blocks::surface_deformation::parameters::value> {
return values();
}
[[nodiscard]] decltype(auto) specificEnthalpy() const
requires std::same_as<ValueBlock, utils::blocks::enthalpy::specific::value> {
return values();
}
[[nodiscard]] decltype(auto) gravityGradient() const
requires std::same_as<ValueBlock, utils::blocks::gravity::gradient::value> {
return values();
}
[[nodiscard]] decltype(auto) gravitationalPotential() const
requires std::same_as<ValueBlock, utils::blocks::gravity::poisson::value> {
return values();
}
[[nodiscard]] decltype(auto) gravityPotential() const
requires std::same_as<ValueBlock, utils::blocks::gravity::poisson::value> {
return gravitationalPotential();
}
template <models::ModelSpecification Owner = Specification>
requires std::same_as<
ValueBlock,
detail::GeneratedSpecificationValueBlock<Owner>>
[[nodiscard]] decltype(auto) generatedCoordinate() const {
return values();
}
private:
const StellarStructureDirectionView *m_structure{nullptr};
const mfem::Vector *m_border{nullptr};
};
template <
models::ModelSpecification Specification,
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
detail::SpecificationBorderOperation Operation,
typename ResidualBlock,
typename ValueBlock>
requires detail::SpecificationBelongsToProblem<Specification, Problem>
class SpecificationBorderCouplingRowAction final {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using Model = typename ProblemType::ModelType;
using CouplingType = Coupling<
std::remove_cvref_t<ResidualBlock>,
std::remove_cvref_t<ValueBlock>>;
using Couplings =
typename SpecificationBorderContribution<Specification>::RequiredCouplings;
static constexpr bool permitted =
utils::blocks::contains_type_v<CouplingType, Couplings> &&
detail::directionParticipatesInCoupling<
std::remove_cvref_t<ValueBlock>,
CouplingType,
Operation>() &&
detail::actionParticipatesInCoupling<
std::remove_cvref_t<ResidualBlock>,
CouplingType,
Operation>();
public:
SpecificationBorderCouplingRowAction(
StellarStructureActionView &structure,
const field::FieldBoundaryDofMap &surfaceRows
) noexcept
requires(
permitted &&
Operation == detail::SpecificationBorderOperation::border_to_structure
)
: m_structure(std::addressof(structure)),
m_surfaceRows(std::addressof(surfaceRows)) {
}
explicit SpecificationBorderCouplingRowAction(mfem::Vector &border) noexcept
requires(
permitted &&
Operation != detail::SpecificationBorderOperation::border_to_structure
)
: m_border(std::addressof(border)) {
}
[[nodiscard]] stellar::ContributionAdded add(
const double contribution
) {
RequireUnused();
decltype(auto) target = block();
target += contribution;
RestoreReplacedRows(target, contribution);
SynchronizeGeneratedBlock(target);
m_addCount = 1;
return {};
}
[[nodiscard]] stellar::ContributionAdded add(
const mfem::Vector &contribution
) {
RequireUnused();
decltype(auto) target = block();
if (target.Size() != contribution.Size()) {
throw std::invalid_argument(
"A specification-border physics contribution has the wrong block size."
);
}
target += contribution;
RestoreReplacedRows(target, contribution);
SynchronizeGeneratedBlock(target);
m_addCount = 1;
return {};
}
template <stellar::ContributionResult Result>
void Verify(const Result &) const {
if constexpr (std::same_as<
std::remove_cvref_t<Result>,
stellar::ContributionAdded>) {
if (m_addCount != 1) {
throw std::logic_error(
"A specification-border provider returned ContributionAdded without adding exactly once."
);
}
} else if (m_addCount != 0) {
throw std::logic_error(
"A specification-border provider returned StructuralZero after adding to its row."
);
}
}
private:
void RequireUnused() const {
if (m_addCount != 0) {
throw std::logic_error(
"A compiler-enumerated specification-border edge may be assembled only once."
);
}
}
[[nodiscard]] decltype(auto) block() const {
if constexpr (detail::isStellarStructureResidual<ResidualBlock>) {
return detail::structureActionBlock<ResidualBlock>(*m_structure);
} else {
using Owner =
typename detail::GeneratedBorderResidualOwner<ResidualBlock>::Specification;
constexpr int offset = static_cast<int>(
specificationBorderResidualOffset<Owner, Model>
);
return mfem::Vector(
m_border->GetData() + offset,
ResidualBlock::static_block_size
);
}
}
void RestoreReplacedRows(
mfem::Vector &target,
const double contribution
) const {
if constexpr (
Operation == detail::SpecificationBorderOperation::border_to_structure &&
std::same_as<ResidualBlock, utils::blocks::enthalpy::specific::residual>) {
for (const int row : m_surfaceRows->reduced_dofs()) {
target(row) -= contribution;
}
}
}
void RestoreReplacedRows(
mfem::Vector &target,
const mfem::Vector &contribution
) const {
if constexpr (
Operation == detail::SpecificationBorderOperation::border_to_structure &&
std::same_as<ResidualBlock, utils::blocks::enthalpy::specific::residual>) {
for (const int row : m_surfaceRows->reduced_dofs()) {
target(row) -= contribution(row);
}
}
}
void SynchronizeGeneratedBlock(mfem::Vector &target) const {
if constexpr (detail::isGeneratedBorderResidual<ResidualBlock>) {
target.SyncAliasMemory(*m_border);
}
}
StellarStructureActionView *m_structure{nullptr};
mfem::Vector *m_border{nullptr};
const field::FieldBoundaryDofMap *m_surfaceRows{nullptr};
int m_addCount{0};
};
/* This is the only object delivered to extension physics for an operation.
* A callback is invoked only after an exact compiled (row, source) edge has
* been selected. Its direction argument contains that source alone and
* its row argument supports additive updates to that row alone. */
template <
models::ModelSpecification Specification,
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
detail::SpecificationBorderOperation Operation>
requires detail::SpecificationBelongsToProblem<Specification, Problem>
class SpecificationBorderActionView final {
private:
using Couplings =
typename SpecificationBorderContribution<Specification>::RequiredCouplings;
template <typename ResidualBlock, typename ValueBlock>
using Direction = SpecificationBorderCouplingDirectionView<
Specification,
Problem,
Operation,
std::remove_cvref_t<ResidualBlock>,
std::remove_cvref_t<ValueBlock>>;
template <typename ResidualBlock, typename ValueBlock>
using RowAction = SpecificationBorderCouplingRowAction<
Specification,
Problem,
Operation,
std::remove_cvref_t<ResidualBlock>,
std::remove_cvref_t<ValueBlock>>;
template <typename ResidualBlock, typename ValueBlock>
static constexpr bool permitsCoupling =
utils::blocks::contains_type_v<
Coupling<
std::remove_cvref_t<ResidualBlock>,
std::remove_cvref_t<ValueBlock>>,
Couplings> &&
detail::directionParticipatesInCoupling<
std::remove_cvref_t<ValueBlock>,
Coupling<
std::remove_cvref_t<ResidualBlock>,
std::remove_cvref_t<ValueBlock>>,
Operation>() &&
detail::actionParticipatesInCoupling<
std::remove_cvref_t<ResidualBlock>,
Coupling<
std::remove_cvref_t<ResidualBlock>,
std::remove_cvref_t<ValueBlock>>,
Operation>();
template <typename ResidualBlock, typename ValueBlock, typename Callback>
static constexpr bool completesCoupling = requires(
Callback &&callback,
const Direction<ResidualBlock, ValueBlock> &direction,
RowAction<ResidualBlock, ValueBlock> &row
) {
{
std::forward<Callback>(callback)(direction, row)
} -> stellar::ContributionResult;
};
public:
SpecificationBorderActionView(
const StellarStructureDirectionView &direction,
mfem::Vector &action
) noexcept
requires(Operation == detail::SpecificationBorderOperation::structure_to_border)
: m_structureDirection(std::addressof(direction)),
m_borderAction(std::addressof(action)) {
}
SpecificationBorderActionView(
const mfem::Vector &direction,
StellarStructureActionView &action,
const field::FieldBoundaryDofMap &surfaceRows
) noexcept
requires(Operation == detail::SpecificationBorderOperation::border_to_structure)
: m_borderDirection(std::addressof(direction)),
m_structureAction(std::addressof(action)),
m_surfaceRows(std::addressof(surfaceRows)) {
}
SpecificationBorderActionView(
const mfem::Vector &direction,
mfem::Vector &action
) noexcept
requires(Operation == detail::SpecificationBorderOperation::border_to_border)
: m_borderDirection(std::addressof(direction)),
m_borderAction(std::addressof(action)) {
}
template <typename ResidualTerm, typename ValueTerm, typename Callback>
requires requires {
typename std::remove_cvref_t<ResidualTerm>::residual;
typename std::remove_cvref_t<ValueTerm>::value;
} && permitsCoupling<
typename std::remove_cvref_t<ResidualTerm>::residual,
typename std::remove_cvref_t<ValueTerm>::value> &&
completesCoupling<
typename std::remove_cvref_t<ResidualTerm>::residual,
typename std::remove_cvref_t<ValueTerm>::value,
Callback>
void add(
const ResidualTerm &,
const ValueTerm &,
Callback &&callback
) const {
using Residual = typename std::remove_cvref_t<ResidualTerm>::residual;
using Value = typename std::remove_cvref_t<ValueTerm>::value;
const Direction<Residual, Value> direction = makeDirection<Residual, Value>();
RowAction<Residual, Value> row = makeRowAction<Residual, Value>();
decltype(auto) result =
std::forward<Callback>(callback)(direction, row);
row.Verify(result);
}
template <typename ValueTerm, typename Callback>
void addDensityFrom(const ValueTerm &valueTerm, Callback &&callback) const
requires requires {
typename std::remove_cvref_t<ValueTerm>::value;
} && permitsCoupling<
utils::blocks::density::mass::residual,
typename std::remove_cvref_t<ValueTerm>::value> &&
completesCoupling<
utils::blocks::density::mass::residual,
typename std::remove_cvref_t<ValueTerm>::value,
Callback> {
add(
utils::blocks::density_field.mass_term,
valueTerm,
std::forward<Callback>(callback)
);
}
template <typename ValueTerm, typename Callback>
void addSurfaceShapeFrom(const ValueTerm &valueTerm, Callback &&callback) const
requires requires {
typename std::remove_cvref_t<ValueTerm>::value;
} && permitsCoupling<
utils::blocks::surface_deformation::shape_equilibrium::residual,
typename std::remove_cvref_t<ValueTerm>::value> &&
completesCoupling<
utils::blocks::surface_deformation::shape_equilibrium::residual,
typename std::remove_cvref_t<ValueTerm>::value,
Callback> {
add(
utils::blocks::surface_deformation_field.shape_equilibrium_term,
valueTerm,
std::forward<Callback>(callback)
);
}
template <typename ValueTerm, typename Callback>
void addSpecificEnthalpyFrom(const ValueTerm &valueTerm, Callback &&callback) const
requires requires {
typename std::remove_cvref_t<ValueTerm>::value;
} && permitsCoupling<
utils::blocks::enthalpy::specific::residual,
typename std::remove_cvref_t<ValueTerm>::value> &&
completesCoupling<
utils::blocks::enthalpy::specific::residual,
typename std::remove_cvref_t<ValueTerm>::value,
Callback> {
add(
utils::blocks::enthalpy_field.specific_term,
valueTerm,
std::forward<Callback>(callback)
);
}
template <typename ValueTerm, typename Callback>
void addGravityGradientFrom(const ValueTerm &valueTerm, Callback &&callback) const
requires requires {
typename std::remove_cvref_t<ValueTerm>::value;
} && permitsCoupling<
utils::blocks::gravity::gradient::residual,
typename std::remove_cvref_t<ValueTerm>::value> &&
completesCoupling<
utils::blocks::gravity::gradient::residual,
typename std::remove_cvref_t<ValueTerm>::value,
Callback> {
add(
utils::blocks::gravity_field.gradient_term,
valueTerm,
std::forward<Callback>(callback)
);
}
template <typename ValueTerm, typename Callback>
void addGravityPotentialFrom(const ValueTerm &valueTerm, Callback &&callback) const
requires requires {
typename std::remove_cvref_t<ValueTerm>::value;
} && permitsCoupling<
utils::blocks::gravity::poisson::residual,
typename std::remove_cvref_t<ValueTerm>::value> &&
completesCoupling<
utils::blocks::gravity::poisson::residual,
typename std::remove_cvref_t<ValueTerm>::value,
Callback> {
add(
utils::blocks::gravity_field.poisson_term,
valueTerm,
std::forward<Callback>(callback)
);
}
template <models::ModelSpecification Owner = Specification, typename ValueTerm, typename Callback>
void addConstraintResidualFrom(
const ValueTerm &valueTerm,
Callback &&callback
) const
requires requires {
typename std::remove_cvref_t<ValueTerm>::value;
} && permitsCoupling<
detail::GeneratedSpecificationResidualBlock<Owner>,
typename std::remove_cvref_t<ValueTerm>::value> &&
completesCoupling<
detail::GeneratedSpecificationResidualBlock<Owner>,
typename std::remove_cvref_t<ValueTerm>::value,
Callback> {
struct GeneratedResidualTerm final {
using residual = detail::GeneratedSpecificationResidualBlock<Owner>;
};
add(GeneratedResidualTerm{}, valueTerm, std::forward<Callback>(callback));
}
private:
template <typename Residual, typename Value>
[[nodiscard]] Direction<Residual, Value> makeDirection() const {
if constexpr (Operation == detail::SpecificationBorderOperation::structure_to_border) {
return Direction<Residual, Value>{*m_structureDirection};
} else {
return Direction<Residual, Value>{*m_borderDirection};
}
}
template <typename Residual, typename Value>
[[nodiscard]] RowAction<Residual, Value> makeRowAction() const {
if constexpr (Operation == detail::SpecificationBorderOperation::border_to_structure) {
return RowAction<Residual, Value>{*m_structureAction, *m_surfaceRows};
} else {
return RowAction<Residual, Value>{*m_borderAction};
}
}
const StellarStructureDirectionView *m_structureDirection{nullptr};
const mfem::Vector *m_borderDirection{nullptr};
StellarStructureActionView *m_structureAction{nullptr};
mfem::Vector *m_borderAction{nullptr};
const field::FieldBoundaryDofMap *m_surfaceRows{nullptr};
};
template <models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem>
using SpecificationStructureToBorderActionView = SpecificationBorderActionView<
Specification,
Problem,
detail::SpecificationBorderOperation::structure_to_border>;
template <models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem>
using SpecificationBorderToStructureActionView = SpecificationBorderActionView<
Specification,
Problem,
detail::SpecificationBorderOperation::border_to_structure>;
template <models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem>
using SpecificationBorderToBorderActionView = SpecificationBorderActionView<
Specification,
Problem,
detail::SpecificationBorderOperation::border_to_border>;
/** The exact prepared equilibrium-physics object owned by one
* specification slot in a discretized problem. Physics-facing border
* actions may snapshot coefficients from this object, but do not receive
* the enclosing Problem or its backend facilities. */
template <models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem>
requires detail::SpecificationBelongsToProblem<Specification, Problem>
using PreparedSpecificationEquilibriumPhysicsT = std::remove_cvref_t<decltype(
std::declval<const std::remove_cvref_t<Problem> &>()
.GetPreparedOperator()
.template GetPreparedContribution<std::remove_cvref_t<Specification>>()
)>;
namespace detail {
template <typename ResidualBlock>
struct PhysicsFacingBorderResidualTerm final {
using residual = ResidualBlock;
};
template <typename ValueBlock>
struct PhysicsFacingBorderValueTerm final {
using value = ValueBlock;
};
template <typename Derivative>
struct PhysicsDerivativeTraits;
template <typename Equation, typename State>
struct PhysicsDerivativeTraits<
models::stellar::Derivative<Equation, State>> final {
using EquationTag = Equation;
using StateTag = State;
};
template <
models::ModelSpecification Specification,
typename Problem,
SpecificationBorderOperation Operation,
typename Derivative>
struct SpecificationBorderDerivative final {
using Traits = PhysicsDerivativeTraits<Derivative>;
using ResidualBlock = typename operators::detail::StellarDependencyBlock<
Specification,
typename Traits::EquationTag>::Type;
using ValueBlock = typename operators::detail::StellarDependencyBlock<
Specification,
typename Traits::StateTag>::Type;
using CouplingType = Coupling<ResidualBlock, ValueBlock>;
using Direction = SpecificationBorderCouplingDirectionView<
Specification,
Problem,
Operation,
ResidualBlock,
ValueBlock>;
using Row = SpecificationBorderCouplingRowAction<
Specification,
Problem,
Operation,
ResidualBlock,
ValueBlock>;
static constexpr bool participates =
utils::blocks::contains_type_v<
CouplingType,
typename SpecificationBorderContribution<
Specification>::RequiredCouplings> &&
directionParticipatesInCoupling<
ValueBlock,
CouplingType,
Operation>() &&
actionParticipatesInCoupling<
ResidualBlock,
CouplingType,
Operation>();
};
template <
typename Physics,
models::ModelSpecification Specification,
typename Problem,
SpecificationBorderOperation Operation,
typename Derivatives>
struct ExactSpecificationBorderProviderSet;
template <
typename Physics,
models::ModelSpecification Specification,
typename Problem,
SpecificationBorderOperation Operation,
typename... Derivatives>
struct ExactSpecificationBorderProviderSet<
Physics,
Specification,
Problem,
Operation,
utils::blocks::type_list<Derivatives...>> final {
private:
template <typename Derivative>
[[nodiscard]] static consteval bool ProviderIsComplete() {
using Edge = SpecificationBorderDerivative<
Specification,
Problem,
Operation,
Derivative>;
if constexpr (!Edge::participates) {
return true;
} else {
return requires(
const Physics &physics,
const typename Edge::Direction &direction,
typename Edge::Row &row
) {
{
physics.ApplyJacobianAction(
Derivative{},
direction,
row
)
} -> stellar::ContributionResult;
};
}
}
public:
static constexpr bool complete =
(ProviderIsComplete<Derivatives>() && ...);
static void Apply(
const Physics &physics,
SpecificationBorderActionView<
Specification,
Problem,
Operation> action
) requires complete {
(ApplyOne<Derivatives>(physics, action), ...);
}
private:
template <typename Derivative>
static void ApplyOne(
const Physics &physics,
const SpecificationBorderActionView<
Specification,
Problem,
Operation> &action
) {
using Edge = SpecificationBorderDerivative<
Specification,
Problem,
Operation,
Derivative>;
if constexpr (Edge::participates) {
action.add(
PhysicsFacingBorderResidualTerm<
typename Edge::ResidualBlock>{},
PhysicsFacingBorderValueTerm<
typename Edge::ValueBlock>{},
[&](const auto &direction, auto &row) -> decltype(auto) {
return physics.ApplyJacobianAction(
Derivative{},
direction,
row
);
}
);
}
}
};
template <
typename Physics,
models::ModelSpecification Specification,
equilibrium::DiscretizedStellarEquilibriumProblem Problem>
struct ExactSpecificationBorderProviders final {
using Model = typename Problem::ModelType;
using Derivatives = typename operators::StellarEquilibriumContributionTopology<
Specification,
Model>::Derivatives;
using StructureToBorder = ExactSpecificationBorderProviderSet<
Physics,
Specification,
Problem,
SpecificationBorderOperation::structure_to_border,
Derivatives>;
using BorderToStructure = ExactSpecificationBorderProviderSet<
Physics,
Specification,
Problem,
SpecificationBorderOperation::border_to_structure,
Derivatives>;
using BorderToBorder = ExactSpecificationBorderProviderSet<
Physics,
Specification,
Problem,
SpecificationBorderOperation::border_to_border,
Derivatives>;
static constexpr bool complete =
StructureToBorder::complete &&
BorderToStructure::complete &&
BorderToBorder::complete;
};
} // namespace detail
/*
* Public protocol check for one prepared numerical constraint action.
* The topology itself is inferred from ModelDefinition; this concept makes
* the remaining physics implementation fail at the contribution boundary
* instead of deep inside the assembled preconditioner.
*/
template <typename Candidate, typename Problem>
concept PreparedSpecificationBorderActionFor =
std::constructible_from<Candidate, const Problem &> &&
std::move_constructible<Candidate> &&
requires(
const Candidate &action,
const StellarStructureDirectionView &structureDirection,
const mfem::Vector &borderDirection,
StellarStructureActionView structureAction,
mfem::Vector &borderAction
) {
{ Candidate::registered } -> std::convertible_to<bool>;
requires Candidate::registered;
action.ApplyStructureToBorder(structureDirection, borderAction);
action.ApplyBorderToStructure(borderDirection, structureAction);
action.ApplyBorderToBorder(borderDirection, borderAction);
};
/*
* Physics-facing opt-in for a new generated constraint. The specification
* may name this wrapper as
*
* using SpecificationBorderPhysics =
* preconditioning::SpecificationBorderPhysics<MyPreparedAction>;
*
* where MyPreparedAction implements one overload for each border-incident
* derivative inferred from Reads/Changes, for example
*
* auto ApplyJacobianAction(
* stellar::Derivative<equation::OwnConstraint,
* state::SpecificEnthalpy>,
* const auto &direction,
* auto &row) const {
* return row.add(coefficient * direction.specificEnthalpy()(0));
* }
*
* The adapter enumerates the exact inferred set. Missing overloads fail at
* compile time; identically absent terms return stellar::zeroDerivative.
* Its constructor receives only the exact specification's prepared
* equilibrium physics, never the full Problem. The wrapper owns the
* generic construction plumbing; extension authors neither specialize a
* detail:: class nor reproduce pack traversal.
* LocalSpecificationBorderPhysics below removes even the class-template
* spelling for the common concrete-class case.
*/
template <typename Candidate, typename Specification, typename Problem>
concept CompleteSpecificationBorderPhysicsProvider =
models::ModelSpecification<std::remove_cvref_t<Specification>> &&
detail::SpecificationBelongsToProblem<Specification, Problem> &&
equilibrium::DiscretizedStellarEquilibriumProblem<
std::remove_cvref_t<Problem>> &&
detail::ExactSpecificationBorderProviders<
std::remove_cvref_t<Candidate>,
std::remove_cvref_t<Specification>,
std::remove_cvref_t<Problem>>::complete;
template <typename Candidate, typename Specification, typename Problem>
concept PreparedSpecificationBorderPhysicsActionFor =
CompleteSpecificationBorderPhysicsProvider<
Candidate,
Specification,
Problem> &&
std::constructible_from<
Candidate,
const PreparedSpecificationEquilibriumPhysicsT<
std::remove_cvref_t<Specification>,
std::remove_cvref_t<Problem>> &> &&
std::move_constructible<Candidate>;
template <template <typename> typename PreparedAction>
struct SpecificationBorderPhysics final {
static constexpr bool registered = true;
template <models::ModelSpecification Specification, typename Problem>
requires PreparedSpecificationBorderPhysicsActionFor<
PreparedAction<std::remove_cvref_t<Problem>>,
Specification,
std::remove_cvref_t<Problem>>
class Prepared final {
public:
using Physics = PreparedAction<std::remove_cvref_t<Problem>>;
using EquilibriumPhysics = PreparedSpecificationEquilibriumPhysicsT<
Specification,
std::remove_cvref_t<Problem>>;
using Providers = detail::ExactSpecificationBorderProviders<
Physics,
Specification,
std::remove_cvref_t<Problem>>;
static constexpr bool registered = true;
explicit Prepared(const std::remove_cvref_t<Problem> &problem)
requires std::constructible_from<Physics, const EquilibriumPhysics &>
: m_surfaceRows(std::addressof(problem.GetPressureSurfaceRows())),
m_physics(
problem.GetPreparedOperator()
.template GetPreparedContribution<Specification>()
) {
}
void ApplyStructureToBorder(
const StellarStructureDirectionView &structureDirection,
mfem::Vector &borderAction
) const {
SpecificationStructureToBorderActionView<Specification, Problem> action{
structureDirection,
borderAction
};
Providers::StructureToBorder::Apply(m_physics, action);
}
void ApplyBorderToStructure(
const mfem::Vector &borderDirection,
StellarStructureActionView structureAction
) const {
SpecificationBorderToStructureActionView<Specification, Problem> action{
borderDirection,
structureAction,
*m_surfaceRows
};
Providers::BorderToStructure::Apply(m_physics, action);
}
void ApplyBorderToBorder(
const mfem::Vector &borderDirection,
mfem::Vector &borderAction
) const {
SpecificationBorderToBorderActionView<Specification, Problem> action{
borderDirection,
borderAction
};
Providers::BorderToBorder::Apply(m_physics, action);
}
[[nodiscard]] const Physics &physics() const noexcept {
return m_physics;
}
private:
const field::FieldBoundaryDofMap *m_surfaceRows;
Physics m_physics;
};
template <models::ModelSpecification Specification, typename Problem>
requires PreparedSpecificationBorderPhysicsActionFor<
PreparedAction<std::remove_cvref_t<Problem>>,
Specification,
std::remove_cvref_t<Problem>>
[[nodiscard]] static auto prepare(const Problem &problem) {
return Prepared<Specification, std::remove_cvref_t<Problem>>{problem};
}
};
namespace detail {
template <typename LocalPhysics>
struct BindLocalSpecificationBorderPhysics final {
template <typename>
using Physics = LocalPhysics;
};
} // namespace detail
/** Convenience spelling for a concrete, specification-local border action.
* This aliases the ordinary SpecificationBorderPhysics adapter and contains
* no independent preparation or dispatch logic. */
template <typename LocalPhysics>
using LocalSpecificationBorderPhysics = SpecificationBorderPhysics<
detail::BindLocalSpecificationBorderPhysics<LocalPhysics>::template Physics>;
namespace detail {
/* Default astronomy-facing border action. The equilibrium physics
* object already implements one exact AddJacobianAction overload for
* every compiler-inferred derivative. Border preconditioning is only
* a projection of that same Jacobian, so forwarding those providers
* removes a duplicate set of formulas from every ordinary scalar
* constraint. ExactSpecificationBorderProviders still enumerates and
* audits the incident subset independently; an implementation whose
* provider signatures cannot serve the restricted border views is
* therefore rejected at compile time. */
template <
models::ModelSpecification Specification,
equilibrium::DiscretizedStellarEquilibriumProblem Problem>
class EquilibriumJacobianBorderAction final {
public:
using EquilibriumPhysics =
PreparedSpecificationEquilibriumPhysicsT<
Specification,
Problem>;
explicit EquilibriumJacobianBorderAction(
const EquilibriumPhysics &physics
) noexcept
: m_physics(std::addressof(physics)) {
}
template <typename Derivative, typename Direction, typename Row>
requires requires(
const EquilibriumPhysics &physics,
const Direction &direction,
Row &row
) {
{
physics.AddJacobianAction(
Derivative{},
direction,
row
)
} -> stellar::ContributionResult;
}
[[nodiscard]] decltype(auto) ApplyJacobianAction(
Derivative,
const Direction &direction,
Row &row
) const {
return m_physics->AddJacobianAction(
Derivative{},
direction,
row
);
}
private:
const EquilibriumPhysics *m_physics;
};
template <models::ModelSpecification Specification>
struct BindEquilibriumJacobianBorderAction final {
template <typename Problem>
using Physics = EquilibriumJacobianBorderAction<
Specification,
std::remove_cvref_t<Problem>>;
};
template <models::ModelSpecification Specification>
using EquilibriumJacobianSpecificationBorderPhysics =
SpecificationBorderPhysics<
BindEquilibriumJacobianBorderAction<
Specification>::template Physics>;
template <models::ModelSpecification Specification>
struct BuiltinSpecificationBorderPhysics;
template <typename Specification, typename = void>
struct DefaultSpecificationBorderPhysics {
using Type = BuiltinSpecificationBorderPhysics<Specification>;
};
template <models::ModelSpecification Specification>
struct DefaultSpecificationBorderPhysics<
Specification,
std::void_t<typename Specification::EquilibriumPhysics>> {
using Type =
EquilibriumJacobianSpecificationBorderPhysics<Specification>;
};
} // namespace detail
template <typename Candidate>
struct IsDeclaredCouplingSafeSpecificationBorderPhysics : std::false_type { };
template <template <typename> typename PreparedAction>
struct IsDeclaredCouplingSafeSpecificationBorderPhysics<
SpecificationBorderPhysics<PreparedAction>> : std::true_type { };
/* Trusted library backends adapt legacy kernels to the same aggregate
* protocol internally. Third-party specifications cannot name this detail
* provider and must use SpecificationBorderPhysics above. */
template <models::ModelSpecification Specification>
struct IsDeclaredCouplingSafeSpecificationBorderPhysics<
detail::BuiltinSpecificationBorderPhysics<Specification>> : std::true_type { };
template <typename Candidate>
concept DeclaredCouplingSafeSpecificationBorderPhysics =
IsDeclaredCouplingSafeSpecificationBorderPhysics<
std::remove_cvref_t<Candidate>>::value;
template <typename Candidate, typename Specification, typename Problem>
concept SpecificationBorderPhysicsFor =
DeclaredCouplingSafeSpecificationBorderPhysics<Candidate> &&
detail::SpecificationBelongsToProblem<std::remove_cvref_t<Specification>, std::remove_cvref_t<Problem>> &&
requires(const std::remove_cvref_t<Problem> &problem) {
typename Candidate::template Prepared<
std::remove_cvref_t<Specification>,
std::remove_cvref_t<Problem>>;
{ Candidate::registered } -> std::convertible_to<bool>;
requires Candidate::registered;
{
Candidate::template prepare<std::remove_cvref_t<Specification>>(problem)
} -> std::same_as<typename Candidate::template Prepared<
std::remove_cvref_t<Specification>,
std::remove_cvref_t<Problem>>>;
requires PreparedSpecificationBorderActionFor<
typename Candidate::template Prepared<
std::remove_cvref_t<Specification>,
std::remove_cvref_t<Problem>>,
std::remove_cvref_t<Problem>>;
};
namespace detail {
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
[[nodiscard]] const auto &specificationBorderPhysicalOperator(const Problem &problem) {
return problem.GetPhysicalOperator();
}
template <typename Candidate>
concept FixedMassBorderPhysicalCore = requires(
const Candidate &physical,
const mfem::Vector &first,
const mfem::Vector &second,
mfem::Vector &action,
double scalar
) {
{ physical.GetDomainDeformation().volumeDisplacementSize() } -> std::same_as<int>;
{ physical.GetBarotropicClosureOperator().GetEnthalpySize() } -> std::same_as<int>;
physical.GetDomainDeformation().applyJacobian(
physical.GetSurfaceDeformationParameters(), first, action
);
physical.GetMassNormalizationOperator().ApplyCompleteJacobianAction(first, second, action);
physical.GetHydrostaticOperator().ApplyBernoulliConstantJacobianAction(scalar, action);
physical.GetSurfaceConstraintOperator().ApplyJacobianRows(first, action);
};
template <typename Candidate>
concept AngularMomentumBorderPhysicalCore = FixedMassBorderPhysicalCore<Candidate> && requires(
const Candidate &physical,
const mfem::Vector &first,
const mfem::Vector &second,
mfem::Vector &action,
double scalar
) {
{ physical.GetDomainDeformation().parameterCount() } -> std::same_as<int>;
physical.GetHydrostaticOperator().ApplyRotationAmplitudeJacobianAction(scalar, action);
physical.GetDisplacementOperator().GetRotationalOperator().BuildResidual(action);
physical.GetDomainDeformation().applyJacobianTranspose(
physical.GetSurfaceDeformationParameters(), first, action
);
};
template <typename Candidate>
concept CentralDensityBorderPhysicalCore = requires(const Candidate &physical) {
{ physical.GetBarotropicClosureOperator().GetEnthalpySize() } -> std::same_as<int>;
};
template <models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem>
class PreparedSpecificationBorderAction {
public:
static constexpr bool registered = !specificationGeneratesBorder<Specification>;
explicit PreparedSpecificationBorderAction(const Problem &) noexcept {
}
void ApplyStructureToBorder(
const StellarStructureDirectionView &,
mfem::Vector &
) const noexcept {
}
void ApplyBorderToStructure(
const mfem::Vector &,
StellarStructureActionView
) const noexcept {
}
void ApplyBorderToBorder(
const mfem::Vector &,
mfem::Vector &
) const noexcept {
}
};
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
class PreparedSpecificationBorderAction<models::FixedTotalMass, Problem> {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using Model = typename ProblemType::ModelType;
using PhysicalCore = typename ProblemType::PhysicalCoreType;
public:
static constexpr bool registered = FixedMassBorderPhysicalCore<PhysicalCore>;
explicit PreparedSpecificationBorderAction(const Problem &problem) requires registered
: m_physical(std::addressof(specificationBorderPhysicalOperator(problem))),
m_volumeDisplacement(m_physical->GetDomainDeformation().volumeDisplacementSize()),
m_enthalpyWorkspace(m_physical->GetBarotropicClosureOperator().GetEnthalpySize()),
m_zeroEnthalpy(m_physical->GetBarotropicClosureOperator().GetEnthalpySize()) {
m_zeroEnthalpy = 0.0;
}
void ApplyStructureToBorder(
const StellarStructureDirectionView &structure,
mfem::Vector &borderAction
) const {
constexpr int residualOffset =
static_cast<int>(specificationBorderResidualOffset<models::FixedTotalMass, Model>);
mfem::Vector massAction(borderAction, residualOffset, 1);
m_physical->GetDomainDeformation().applyJacobian(
m_physical->GetSurfaceDeformationParameters(), structure.surface, m_volumeDisplacement
);
m_physical->GetMassNormalizationOperator().ApplyCompleteJacobianAction(
structure.density, m_volumeDisplacement, massAction
);
massAction.SyncAliasMemory(borderAction);
}
void ApplyBorderToStructure(
const mfem::Vector &borderDirection,
StellarStructureActionView structureAction
) const {
constexpr int valueOffset =
static_cast<int>(specificationBorderValueOffset<models::FixedTotalMass, Model>);
m_physical->GetHydrostaticOperator().ApplyBernoulliConstantJacobianAction(
borderDirection(valueOffset), m_enthalpyWorkspace
);
m_physical->GetSurfaceConstraintOperator().ApplyJacobianRows(m_zeroEnthalpy, m_enthalpyWorkspace);
structureAction.enthalpy += m_enthalpyWorkspace;
}
void ApplyBorderToBorder(
const mfem::Vector &,
mfem::Vector &
) const noexcept {
}
private:
const PhysicalCore *m_physical;
mutable mfem::Vector m_volumeDisplacement;
mutable mfem::Vector m_enthalpyWorkspace;
mfem::Vector m_zeroEnthalpy;
};
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
class PreparedSpecificationBorderAction<models::FixedAngularMomentum, Problem> {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using Model = typename ProblemType::ModelType;
using PhysicalCore = typename ProblemType::PhysicalCoreType;
public:
static constexpr bool registered = AngularMomentumBorderPhysicalCore<PhysicalCore>;
explicit PreparedSpecificationBorderAction(const Problem &problem) requires registered
: m_physical(std::addressof(specificationBorderPhysicalOperator(problem))),
m_constraint(std::addressof(
problem.GetPreparedOperator()
.template GetPreparedContribution<models::FixedAngularMomentum>()
.constraint()
)),
m_volumeDisplacement(m_physical->GetDomainDeformation().volumeDisplacementSize()),
m_rotationalAction(m_physical->GetDomainDeformation().volumeDisplacementSize()),
m_surfaceWorkspace(m_physical->GetDomainDeformation().parameterCount()),
m_enthalpyWorkspace(m_physical->GetBarotropicClosureOperator().GetEnthalpySize()),
m_zeroEnthalpy(m_enthalpyWorkspace.Size()),
m_scalarWorkspace(1) {
m_zeroEnthalpy = 0.0;
}
void ApplyStructureToBorder(
const StellarStructureDirectionView &structure,
mfem::Vector &borderAction
) const {
constexpr int residualOffset = static_cast<int>(
specificationBorderResidualOffset<models::FixedAngularMomentum, Model>
);
m_physical->GetDomainDeformation().applyJacobian(
m_physical->GetSurfaceDeformationParameters(),
structure.surface,
m_volumeDisplacement
);
m_constraint->ApplyCompleteJacobianAction(
structure.density,
m_volumeDisplacement,
0.0,
m_scalarWorkspace
);
borderAction(residualOffset) += m_scalarWorkspace(0);
}
void ApplyBorderToStructure(
const mfem::Vector &borderDirection,
StellarStructureActionView structureAction
) const {
constexpr int valueOffset = static_cast<int>(
specificationBorderValueOffset<models::FixedAngularMomentum, Model>
);
const double angularVelocityVariation = borderDirection(valueOffset);
const double angularVelocity = m_constraint->GetAngularVelocity();
if (angularVelocity == 0.0 || angularVelocityVariation == 0.0) {
return;
}
const double fractionalVariation = angularVelocityVariation / angularVelocity;
m_physical->GetHydrostaticOperator().ApplyRotationAmplitudeJacobianAction(
fractionalVariation,
m_enthalpyWorkspace
);
m_physical->GetSurfaceConstraintOperator().ApplyJacobianRows(
m_zeroEnthalpy,
m_enthalpyWorkspace
);
structureAction.enthalpy += m_enthalpyWorkspace;
m_physical->GetDisplacementOperator().GetRotationalOperator().BuildResidual(m_rotationalAction);
m_rotationalAction *= 2.0 * fractionalVariation;
m_physical->GetDomainDeformation().applyJacobianTranspose(
m_physical->GetSurfaceDeformationParameters(),
m_rotationalAction,
m_surfaceWorkspace
);
structureAction.surface += m_surfaceWorkspace;
}
void ApplyBorderToBorder(
const mfem::Vector &borderDirection,
mfem::Vector &borderAction
) const {
constexpr int valueOffset = static_cast<int>(
specificationBorderValueOffset<models::FixedAngularMomentum, Model>
);
constexpr int residualOffset = static_cast<int>(
specificationBorderResidualOffset<models::FixedAngularMomentum, Model>
);
m_constraint->ApplyAngularVelocityJacobianAction(
borderDirection(valueOffset),
m_scalarWorkspace
);
borderAction(residualOffset) += m_scalarWorkspace(0);
}
private:
const PhysicalCore *m_physical;
const operators::PreparedAngularMomentumOperator *m_constraint;
mutable mfem::Vector m_volumeDisplacement;
mutable mfem::Vector m_rotationalAction;
mutable mfem::Vector m_surfaceWorkspace;
mutable mfem::Vector m_enthalpyWorkspace;
mfem::Vector m_zeroEnthalpy;
mutable mfem::Vector m_scalarWorkspace;
};
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
class PreparedSpecificationBorderAction<models::FixedCentralDensity, Problem> {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using Model = typename ProblemType::ModelType;
using PhysicalCore = typename ProblemType::PhysicalCoreType;
public:
static constexpr bool registered = CentralDensityBorderPhysicalCore<PhysicalCore>;
explicit PreparedSpecificationBorderAction(const Problem &problem) requires registered
: m_constraint(std::addressof(
problem.GetPreparedOperator()
.template GetPreparedContribution<models::FixedCentralDensity>()
.constraint()
)),
m_zeroEnthalpy(
specificationBorderPhysicalOperator(problem).GetBarotropicClosureOperator().GetEnthalpySize()
),
m_enthalpyWorkspace(m_zeroEnthalpy.Size()),
m_phaseWorkspace(1) {
m_zeroEnthalpy = 0.0;
}
void ApplyStructureToBorder(
const StellarStructureDirectionView &structure,
mfem::Vector &borderAction
) const {
constexpr int residualOffset =
static_cast<int>(specificationBorderResidualOffset<models::FixedCentralDensity, Model>);
mfem::Vector phaseAction(borderAction, residualOffset, 1);
m_enthalpyWorkspace = 0.0;
m_constraint->ApplyJacobian(
{.enthalpyVariation = structure.enthalpy, .borderVariation = 0.0},
{.enthalpyAction = m_enthalpyWorkspace, .phaseAction = phaseAction}
);
phaseAction.SyncAliasMemory(borderAction);
}
void ApplyBorderToStructure(
const mfem::Vector &borderDirection,
StellarStructureActionView structureAction
) const {
constexpr int valueOffset =
static_cast<int>(specificationBorderValueOffset<models::FixedCentralDensity, Model>);
m_enthalpyWorkspace = 0.0;
m_phaseWorkspace = 0.0;
m_constraint->ApplyJacobian(
{.enthalpyVariation = m_zeroEnthalpy, .borderVariation = borderDirection(valueOffset)},
{.enthalpyAction = m_enthalpyWorkspace, .phaseAction = m_phaseWorkspace}
);
structureAction.enthalpy += m_enthalpyWorkspace;
}
void ApplyBorderToBorder(
const mfem::Vector &,
mfem::Vector &
) const noexcept {
}
private:
const operators::PreparedCentralDensityConstraint *m_constraint;
mfem::Vector m_zeroEnthalpy;
mutable mfem::Vector m_enthalpyWorkspace;
mutable mfem::Vector m_phaseWorkspace;
};
template <models::ModelSpecification Specification>
struct BuiltinSpecificationBorderPhysics final {
static constexpr bool registered = true;
template <models::ModelSpecification RequestedSpecification, typename Problem>
requires std::same_as<std::remove_cvref_t<RequestedSpecification>, Specification>
using Prepared = PreparedSpecificationBorderAction<Specification, std::remove_cvref_t<Problem>>;
template <models::ModelSpecification RequestedSpecification, typename Problem>
requires std::same_as<std::remove_cvref_t<RequestedSpecification>, Specification>
[[nodiscard]] static Prepared<RequestedSpecification, Problem> prepare(const Problem &problem) {
return Prepared<RequestedSpecification, Problem>{problem};
}
};
template <typename Specification, typename = void> struct SelectSpecificationBorderPhysics {
using Type = typename DefaultSpecificationBorderPhysics<
Specification>::Type;
};
template <typename Specification>
struct SelectSpecificationBorderPhysics<
Specification,
std::void_t<typename Specification::SpecificationBorderPhysics>> {
using Type = typename Specification::SpecificationBorderPhysics;
};
/* A nested, physics-facing package and a trusted backend adapter are
* two independent registrations for the same contribution. Never
* choose one by precedence: doing so would make behavior depend on an
* implementation-detail ordering and could silently shadow a backend
* added later. Keep the audit detection-safe so public capability
* queries remain ordinary constraint failure for unrelated types. */
template <typename Specification, typename Problem, typename = void>
struct SpecificationBorderPhysicsSelectionAudit {
static constexpr bool ambiguous = false;
static constexpr bool available = false;
};
template <typename Specification, typename Problem>
requires models::ModelSpecification<std::remove_cvref_t<Specification>> &&
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
SpecificationBelongsToProblem<
std::remove_cvref_t<Specification>,
std::remove_cvref_t<Problem>>
struct SpecificationBorderPhysicsSelectionAudit<Specification, Problem, void> {
private:
using SpecificationType = std::remove_cvref_t<Specification>;
using ProblemType = std::remove_cvref_t<Problem>;
using Selected = typename SelectSpecificationBorderPhysics<SpecificationType>::Type;
using Builtin = BuiltinSpecificationBorderPhysics<SpecificationType>;
static constexpr bool hasNestedProvider = requires {
typename SpecificationType::SpecificationBorderPhysics;
};
static constexpr bool hasAvailableBuiltin =
SpecificationBorderPhysicsFor<Builtin, SpecificationType, ProblemType>;
public:
static constexpr bool ambiguous = hasNestedProvider && hasAvailableBuiltin;
static constexpr bool available =
!ambiguous &&
SpecificationBorderPhysicsFor<Selected, SpecificationType, ProblemType>;
};
template <typename Problem>
class UnavailablePreparedSpecificationBorderAction final {
public:
static constexpr bool registered = false;
explicit UnavailablePreparedSpecificationBorderAction(const Problem &) noexcept {
}
void ApplyStructureToBorder(const StellarStructureDirectionView &, mfem::Vector &) const noexcept {
}
void ApplyBorderToStructure(const mfem::Vector &, StellarStructureActionView) const noexcept {
}
void ApplyBorderToBorder(const mfem::Vector &, mfem::Vector &) const noexcept {
}
};
template <
models::ModelSpecification Specification,
typename Problem,
bool = SpecificationBorderPhysicsSelectionAudit<
Specification,
std::remove_cvref_t<Problem>>::available>
struct SafePreparedSpecificationBorderPhysicsAction {
using Type = UnavailablePreparedSpecificationBorderAction<std::remove_cvref_t<Problem>>;
};
template <models::ModelSpecification Specification, typename Problem>
struct SafePreparedSpecificationBorderPhysicsAction<Specification, Problem, true> {
private:
using Physics = typename SelectSpecificationBorderPhysics<Specification>::Type;
public:
using Type = typename Physics::template Prepared<Specification, std::remove_cvref_t<Problem>>;
};
template <models::ModelSpecification Specification, typename Problem>
using PreparedSpecificationBorderPhysicsAction =
typename SafePreparedSpecificationBorderPhysicsAction<Specification, Problem>::Type;
template <models::ModelSpecification Specification, typename Problem>
[[nodiscard]] auto prepareSpecificationBorderPhysics(const Problem &problem) {
using Physics = typename SelectSpecificationBorderPhysics<Specification>::Type;
if constexpr (SpecificationBorderPhysicsSelectionAudit<
Specification,
std::remove_cvref_t<Problem>>::available) {
return Physics::template prepare<Specification>(problem);
} else {
return UnavailablePreparedSpecificationBorderAction<std::remove_cvref_t<Problem>>{problem};
}
}
template <typename SpecificationSet, equilibrium::DiscretizedStellarEquilibriumProblem Problem>
class PreparedSpecificationBorderActions;
template <
models::ModelSpecification... Specifications,
equilibrium::DiscretizedStellarEquilibriumProblem Problem>
class PreparedSpecificationBorderActions<models::detail::SpecificationSetStorage<Specifications...>, Problem> {
public:
static constexpr bool complete =
(SpecificationBorderPhysicsSelectionAudit<
Specifications,
Problem>::available && ...);
explicit PreparedSpecificationBorderActions(const Problem &problem)
: m_actions(
prepareSpecificationBorderPhysics<Specifications>(problem)...
) {
}
void ApplyStructureToBorder(
const StellarStructureDirectionView &structure,
mfem::Vector &borderAction
) const {
std::apply(
[&](const auto &...actions) { (actions.ApplyStructureToBorder(structure, borderAction), ...); },
m_actions
);
}
void ApplyBorderToStructure(
const mfem::Vector &borderDirection,
StellarStructureActionView structureAction
) const {
std::apply(
[&](const auto &...actions) {
(actions.ApplyBorderToStructure(borderDirection, structureAction), ...);
},
m_actions
);
}
void ApplyBorderToBorder(
const mfem::Vector &borderDirection,
mfem::Vector &borderAction
) const {
std::apply(
[&](const auto &...actions) { (actions.ApplyBorderToBorder(borderDirection, borderAction), ...); },
m_actions
);
}
private:
std::tuple<PreparedSpecificationBorderPhysicsAction<Specifications, Problem>...> m_actions;
};
} // namespace detail
template <typename Specification, typename Problem>
concept SpecificationBorderPhysicsAvailableFor =
models::ModelSpecification<std::remove_cvref_t<Specification>> &&
detail::SpecificationBorderPhysicsSelectionAudit<
std::remove_cvref_t<Specification>,
std::remove_cvref_t<Problem>>::available;
template <models::ModelSpecification Specification, typename Problem>
requires SpecificationBorderPhysicsAvailableFor<Specification, Problem>
using PreparedSpecificationBorderPhysicsT =
detail::PreparedSpecificationBorderPhysicsAction<
std::remove_cvref_t<Specification>,
std::remove_cvref_t<Problem>>;
template <typename Problem>
inline constexpr bool completeSpecificationBorderActionsFor = false;
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
inline constexpr bool completeSpecificationBorderActionsFor<Problem> =
detail::PreparedSpecificationBorderActions<
typename std::remove_cvref_t<Problem>::ModelType::SpecificationTypes,
std::remove_cvref_t<Problem>>::complete;
template <typename Problem>
concept CompleteSpecificationBorderActionsFor =
completeSpecificationBorderActionsFor<std::remove_cvref_t<Problem>>;
/*
* Complete support for the library's default coupled stellar
* preconditioner. A future solver can use this one capability instead of
* discovering an unsupported physical core or missing constraint action in
* an auto-return function body.
*/
template <typename Problem>
concept DefaultStellarPreconditionerAvailableFor =
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
CompleteSpecificationBorderActionsFor<std::remove_cvref_t<Problem>> &&
StellarStructurePreconditionerProblem<std::remove_cvref_t<Problem>>;
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
requires CompleteSpecificationBorderActionsFor<Problem>
class SpecificationBorderJacobianOperator final : public mfem::Operator {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using Model = typename ProblemType::ModelType;
using CompiledBorder = CompiledSpecificationBorderFor<Model>;
using Actions = detail::PreparedSpecificationBorderActions<typename Model::SpecificationTypes, ProblemType>;
public:
explicit SpecificationBorderJacobianOperator(const ProblemType &problem)
: mfem::Operator(StructureSizeOf(problem) + BorderSizeOf(problem)),
m_problem(std::addressof(RequirePreparedProblem(problem))),
m_structureOffsets(6),
m_actions(std::in_place, *m_problem),
m_snapshot(StellarEquilibriumProblemTraits<ProblemType>::Snapshot(*m_problem)) {
using Form = typename ProblemType::FormType;
constexpr auto densityValue =
utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto surfaceValue = utils::blocks::get_value_block<Form>(
utils::blocks::surface_deformation_field.parameters_term
);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto surfaceResidual = utils::blocks::get_residual_block<Form>(
utils::blocks::surface_deformation_field.shape_equilibrium_term
);
constexpr auto enthalpyResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto gravityGradientResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.poisson_term);
const auto &layout = problem.GetManifest().layout();
m_structureOffsets[0] = 0;
m_structureOffsets[1] = layout.size(densityValue);
m_structureOffsets[2] = m_structureOffsets[1] + layout.size(surfaceValue);
m_structureOffsets[3] = m_structureOffsets[2] + layout.size(enthalpyValue);
m_structureOffsets[4] = m_structureOffsets[3] + layout.size(gravityGradientValue);
m_structureOffsets[5] = StructureSizeOf(problem);
if (layout.size(densityValue) != layout.size(densityResidual) ||
layout.size(surfaceValue) != layout.size(surfaceResidual) ||
layout.size(enthalpyValue) != layout.size(enthalpyResidual) ||
layout.size(gravityGradientValue) != layout.size(gravityGradientResidual) ||
layout.size(gravityPotentialValue) != layout.size(gravityPotentialResidual)) {
throw std::logic_error(
"The compiled stellar structure has mismatched value and residual block sizes."
);
}
if (StructureSize() + BorderSize() != problem.StateSize() ||
StructureSize() + BorderSize() != problem.EquationSize()) {
throw std::logic_error(
"The compiled specification border does not complete the stellar-equilibrium problem."
);
}
}
SpecificationBorderJacobianOperator(ProblemType &&) = delete;
SpecificationBorderJacobianOperator(const ProblemType &&) = delete;
void Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const override {
VerifyCurrent();
VerifyCombined(direction, action);
action = 0.0;
const mfem::Vector structureDirection(const_cast<mfem::real_t *>(direction.GetData()), StructureSize());
const mfem::Vector borderDirection(
const_cast<mfem::real_t *>(direction.GetData()) + StructureSize(), BorderSize()
);
mfem::Vector structureAction(action, 0, StructureSize());
mfem::Vector borderAction(action, StructureSize(), BorderSize());
ApplyBorderToStructure(borderDirection, structureAction);
ApplyStructureToBorder(structureDirection, borderAction);
mfem::Vector borderDiagonalAction(BorderSize());
ApplyBorderToBorder(borderDirection, borderDiagonalAction);
borderAction += borderDiagonalAction;
structureAction.SyncAliasMemory(action);
borderAction.SyncAliasMemory(action);
}
void ApplyStructureToBorder(
const mfem::Vector &structureDirection,
mfem::Vector &borderAction
) const {
VerifyCurrent();
VerifyStructure(structureDirection, "direction");
VerifyBorder(borderAction, "action");
borderAction = 0.0;
const auto directionView = StructureDirection(structureDirection);
m_actions->ApplyStructureToBorder(directionView, borderAction);
}
void ApplyBorderToStructure(
const mfem::Vector &borderDirection,
mfem::Vector &structureAction
) const {
VerifyCurrent();
VerifyBorder(borderDirection, "direction");
VerifyStructure(structureAction, "action");
structureAction = 0.0;
auto densityAction = MutableStructureBlock(structureAction, 0);
auto surfaceAction = MutableStructureBlock(structureAction, 1);
auto enthalpyAction = MutableStructureBlock(structureAction, 2);
auto gravityGradientAction = MutableStructureBlock(structureAction, 3);
auto gravityPotentialAction = MutableStructureBlock(structureAction, 4);
m_actions->ApplyBorderToStructure(
borderDirection, {.density = densityAction,
.surface = surfaceAction,
.enthalpy = enthalpyAction,
.gravityGradient = gravityGradientAction,
.gravityPotential = gravityPotentialAction}
);
densityAction.SyncAliasMemory(structureAction);
surfaceAction.SyncAliasMemory(structureAction);
enthalpyAction.SyncAliasMemory(structureAction);
gravityGradientAction.SyncAliasMemory(structureAction);
gravityPotentialAction.SyncAliasMemory(structureAction);
}
void ApplyBorderToBorder(
const mfem::Vector &borderDirection,
mfem::Vector &borderAction
) const {
VerifyCurrent();
VerifyBorder(borderDirection, "direction");
VerifyBorder(borderAction, "action");
borderAction = 0.0;
m_actions->ApplyBorderToBorder(borderDirection, borderAction);
}
[[nodiscard]] bool Refresh() {
if (!m_problem->IsPrepared()) {
throw std::logic_error(
"Specification-border actions cannot refresh from an unprepared equilibrium problem."
);
}
const auto current = StellarEquilibriumProblemTraits<ProblemType>::Snapshot(*m_problem);
if (current == m_snapshot) {
return false;
}
m_actions.emplace(*m_problem);
m_snapshot = current;
return true;
}
[[nodiscard]] bool IsCurrent() const {
return m_problem->IsPrepared() && m_actions.has_value() &&
StellarEquilibriumProblemTraits<ProblemType>::Snapshot(*m_problem) == m_snapshot;
}
[[nodiscard]] int StructureSize() const noexcept {
return m_structureOffsets.Last();
}
[[nodiscard]] static constexpr int BorderSize() noexcept {
return static_cast<int>(CompiledBorder::valueArity);
}
[[nodiscard]] const mfem::Array<int> &GetStructureOffsets() const noexcept {
return m_structureOffsets;
}
private:
[[nodiscard]] static const ProblemType &RequirePreparedProblem(const ProblemType &problem) {
if (!problem.IsPrepared()) {
throw std::invalid_argument(
"Specification-border actions require a prepared equilibrium linearization."
);
}
return problem;
}
void VerifyCurrent() const {
if (!IsCurrent()) {
throw std::logic_error(
"The specification-border Jacobian is stale; refresh it before application."
);
}
}
[[nodiscard]] static int StructureSizeOf(const ProblemType &problem) {
using Form = typename ProblemType::FormType;
constexpr auto densityValue =
utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto surfaceValue = utils::blocks::get_value_block<Form>(
utils::blocks::surface_deformation_field.parameters_term
);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.poisson_term);
const auto &layout = problem.GetManifest().layout();
return layout.size(densityValue) + layout.size(surfaceValue) + layout.size(enthalpyValue) +
layout.size(gravityGradientValue) + layout.size(gravityPotentialValue);
}
[[nodiscard]] static constexpr int BorderSizeOf(const ProblemType &) noexcept {
return BorderSize();
}
[[nodiscard]] mfem::Vector ConstStructureBlock(
const mfem::Vector &vector,
const int block
) const {
return mfem::Vector(
const_cast<mfem::real_t *>(vector.GetData()) + m_structureOffsets[block],
m_structureOffsets[block + 1] - m_structureOffsets[block]
);
}
[[nodiscard]] mfem::Vector MutableStructureBlock(
mfem::Vector &vector,
const int block
) const {
return mfem::Vector(
vector, m_structureOffsets[block], m_structureOffsets[block + 1] - m_structureOffsets[block]
);
}
[[nodiscard]] StellarStructureDirectionView StructureDirection(const mfem::Vector &direction) const {
m_directionDensity = ConstStructureBlock(direction, 0);
m_directionSurface = ConstStructureBlock(direction, 1);
m_directionEnthalpy = ConstStructureBlock(direction, 2);
m_directionGravityGradient = ConstStructureBlock(direction, 3);
m_directionGravityPotential = ConstStructureBlock(direction, 4);
return {
.density = m_directionDensity,
.surface = m_directionSurface,
.enthalpy = m_directionEnthalpy,
.gravityGradient = m_directionGravityGradient,
.gravityPotential = m_directionGravityPotential
};
}
void VerifyCombined(
const mfem::Vector &direction,
const mfem::Vector &action
) const {
if (direction.Size() != Width() || action.Size() != Height()) {
throw std::invalid_argument(
"The specification-border Jacobian requires compatible, preallocated vectors."
);
}
}
void VerifyStructure(
const mfem::Vector &vector,
const char *role
) const {
if (vector.Size() != StructureSize()) {
throw std::invalid_argument(
std::string("The specification-border structure ") + role + " has the wrong size."
);
}
}
void VerifyBorder(
const mfem::Vector &vector,
const char *role
) const {
if (vector.Size() != BorderSize()) {
throw std::invalid_argument(std::string("The specification border ") + role + " has the wrong size.");
}
}
const ProblemType *m_problem;
mfem::Array<int> m_structureOffsets;
std::optional<Actions> m_actions;
StellarPreconditionerLifecycleSnapshot m_snapshot;
mutable mfem::Vector m_directionDensity;
mutable mfem::Vector m_directionSurface;
mutable mfem::Vector m_directionEnthalpy;
mutable mfem::Vector m_directionGravityGradient;
mutable mfem::Vector m_directionGravityPotential;
};
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
SpecificationBorderJacobianOperator(const Problem &)
-> SpecificationBorderJacobianOperator<std::remove_cvref_t<Problem>>;
template <typename Candidate>
concept SpecificationBorderCouplingOperator = requires(
const Candidate &couplings,
const mfem::Vector &structureDirection,
const mfem::Vector &borderDirection,
mfem::Vector &structureAction,
mfem::Vector &borderAction
) {
{ couplings.StructureSize() } -> std::same_as<int>;
{ couplings.BorderSize() } -> std::same_as<int>;
couplings.ApplyStructureToBorder(structureDirection, borderAction);
couplings.ApplyBorderToStructure(borderDirection, structureAction);
couplings.ApplyBorderToBorder(borderDirection, borderAction);
};
struct SpecificationBorderFactorizationStatistics final {
std::uint64_t setups{0};
std::uint64_t applications{0};
std::uint64_t structureInverseApplications{0};
std::uint64_t cachedStructureInverseBorderApplications{0};
std::uint64_t structureToBorderApplications{0};
std::uint64_t borderToStructureApplications{0};
std::uint64_t borderToBorderApplications{0};
std::uint64_t schurProbes{0};
};
template <
SpecificationBorderCouplingOperator CouplingOperator,
ApplicationContract StructureInverseContract = ApplicationContract::stationary_linear>
class SpecificationBorderFactorizationOperator final : public mfem::Solver {
public:
static constexpr bool cachesStructureInverseBorderCoupling =
StructureInverseContract == ApplicationContract::stationary_linear;
SpecificationBorderFactorizationOperator(
const mfem::Solver &structureInverse,
const CouplingOperator &couplings,
backend::DenseDirect borderBackend = {}
)
: mfem::Solver(couplings.StructureSize() + couplings.BorderSize()),
m_structureInverse(std::addressof(structureInverse)),
m_couplings(std::addressof(couplings)),
m_borderBackend(std::move(borderBackend)),
m_schurComplement(couplings.BorderSize()),
m_structureInverseBorderCoupling(
cachesStructureInverseBorderCoupling ? couplings.StructureSize() : 0,
cachesStructureInverseBorderCoupling ? couplings.BorderSize() : 0
),
m_structureWorkspace(couplings.StructureSize()),
m_structureCoupling(couplings.StructureSize()),
m_borderWorkspace(couplings.BorderSize()),
m_borderCoupling(couplings.BorderSize()),
m_borderDiagonal(couplings.BorderSize()),
m_borderBasis(couplings.BorderSize()) {
if (structureInverse.Height() <= 0 || structureInverse.Height() != structureInverse.Width() ||
structureInverse.Height() != couplings.StructureSize() || couplings.BorderSize() < 0) {
throw std::invalid_argument(
"The structure inverse and specification-border couplings have incompatible dimensions."
);
}
AssembleSchurComplement();
}
SpecificationBorderFactorizationOperator(const SpecificationBorderFactorizationOperator &) = delete;
SpecificationBorderFactorizationOperator &operator=(const SpecificationBorderFactorizationOperator &) = delete;
SpecificationBorderFactorizationOperator(SpecificationBorderFactorizationOperator &&) = delete;
SpecificationBorderFactorizationOperator &operator=(SpecificationBorderFactorizationOperator &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument(
"The specification-border factorization received an incompatible operator."
);
}
}
void Mult(
const mfem::Vector &rightHandSide,
mfem::Vector &action
) const override {
if (rightHandSide.Size() != Width() || action.Size() != Height()) {
throw std::invalid_argument(
"The specification-border factorization requires compatible, preallocated vectors."
);
}
const mfem::Vector structureRightHandSide(
const_cast<mfem::real_t *>(rightHandSide.GetData()), m_couplings->StructureSize()
);
const mfem::Vector borderRightHandSide(
const_cast<mfem::real_t *>(rightHandSide.GetData()) + m_couplings->StructureSize(),
m_couplings->BorderSize()
);
action = 0.0;
mfem::Vector structureAction(action, 0, m_couplings->StructureSize());
mfem::Vector borderAction(action, m_couplings->StructureSize(), m_couplings->BorderSize());
if (m_couplings->BorderSize() == 0) {
m_structureInverse->Mult(structureRightHandSide, structureAction);
++m_statistics.structureInverseApplications;
} else {
m_structureInverse->Mult(structureRightHandSide, m_structureWorkspace);
m_couplings->ApplyStructureToBorder(m_structureWorkspace, m_borderCoupling);
m_borderWorkspace = borderRightHandSide;
m_borderWorkspace -= m_borderCoupling;
m_borderInverse->Mult(m_borderWorkspace, borderAction);
if constexpr (cachesStructureInverseBorderCoupling) {
// W = A^{-1} B was assembled with the border Schur complement, so the
// stationary-linear structure correction is A^{-1} f - W y.
m_structureInverseBorderCoupling.Mult(borderAction, m_structureCoupling);
structureAction = m_structureWorkspace;
structureAction -= m_structureCoupling;
++m_statistics.structureInverseApplications;
++m_statistics.cachedStructureInverseBorderApplications;
} else {
m_couplings->ApplyBorderToStructure(borderAction, m_structureCoupling);
m_structureCoupling *= -1.0;
m_structureCoupling += structureRightHandSide;
m_structureInverse->Mult(m_structureCoupling, structureAction);
m_statistics.structureInverseApplications += 2;
++m_statistics.borderToStructureApplications;
}
++m_statistics.structureToBorderApplications;
}
structureAction.SyncAliasMemory(action);
borderAction.SyncAliasMemory(action);
++m_statistics.applications;
}
void RefreshSchurComplement() {
AssembleSchurComplement();
}
[[nodiscard]] const mfem::DenseMatrix &GetSchurComplement() const noexcept {
return m_schurComplement;
}
[[nodiscard]] const backend::PreparedDenseDirect *GetBorderInverse() const noexcept {
return m_borderInverse.get();
}
[[nodiscard]] const SpecificationBorderFactorizationStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
void AssembleSchurComplement() {
const int borderSize = m_couplings->BorderSize();
if (borderSize == 0) {
m_schurComplement.SetSize(0, 0);
m_borderInverse.reset();
++m_statistics.setups;
return;
}
mfem::Vector schurColumn(borderSize);
for (int column = 0; column < borderSize; ++column) {
m_borderBasis = 0.0;
m_borderBasis(column) = 1.0;
m_couplings->ApplyBorderToStructure(m_borderBasis, m_structureCoupling);
++m_statistics.borderToStructureApplications;
m_structureInverse->Mult(m_structureCoupling, m_structureWorkspace);
++m_statistics.structureInverseApplications;
if constexpr (cachesStructureInverseBorderCoupling) {
m_structureInverseBorderCoupling.SetCol(column, m_structureWorkspace);
}
m_couplings->ApplyStructureToBorder(m_structureWorkspace, m_borderCoupling);
++m_statistics.structureToBorderApplications;
m_couplings->ApplyBorderToBorder(m_borderBasis, m_borderDiagonal);
++m_statistics.borderToBorderApplications;
schurColumn = m_borderDiagonal;
schurColumn -= m_borderCoupling;
for (int row = 0; row < borderSize; ++row) {
m_schurComplement(row, column) = schurColumn(row);
}
++m_statistics.schurProbes;
}
if (m_borderInverse == nullptr) {
m_borderInverse = std::make_unique<backend::PreparedDenseDirect>(m_borderBackend, m_schurComplement);
} else {
m_borderInverse->Refresh(m_schurComplement);
}
++m_statistics.setups;
}
const mfem::Solver *m_structureInverse;
const CouplingOperator *m_couplings;
backend::DenseDirect m_borderBackend;
mfem::DenseMatrix m_schurComplement;
mfem::DenseMatrix m_structureInverseBorderCoupling;
std::unique_ptr<backend::PreparedDenseDirect> m_borderInverse;
mutable mfem::Vector m_structureWorkspace;
mutable mfem::Vector m_structureCoupling;
mutable mfem::Vector m_borderWorkspace;
mutable mfem::Vector m_borderCoupling;
mutable mfem::Vector m_borderDiagonal;
mutable mfem::Vector m_borderBasis;
mutable SpecificationBorderFactorizationStatistics m_statistics;
};
struct SpecificationBorderBlockPreparationReport final {
bool structureRefreshed{false};
bool specificationActionsRefreshed{false};
bool rebuiltSchurComplement{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return structureRefreshed || specificationActionsRefreshed || rebuiltSchurComplement;
}
};
struct PreparedSpecificationBorderBlockStatistics final {
std::uint64_t setups{0};
std::uint64_t refreshChecks{0};
std::uint64_t refreshes{0};
std::uint64_t noOpRefreshes{0};
};
namespace detail {
[[nodiscard]] constexpr bool specificationBorderCachesRequireRefresh(
const bool problemChanged,
const bool structureRefreshed
) noexcept {
return problemChanged || structureRefreshed;
}
} // namespace detail
template <typename Candidate>
concept PreparedSpecificationStructureSolver =
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
requires(
std::remove_cvref_t<Candidate> &prepared,
const std::remove_cvref_t<Candidate> &constantPrepared
) {
{ prepared.Refresh().DidAnyWork() } -> std::convertible_to<bool>;
{ constantPrepared.IsCurrent() } -> std::convertible_to<bool>;
};
/* Exact public preparation boundary for a generated border. A custom
* structure component is accepted only when its own prepare overload
* produces the refreshable solver that this factorization actually uses.
* This keeps unsupported backend combinations in requires/SFINAE space
* instead of diagnosing them from a constructor body. */
template <typename Problem, typename Block>
concept SpecificationBorderPreparableFor =
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
SpecificationBorderBlockType<std::remove_cvref_t<Block>> &&
CompleteSpecificationBorderActionsFor<std::remove_cvref_t<Problem>> &&
std::move_constructible<std::remove_cvref_t<Block>> &&
requires(
const std::remove_cvref_t<Problem> &problem,
typename std::remove_cvref_t<Block>::StructureComponent structure
) {
{
preconditioning::prepare(problem, std::move(structure))
} -> PreparedSpecificationStructureSolver;
};
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem, SpecificationBorderBlockType Block>
requires SpecificationBorderPreparableFor<Problem, Block>
class PreparedSpecificationBorderBlock final : public mfem::Solver {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using BlockType = std::remove_cvref_t<Block>;
using PreparedStructure = decltype(preconditioning::prepare(
std::declval<const ProblemType &>(),
std::declval<typename BlockType::StructureComponent>()
));
static constexpr ApplicationContract structureInverseContract =
backend::applicationContract<typename BlockType::StructureComponent::BackendType>;
public:
using Factorization = SpecificationBorderFactorizationOperator<
SpecificationBorderJacobianOperator<ProblemType>,
structureInverseContract>;
PreparedSpecificationBorderBlock(
const ProblemType &problem,
BlockType block
)
: mfem::Solver(problem.StateSize()),
m_problem(std::addressof(problem)),
m_block(std::move(block)),
m_structure(
preconditioning::prepare(
problem,
m_block.structureComponent()
)
),
m_couplings(problem),
m_factorization(
m_structure,
m_couplings,
m_block.borderBackend()
),
m_snapshot(StellarEquilibriumProblemTraits<ProblemType>::Snapshot(problem)) {
if (m_factorization.Height() != Height() || m_factorization.Width() != Width()) {
throw std::logic_error(
"The prepared specification border does not span the grouped equilibrium coordinates."
);
}
m_statistics.setups = 1;
}
PreparedSpecificationBorderBlock(ProblemType &&, BlockType) = delete;
PreparedSpecificationBorderBlock(const ProblemType &&, BlockType) = delete;
PreparedSpecificationBorderBlock(const PreparedSpecificationBorderBlock &) = delete;
PreparedSpecificationBorderBlock &operator=(const PreparedSpecificationBorderBlock &) = delete;
PreparedSpecificationBorderBlock(PreparedSpecificationBorderBlock &&) = delete;
PreparedSpecificationBorderBlock &operator=(PreparedSpecificationBorderBlock &&) = delete;
void SetOperator(const mfem::Operator &operation) override {
m_factorization.SetOperator(operation);
}
void Mult(
const mfem::Vector &rightHandSide,
mfem::Vector &action
) const override {
if (!IsCurrent()) {
throw std::logic_error("The specification-border block is stale; refresh it before application.");
}
m_factorization.Mult(rightHandSide, action);
}
[[nodiscard]] SpecificationBorderBlockPreparationReport Refresh() {
const auto current = StellarEquilibriumProblemTraits<ProblemType>::Snapshot(*m_problem);
++m_statistics.refreshChecks;
SpecificationBorderBlockPreparationReport report;
const auto structureReport = m_structure.Refresh();
report.structureRefreshed = structureReport.DidAnyWork();
const bool problemChanged = current != m_snapshot;
if (problemChanged) {
report.specificationActionsRefreshed = m_couplings.Refresh();
}
if (detail::specificationBorderCachesRequireRefresh(
problemChanged,
report.structureRefreshed
)) {
m_factorization.RefreshSchurComplement();
report.rebuiltSchurComplement = true;
m_snapshot = current;
++m_statistics.refreshes;
} else {
++m_statistics.noOpRefreshes;
}
return report;
}
[[nodiscard]] bool IsCurrent() const {
return m_structure.IsCurrent() && m_couplings.IsCurrent() && m_problem->IsPrepared() &&
StellarEquilibriumProblemTraits<ProblemType>::Snapshot(*m_problem) == m_snapshot;
}
[[nodiscard]] const BlockType &GetBlock() const noexcept {
return m_block;
}
[[nodiscard]] const ProblemType &GetProblem() const noexcept {
return *m_problem;
}
[[nodiscard]] const PreparedStructure &GetStructurePreconditioner() const noexcept {
return m_structure;
}
[[nodiscard]] const SpecificationBorderJacobianOperator<ProblemType> &GetCouplings() const noexcept {
return m_couplings;
}
[[nodiscard]] const Factorization &GetFactorization() const noexcept {
return m_factorization;
}
[[nodiscard]] const PreparedSpecificationBorderBlockStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
const ProblemType *m_problem;
BlockType m_block;
PreparedStructure m_structure;
SpecificationBorderJacobianOperator<ProblemType> m_couplings;
Factorization m_factorization;
StellarPreconditionerLifecycleSnapshot m_snapshot;
PreparedSpecificationBorderBlockStatistics m_statistics;
};
template <
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
PreconditionerComponent StructureComponent>
requires CompleteSpecificationBorderActionsFor<Problem>
[[nodiscard]] constexpr auto specificationBorderBlock(
const Problem &,
StructureComponent structureComponent,
backend::DenseDirect borderBackend = {}
) {
using ProblemType = std::remove_cvref_t<Problem>;
using Block = SpecificationBorderBlock<
StructureComponent, typename ProblemType::ModelType, typename ProblemType::FormType,
typename ProblemType::JacobianFormType>;
using Plan = PreconditionerPlan<Block>;
static_assert(
CompletePreconditionerFor<Plan, typename ProblemType::FormType>,
"The model-compiled preconditioner must own every correction and residual block exactly once."
);
static_assert(
CompatiblePreconditionerFor<Plan, typename ProblemType::FormType, typename ProblemType::JacobianFormType>,
"Every coupling required by the model-compiled preconditioner must exist in the compiled Jacobian."
);
return Block{std::move(structureComponent), std::move(borderBackend)};
}
template <DefaultStellarPreconditionerAvailableFor Problem>
[[nodiscard]] constexpr auto specificationBorderBlock(const Problem &problem) {
return specificationBorderBlock(problem, stellarStructureBlock(problem), backend::DenseDirect{});
}
template <DefaultStellarPreconditionerAvailableFor Problem>
[[nodiscard]] constexpr auto makePreconditioner(const Problem &problem) {
return specificationBorderBlock(problem);
}
} // namespace mean_field::preconditioning