Files
MeanField/libmeanfield/interface/operators/root_manifest.cppm

1454 lines
63 KiB
C++

module;
#include <algorithm>
#include <array>
#include <cmath>
#include <concepts>
#include <cstddef>
#include <optional>
#include <span>
#include <stdexcept>
#include <string_view>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.root_manifest;
export import :model.compiled_fixed_mass;
export import :model.compiled_fixed_angular_momentum;
export import :model.compiled_fixed_central_density;
export import :model.specifications;
export import :operators.stellar_equilibrium_compiler;
export import :utils.blocks;
export namespace mean_field::operators {
enum class RootBlockKind { value, residual };
enum class RootBlockProvenance { physical_operator, model_specification };
enum class RootRowInjection { physical_equation, append_global, replace_carrier_rows };
enum class RootColumnPolicy {
physical_state,
existing_physical_multiplier,
generated_physical_coordinate,
solver_border,
no_column
};
enum class RootScalePolicy { unscaled, target_relative };
struct RootBlockDescriptor final {
std::string_view stableId;
std::string_view symbol;
RootBlockKind kind;
RootBlockProvenance provenance;
std::string_view source;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
RootScalePolicy scalePolicy;
int canonicalIndex;
int offset;
int size;
double scale;
};
struct RootRowReplacementDescriptor final {
std::string_view stableId;
std::string_view sourceSpecification;
models::SpecificationRole role;
int carrierResidualBlock;
int replacedRowCount;
};
struct RootConstraintDescriptor final {
std::string_view stableId;
models::SpecificationRole role;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
int valueBlock;
int residualBlock;
int rowArity;
int columnArity;
double target;
std::optional<double> carrierTarget;
std::string_view targetUnits;
std::string_view residualUnits;
double residualScale;
};
struct RootConstraintReport final {
RootConstraintDescriptor descriptor;
double achieved;
double dimensionalResidual;
double scaledResidual;
};
namespace detail {
template <models::SpecificationRole Role, typename Model>
concept HasUniqueModelSpecificationRole = requires {
typename std::remove_cvref_t<Model>::SpecificationTypes;
requires models::HasUniqueSpecificationForRole<
Role, typename std::remove_cvref_t<Model>::SpecificationTypes>;
};
template <models::SpecificationRole Role, typename Model>
requires HasUniqueModelSpecificationRole<Role, Model>
using ModelSpecificationForRole =
models::SpecificationForRoleT<Role, typename std::remove_cvref_t<Model>::SpecificationTypes>;
template <models::SpecificationRole Role, typename Model>
concept AccessibleModelSpecificationRole =
HasUniqueModelSpecificationRole<Role, Model> &&
(requires(const std::remove_cvref_t<Model> &model) {
{
model.template specificationForRole<Role>()
} -> std::same_as<const ModelSpecificationForRole<Role, Model> &>;
} || requires(const std::remove_cvref_t<Model> &model) {
{
model.template specification<ModelSpecificationForRole<Role, Model>>()
} -> std::same_as<const ModelSpecificationForRole<Role, Model> &>;
});
/*
* Model frontends may expose a convenient specificationForRole()
* accessor, while the lightweight compile-time model deliberately
* exposes only specification<Specification>(). Keep manifest
* compilation independent of that presentation choice.
*/
template <
models::SpecificationRole Role,
typename Model>
requires AccessibleModelSpecificationRole<
Role,
Model>
[[nodiscard]] const ModelSpecificationForRole<
Role,
Model> &
modelSpecificationForRole(const Model &model) {
if constexpr (requires {
{
model.template specificationForRole<Role>()
} -> std::same_as<const ModelSpecificationForRole<Role, Model> &>;
}) {
return model.template specificationForRole<Role>();
} else {
using Specification = ModelSpecificationForRole<Role, Model>;
return model.template specification<Specification>();
}
}
struct ManifestTargetData final {
double target;
std::optional<double> carrierTarget;
double residualScale;
};
template <typename Specification, typename Model, typename = void> struct ManifestTargetAdapter {
static constexpr bool registered = false;
};
template <typename Specification>
using DeclaredSpecificationManifest =
typename models::SpecificationContribution<std::remove_cvref_t<Specification>>::Manifest;
template <typename Specification> [[nodiscard]] consteval bool genericManifestTargetIsComplete() {
using Manifest = DeclaredSpecificationManifest<Specification>;
if constexpr (!requires {
typename Manifest::TargetQuantity;
typename Manifest::ConstraintResidualQuantity;
}) {
// Preserve the lower-level, structurally declared manifest
// path. It has no dimensional types from which a distinct
// residual reference could be inferred.
return true;
} else if constexpr (
std::same_as<typename Manifest::TargetQuantity, typename Manifest::ConstraintResidualQuantity>
) {
return true;
} else {
using ResidualReference = dimensions::QuantityValue<typename Manifest::ConstraintResidualQuantity>;
return requires(const Specification &specification) {
{ specification.residualReference() } -> std::same_as<ResidualReference>;
};
}
}
/*
* The ordinary physics-facing extension path is a strongly typed
* target(). If the constraint equation has different units, the
* specification also supplies a strongly typed residualReference().
* This remains local physics vocabulary and prevents a target with,
* for example, density units from silently scaling an energy row.
*/
template <typename Specification, typename Model>
requires(!std::same_as<std::remove_cvref_t<Specification>, models::FixedCentralDensity>) &&
requires(const Specification &specification) {
{ specification.target().value() } -> std::convertible_to<double>;
}
struct ManifestTargetAdapter<Specification, Model> {
using Manifest = DeclaredSpecificationManifest<Specification>;
static constexpr bool registered = genericManifestTargetIsComplete<Specification>();
[[nodiscard]] static ManifestTargetData Read(
const Specification &specification,
const Model &
)
requires registered
{
const double target = static_cast<double>(specification.target().value());
const double residualReference = [&] {
if constexpr (requires {
typename Manifest::TargetQuantity;
typename Manifest::ConstraintResidualQuantity;
}) {
if constexpr (!std::same_as<
typename Manifest::TargetQuantity,
typename Manifest::ConstraintResidualQuantity>) {
return static_cast<double>(specification.residualReference().value());
} else {
return target;
}
} else {
return target;
}
}();
return {
.target = target,
.carrierTarget = std::nullopt,
.residualScale = std::max(std::abs(residualReference), 1.0e-300)
};
}
};
template <typename Model> struct ManifestTargetAdapter<models::FixedTotalMass, Model> {
static constexpr bool registered = true;
[[nodiscard]] static ManifestTargetData Read(
const models::FixedTotalMass &specification,
const Model &
) {
const double target = specification.targetMass().value();
return {
.target = target, .carrierTarget = target, .residualScale = std::max(std::abs(target), 1.0e-300)
};
}
};
template <typename Model> struct ManifestTargetAdapter<models::FixedAngularMomentum, Model> {
static constexpr bool registered = true;
[[nodiscard]] static ManifestTargetData Read(
const models::FixedAngularMomentum &specification,
const Model &
) {
const double target = specification.targetAngularMomentum().value();
return {
.target = target,
.carrierTarget = std::nullopt,
.residualScale = std::max(std::abs(target), 1.0e-300)
};
}
};
template <typename Model>
requires requires(const models::FixedCentralDensity &specification, const Model &model) {
{
models::compileConstraint(
specification, modelSpecificationForRole<models::SpecificationRole::constitutive_law>(model)
)
.targetDensity()
.value()
} -> std::convertible_to<double>;
{
models::compileConstraint(
specification, modelSpecificationForRole<models::SpecificationRole::constitutive_law>(model)
)
.targetEnthalpy()
.value()
} -> std::convertible_to<double>;
}
struct ManifestTargetAdapter<models::FixedCentralDensity, Model> {
static constexpr bool registered = true;
[[nodiscard]] static ManifestTargetData Read(
const models::FixedCentralDensity &specification,
const Model &model
) {
const auto compiled = models::compileConstraint(
specification, modelSpecificationForRole<models::SpecificationRole::constitutive_law>(model)
);
const double target = compiled.targetDensity().value();
const double carrierTarget = compiled.targetEnthalpy().value();
return {
.target = target,
.carrierTarget = carrierTarget,
.residualScale = std::max(std::abs(carrierTarget), 1.0e-300)
};
}
};
struct StaticRootBlockDescriptor final {
std::string_view stableId;
std::string_view symbol;
RootBlockProvenance provenance;
std::string_view source;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
RootScalePolicy scalePolicy;
};
template <typename Block> struct RootBlockTraits;
template <typename Block>
concept DescribedRootBlock = requires { RootBlockTraits<Block>::descriptor; };
#define MEAN_FIELD_PHYSICAL_VALUE_BLOCK(BlockType, StableId, Symbol) \
template <> struct RootBlockTraits<BlockType> { \
static constexpr StaticRootBlockDescriptor descriptor{ \
StableId, \
Symbol, \
RootBlockProvenance::physical_operator, \
"stellar_equilibrium", \
RootRowInjection::physical_equation, \
RootColumnPolicy::physical_state, \
RootScalePolicy::unscaled \
}; \
}
#define MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(BlockType, StableId, Symbol) \
template <> struct RootBlockTraits<BlockType> { \
static constexpr StaticRootBlockDescriptor descriptor{ \
StableId, \
Symbol, \
RootBlockProvenance::physical_operator, \
"stellar_equilibrium", \
RootRowInjection::physical_equation, \
RootColumnPolicy::no_column, \
RootScalePolicy::unscaled \
}; \
}
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::density::mass::value,
"density",
"rho"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::displacement::geometry::value,
"volume_displacement",
"d"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::surface_deformation::parameters::value,
"surface_deformation",
"q"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::gravity::gradient::value,
"gravity_gradient",
"g"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::gravity::poisson::value,
"gravity_potential",
"Phi"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::enthalpy::specific::value,
"specific_enthalpy",
"h"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::gravity::gradient::residual,
"gravity_gradient_relation",
"R_g"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::gravity::poisson::residual,
"poisson_balance",
"R_Phi"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::density::mass::residual,
"barotropic_closure",
"R_rho"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::displacement::geometry::residual,
"mechanical_balance",
"R_d"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::surface_deformation::shape_equilibrium::residual,
"surface_shape_balance",
"R_q"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::enthalpy::specific::residual,
"hydrostatic_balance",
"R_h"
);
#undef MEAN_FIELD_PHYSICAL_VALUE_BLOCK
#undef MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK
/*
* Generated block descriptors are constexpr objects. A third-party
* manifest can satisfy the broad, physics-facing manifest concept while
* publishing non-constant or empty metadata and merely claiming
* available=true. Detect both cases before selecting RootBlockTraits:
* attempting to initialize its constexpr descriptor first would turn a
* capability query into a hard template error.
*/
template <typename Candidate>
using ConstantCompleteGeneratedManifestMetadata = std::bool_constant<
!static_cast<std::string_view>(Candidate::valueStableId).empty() &&
!static_cast<std::string_view>(Candidate::valueSymbol).empty() &&
!static_cast<std::string_view>(Candidate::residualStableId).empty() &&
!static_cast<std::string_view>(Candidate::residualSymbol).empty() &&
!static_cast<std::string_view>(Candidate::targetUnits).empty() &&
!static_cast<std::string_view>(Candidate::residualUnits).empty()>;
template <typename Candidate, typename = void> struct CompleteGeneratedManifestMetadata : std::false_type { };
template <typename Candidate>
struct CompleteGeneratedManifestMetadata<
Candidate,
std::void_t<ConstantCompleteGeneratedManifestMetadata<Candidate>>>
: ConstantCompleteGeneratedManifestMetadata<Candidate> { };
template <typename Generated>
concept ManifestDescribedGeneratedCoordinate =
requires { typename Generated::SpecificationType; } &&
models::ModelSpecification<typename Generated::SpecificationType> &&
models::CompleteGeneratedManifestFor<typename Generated::SpecificationType> &&
CompleteGeneratedManifestMetadata<
typename models::SpecificationContribution<typename Generated::SpecificationType>::Manifest>::value;
template <models::ModelSpecification Specification>
[[nodiscard]] consteval RootColumnPolicy generatedColumnPolicy() {
constexpr auto kind = models::SpecificationContribution<Specification>::generatedStateKind;
if constexpr (kind == models::GeneratedStateKind::multiplier) {
return RootColumnPolicy::existing_physical_multiplier;
} else if constexpr (kind == models::GeneratedStateKind::physical_coordinate) {
return RootColumnPolicy::generated_physical_coordinate;
} else if constexpr (kind == models::GeneratedStateKind::solver_border) {
return RootColumnPolicy::solver_border;
} else {
return RootColumnPolicy::no_column;
}
}
/*
* Generated blocks are described by their owning physics
* specification. This is the manifest analogue of the variadic
* operator compiler: adding a specification must not require another
* combination-specific block-traits specialization.
*/
template <ManifestDescribedGeneratedCoordinate Generated>
struct RootBlockTraits<utils::blocks::generated_value_block<Generated>> {
private:
using Specification = typename Generated::SpecificationType;
using Manifest = typename models::SpecificationContribution<Specification>::Manifest;
public:
static constexpr StaticRootBlockDescriptor descriptor{
Manifest::valueStableId,
Manifest::valueSymbol,
RootBlockProvenance::model_specification,
models::SpecificationTraits<Specification>::name,
RootRowInjection::physical_equation,
generatedColumnPolicy<Specification>(),
RootScalePolicy::unscaled
};
};
template <ManifestDescribedGeneratedCoordinate Generated>
struct RootBlockTraits<utils::blocks::generated_residual_block<Generated>> {
private:
using Specification = typename Generated::SpecificationType;
using Manifest = typename models::SpecificationContribution<Specification>::Manifest;
public:
static constexpr StaticRootBlockDescriptor descriptor{
Manifest::residualStableId,
Manifest::residualSymbol,
RootBlockProvenance::model_specification,
models::SpecificationTraits<Specification>::name,
RootRowInjection::append_global,
RootColumnPolicy::no_column,
RootScalePolicy::target_relative
};
};
template <typename GeneratedValues, typename GeneratedResiduals> struct SingleGeneratedBlockPair;
template <typename GeneratedValue, typename GeneratedResidual>
struct SingleGeneratedBlockPair<
models::ModelTypeList<GeneratedValue>,
models::ModelTypeList<GeneratedResidual>> {
using Value = utils::blocks::generated_value_block<GeneratedValue>;
using Residual = utils::blocks::generated_residual_block<GeneratedResidual>;
};
template <typename Specification, typename Model> struct RootManifestSpecificationContribution {
private:
static constexpr auto role = models::SpecificationTraits<Specification>::role;
static constexpr auto generatedValueArity =
models::SpecificationContribution<Specification>::generatedValueArity;
static constexpr auto generatedResidualArity =
models::SpecificationContribution<Specification>::generatedResidualArity;
public:
static constexpr bool registered = generatedValueArity == 0 && generatedResidualArity == 0 &&
role != models::SpecificationRole::boundary_condition;
static constexpr std::size_t constraintCount = 0;
static constexpr std::size_t replacementCount = 0;
template <typename Form> static constexpr bool completeFor = registered;
template <
typename Form,
std::size_t Count>
static void AppendConstraints(
std::array<
RootConstraintDescriptor,
Count> &,
std::size_t &,
const Model &
) noexcept {
}
template <
typename Form,
std::size_t Count>
static void AppendReplacements(
std::array<
RootRowReplacementDescriptor,
Count> &,
std::size_t &,
const Model &,
int
) noexcept {
}
};
template <models::ModelSpecification Specification, typename Model>
requires(
models::SpecificationContribution<Specification>::generatedValueArity == 1 &&
models::SpecificationContribution<Specification>::generatedResidualArity == 1 &&
models::CompleteGeneratedManifestFor<Specification> &&
ManifestTargetAdapter<Specification, Model>::registered
)
struct RootManifestSpecificationContribution<Specification, Model> {
private:
using Contribution = models::SpecificationContribution<Specification>;
using GeneratedBlocks = SingleGeneratedBlockPair<
typename Contribution::GeneratedValues,
typename Contribution::GeneratedResiduals>;
public:
using ValueBlock = typename GeneratedBlocks::Value;
using ResidualBlock = typename GeneratedBlocks::Residual;
using Manifest = typename Contribution::Manifest;
static constexpr bool registered = true;
static constexpr std::size_t constraintCount = 1;
static constexpr std::size_t replacementCount = 0;
template <typename Form>
static constexpr bool completeFor =
utils::blocks::contains_type_v<ValueBlock, typename Form::value_blocks> &&
utils::blocks::contains_type_v<ResidualBlock, typename Form::residual_blocks>;
template <
typename Form,
std::size_t Count>
static void AppendConstraints(
std::array<
RootConstraintDescriptor,
Count> &descriptors,
std::size_t &next,
const Model &model
) {
const auto target = ManifestTargetAdapter<Specification, Model>::Read(
model.template specification<Specification>(), model
);
descriptors[next++] = {
.stableId = models::SpecificationTraits<Specification>::name,
.role = models::SpecificationTraits<Specification>::role,
.rowInjection = RootRowInjection::append_global,
.columnPolicy = generatedColumnPolicy<Specification>(),
.valueBlock = utils::blocks::type_index_v<ValueBlock, typename Form::value_blocks>,
.residualBlock = utils::blocks::type_index_v<ResidualBlock, typename Form::residual_blocks>,
.rowArity = ResidualBlock::static_block_size,
.columnArity = ValueBlock::static_block_size,
.target = target.target,
.carrierTarget = target.carrierTarget,
.targetUnits = Manifest::targetUnits,
.residualUnits = Manifest::residualUnits,
.residualScale = target.residualScale
};
}
template <
typename Form,
std::size_t Count>
static void AppendReplacements(
std::array<
RootRowReplacementDescriptor,
Count> &,
std::size_t &,
const Model &,
int
) noexcept {
}
};
/*
* Boundary equations replace rows rather than append a generated
* scalar. This is a per-physics-condition adapter, never a
* constraint-pack adapter. Future surface families register their
* carrier equation here (or through a later generalized surface
* compiler) and automatically compose with every integral/phase pack.
*/
template <typename Model>
requires requires(const Model &model) {
{
modelSpecificationForRole<models::SpecificationRole::boundary_condition>(model)
.targetPressure()
.value()
} -> std::convertible_to<double>;
}
struct RootManifestSpecificationContribution<surface::Isobaric, Model> {
static constexpr bool registered = true;
static constexpr std::size_t constraintCount = 1;
static constexpr std::size_t replacementCount = 1;
template <typename Form>
static constexpr bool completeFor = utils::blocks::
contains_type_v<utils::blocks::enthalpy::specific::residual, typename Form::residual_blocks>;
template <
typename Form,
std::size_t Count>
static void AppendConstraints(
std::array<
RootConstraintDescriptor,
Count> &descriptors,
std::size_t &next,
const Model &model
) {
descriptors[next++] = {
.stableId = models::SpecificationTraits<surface::Isobaric>::name,
.role = models::SpecificationRole::boundary_condition,
.rowInjection = RootRowInjection::replace_carrier_rows,
.columnPolicy = RootColumnPolicy::no_column,
.valueBlock = -1,
.residualBlock = utils::blocks::type_index_v<
utils::blocks::enthalpy::specific::residual, typename Form::residual_blocks>,
.rowArity = 0,
.columnArity = 0,
.target = modelSpecificationForRole<models::SpecificationRole::boundary_condition>(model)
.targetPressure()
.value(),
.carrierTarget = std::nullopt,
.targetUnits = "pressure",
.residualUnits = "specific_enthalpy",
.residualScale = 1.0
};
}
template <
typename Form,
std::size_t Count>
static void AppendReplacements(
std::array<
RootRowReplacementDescriptor,
Count> &descriptors,
std::size_t &next,
const Model &,
const int replacedRowCount
) {
descriptors[next++] = {
.stableId = "isobaric_surface.replacement",
.sourceSpecification = models::SpecificationTraits<surface::Isobaric>::name,
.role = models::SpecificationRole::boundary_condition,
.carrierResidualBlock = utils::blocks::type_index_v<
utils::blocks::enthalpy::specific::residual, typename Form::residual_blocks>,
.replacedRowCount = replacedRowCount
};
}
};
template <typename Model, typename SpecificationSet> struct CompileRootManifestContributions;
template <typename Model, models::ModelSpecification... Specifications>
struct CompileRootManifestContributions<Model, models::detail::SpecificationSetStorage<Specifications...>> {
static constexpr bool complete =
(RootManifestSpecificationContribution<Specifications, Model>::registered && ...);
static constexpr std::size_t constraintCount =
(std::size_t{0} + ... + RootManifestSpecificationContribution<Specifications, Model>::constraintCount);
static constexpr std::size_t replacementCount =
(std::size_t{0} + ... + RootManifestSpecificationContribution<Specifications, Model>::replacementCount);
template <typename Form>
static constexpr bool completeFor =
complete &&
(RootManifestSpecificationContribution<Specifications, Model>::template completeFor<Form> && ...);
template <typename Form>
[[nodiscard]] static std::array<
RootConstraintDescriptor,
constraintCount>
MakeConstraints(const Model &model) {
std::array<RootConstraintDescriptor, constraintCount> descriptors{};
std::size_t next = 0;
(RootManifestSpecificationContribution<Specifications, Model>::template AppendConstraints<Form>(
descriptors, next, model
),
...);
return descriptors;
}
template <typename Form>
[[nodiscard]] static std::array<
RootRowReplacementDescriptor,
replacementCount>
MakeReplacements(
const Model &model,
const int replacedRowCount
) {
std::array<RootRowReplacementDescriptor, replacementCount> descriptors{};
std::size_t next = 0;
(RootManifestSpecificationContribution<Specifications, Model>::template AppendReplacements<Form>(
descriptors, next, model, replacedRowCount
),
...);
return descriptors;
}
};
template <typename Blocks> struct AllRootBlocksAreDescribed;
template <typename... Blocks>
struct AllRootBlocksAreDescribed<utils::blocks::type_list<Blocks...>>
: std::bool_constant<(DescribedRootBlock<Blocks> && ...)> { };
template <typename... Blocks> [[nodiscard]] consteval bool rootBlockStableIdsAreUnique() {
constexpr std::array<std::string_view, sizeof...(Blocks)> stableIds{
RootBlockTraits<Blocks>::descriptor.stableId...
};
for (std::size_t first = 0; first < stableIds.size(); ++first) {
for (std::size_t second = first + 1; second < stableIds.size(); ++second) {
if (stableIds[first] == stableIds[second]) {
return false;
}
}
}
return true;
}
/*
* Stable IDs are machine identities within a block kind. Symbols are
* deliberately excluded: they are mathematical presentation labels
* and two independent constraints may reasonably use the same one.
*/
template <typename Blocks> struct RootBlockStableIdsAreUnique : std::false_type { };
template <typename... Blocks>
requires(DescribedRootBlock<Blocks> && ...)
struct RootBlockStableIdsAreUnique<utils::blocks::type_list<Blocks...>>
: std::bool_constant<rootBlockStableIdsAreUnique<Blocks...>()> { };
template <typename Block> struct GeneratedRootBlockOwner {
static constexpr bool available = false;
static constexpr bool isResidual = false;
};
template <typename Generated>
requires requires { typename Generated::SpecificationType; }
struct GeneratedRootBlockOwner<utils::blocks::generated_value_block<Generated>> {
using Specification = typename Generated::SpecificationType;
static constexpr bool available = true;
static constexpr bool isResidual = false;
};
template <typename Generated>
requires requires { typename Generated::SpecificationType; }
struct GeneratedRootBlockOwner<utils::blocks::generated_residual_block<Generated>> {
using Specification = typename Generated::SpecificationType;
static constexpr bool available = true;
static constexpr bool isResidual = true;
};
template <
typename Block,
typename Model>
[[nodiscard]] consteval bool rootBlockBelongsToModel() {
if constexpr (!GeneratedRootBlockOwner<Block>::available) {
return true;
} else {
using Specification = typename GeneratedRootBlockOwner<Block>::Specification;
return requires { requires Model::template containsSpecification<Specification>; };
}
}
template <typename Blocks, typename Model> struct AllGeneratedRootBlocksBelongToModel;
template <typename Model, typename... Blocks>
struct AllGeneratedRootBlocksBelongToModel<utils::blocks::type_list<Blocks...>, Model>
: std::bool_constant<(rootBlockBelongsToModel<Blocks, Model>() && ...)> { };
template <typename Model, typename Form, typename = void> struct RootManifestCompilation {
static constexpr bool complete = false;
static constexpr std::size_t constraintCount = 0;
static constexpr std::size_t replacementCount = 0;
};
template <typename Model, typename Form>
struct RootManifestCompilation<
Model,
Form,
std::void_t<
typename Model::SpecificationTypes,
typename Form::value_blocks,
typename Form::residual_blocks>> {
using Contributions = CompileRootManifestContributions<Model, typename Model::SpecificationTypes>;
static constexpr bool complete =
Contributions::template completeFor<Form> &&
AllRootBlocksAreDescribed<typename Form::value_blocks>::value &&
AllRootBlocksAreDescribed<typename Form::residual_blocks>::value &&
RootBlockStableIdsAreUnique<typename Form::value_blocks>::value &&
RootBlockStableIdsAreUnique<typename Form::residual_blocks>::value &&
AllGeneratedRootBlocksBelongToModel<typename Form::value_blocks, Model>::value &&
AllGeneratedRootBlocksBelongToModel<typename Form::residual_blocks, Model>::value;
static constexpr std::size_t constraintCount = Contributions::constraintCount;
static constexpr std::size_t replacementCount = Contributions::replacementCount;
};
template <
typename Block,
typename Model>
[[nodiscard]] double modelBlockScale(const Model &model) {
if constexpr (GeneratedRootBlockOwner<Block>::isResidual) {
using Specification = typename GeneratedRootBlockOwner<Block>::Specification;
static_assert(
ManifestTargetAdapter<Specification, Model>::registered,
"A generated residual needs a physics target adapter before it can enter the root manifest."
);
return ManifestTargetAdapter<Specification, Model>::Read(
model.template specification<Specification>(), model
)
.residualScale;
} else {
return 1.0;
}
}
template <
RootBlockKind Kind,
typename Model,
typename... Blocks>
[[nodiscard]] std::array<
RootBlockDescriptor,
sizeof...(Blocks)>
makeModelBlockDescriptors(
const mfem::Array<int> &offsets,
const Model &model,
utils::blocks::type_list<Blocks...>
) {
static_assert(
(DescribedRootBlock<Blocks> && ...), "Every compiled equilibrium block requires root-manifest metadata."
);
std::array<RootBlockDescriptor, sizeof...(Blocks)> descriptors{};
int index = 0;
((descriptors[index] =
{.stableId = RootBlockTraits<Blocks>::descriptor.stableId,
.symbol = RootBlockTraits<Blocks>::descriptor.symbol,
.kind = Kind,
.provenance = RootBlockTraits<Blocks>::descriptor.provenance,
.source = RootBlockTraits<Blocks>::descriptor.source,
.rowInjection = RootBlockTraits<Blocks>::descriptor.rowInjection,
.columnPolicy = RootBlockTraits<Blocks>::descriptor.columnPolicy,
.scalePolicy = RootBlockTraits<Blocks>::descriptor.scalePolicy,
.canonicalIndex = index,
.offset = offsets[index],
.size = offsets[index + 1] - offsets[index],
.scale = modelBlockScale<Blocks>(model)},
++index),
...);
return descriptors;
}
template <models::SpecifiedModelType Model>
inline constexpr std::size_t rootConstraintCount =
CompileRootManifestContributions<Model, typename Model::SpecificationTypes>::constraintCount;
template <models::SpecifiedModelType Model>
inline constexpr std::size_t rootReplacementCount =
CompileRootManifestContributions<Model, typename Model::SpecificationTypes>::replacementCount;
template <
typename Model,
typename Form,
typename JacobianForm>
[[nodiscard]] consteval bool manifestMatchesCompiledEquilibriumSystem() {
if constexpr (StellarEquilibriumSystemCompilable<Model>) {
return RootManifestCompilation<std::remove_cvref_t<Model>, std::remove_cvref_t<Form>>::complete &&
std::same_as<Form, CompiledStellarEquilibriumForm<Model>> &&
std::same_as<JacobianForm, CompiledStellarEquilibriumJacobianForm<Model>>;
} else {
return false;
}
}
} // namespace detail
template <typename Model, typename Form>
inline constexpr bool rootManifestIsCompilable =
detail::RootManifestCompilation<std::remove_cvref_t<Model>, std::remove_cvref_t<Form>>::complete;
template <typename Model, typename Form>
concept CompilableRootManifestFor = rootManifestIsCompilable<Model, Form>;
/*
* A model specification is addressable through specification<T>() only
* when it contributes exactly one equation descriptor to the compiled
* root manifest. Membership in the model is deliberately insufficient:
* material laws such as an EOS participate in the physical operator, but
* do not own a root-constraint descriptor.
*/
template <typename Specification, typename Model>
concept RootManifestSpecificationDescriptorFor =
models::ModelSpecification<std::remove_cvref_t<Specification>> &&
models::SpecifiedModelType<std::remove_cvref_t<Model>> && requires {
requires std::remove_cvref_t<Model>::template containsSpecification<std::remove_cvref_t<Specification>>;
requires detail::RootManifestSpecificationContribution<
std::remove_cvref_t<Specification>, std::remove_cvref_t<Model>>::registered;
requires detail::RootManifestSpecificationContribution<
std::remove_cvref_t<Specification>, std::remove_cvref_t<Model>>::constraintCount == 1;
};
/*
* A non-owning, read-only MFEM block. MFEM's aliasing Vector constructor
* requires a mutable pointer even for read-only use, so the writable alias
* stays private and only const operations are exposed. This prevents a
* residual/Jacobian contribution from mutating solver input through a
* nominally const state or direction view.
*/
class ReadOnlyVectorView final {
public:
ReadOnlyVectorView(
const mfem::Vector &vector,
const int offset,
const int size
)
: m_view(
CheckedData(
vector,
offset,
size
),
size
) {
}
[[nodiscard]] int Size() const noexcept {
return m_view.Size();
}
[[nodiscard]] mfem::real_t operator()(const int index) const {
return m_view(index);
}
[[nodiscard]] const mfem::real_t *GetData() const noexcept {
return m_view.GetData();
}
[[nodiscard]] double Norml2() const {
return m_view.Norml2();
}
[[nodiscard]] const mfem::Vector &asMFEMVector() const noexcept {
return m_view;
}
[[nodiscard]] operator const mfem::Vector &() const noexcept {
return m_view;
}
private:
[[nodiscard]] static mfem::real_t *CheckedData(
const mfem::Vector &vector,
const int offset,
const int size
) {
if (offset < 0 || size < 0 || offset > vector.Size() - size) {
throw std::invalid_argument("A read-only block view received an invalid range.");
}
return const_cast<mfem::real_t *>(vector.GetData()) + offset;
}
mfem::Vector m_view;
};
namespace detail {
template <typename Block> struct RootValueTerm final {
using value = Block;
};
template <typename Block> struct RootResidualTerm final {
using residual = Block;
};
template <typename Blocks> struct SingleRootBlock;
template <typename Block> struct SingleRootBlock<utils::blocks::type_list<Block>> final {
using Type = Block;
};
} // namespace detail
template <typename Form> class RootStateView final {
public:
RootStateView(
const mfem::Vector &state,
const utils::blocks::form_layout<Form> &layout
)
: m_state(state),
m_layout(layout) {
if (state.Size() != layout.value_offsets().Last()) {
throw std::invalid_argument("RootStateView received a vector with the wrong size.");
}
}
RootStateView(
mfem::Vector &&,
const utils::blocks::form_layout<Form> &
) = delete;
RootStateView(
const mfem::Vector &&,
const utils::blocks::form_layout<Form> &
) = delete;
RootStateView(
const mfem::Vector &,
utils::blocks::form_layout<Form> &&
) = delete;
RootStateView(
const mfem::Vector &,
const utils::blocks::form_layout<Form> &&
) = delete;
template <typename Term> [[nodiscard]] ReadOnlyVectorView block(const Term &term) const {
constexpr auto valueBlock = utils::blocks::get_value_block<Form>(term);
return {m_state, m_layout.offset(valueBlock), m_layout.size(valueBlock)};
}
template <models::ModelSpecification Specification>
requires(
stellarEquilibriumSpecificationCompilationComplete<Specification> &&
StellarEquilibriumSpecificationCompilation<Specification>::GeneratedValueBlocks::size == 1 &&
utils::blocks::contains_type_v<
typename detail::SingleRootBlock<
typename StellarEquilibriumSpecificationCompilation<Specification>::GeneratedValueBlocks>::Type,
typename Form::value_blocks>
)
[[nodiscard]] ReadOnlyVectorView generatedCoordinate() const {
using Block = typename detail::SingleRootBlock<
typename StellarEquilibriumSpecificationCompilation<Specification>::GeneratedValueBlocks>::Type;
return block(detail::RootValueTerm<Block>{});
}
[[nodiscard]] const mfem::Vector &vector() const noexcept {
return m_state;
}
private:
const mfem::Vector &m_state;
const utils::blocks::form_layout<Form> &m_layout;
};
template <typename Form> class MutableRootStateView final {
public:
MutableRootStateView(
mfem::Vector &state,
const utils::blocks::form_layout<Form> &layout
)
: m_state(state),
m_layout(layout) {
if (state.Size() != layout.value_offsets().Last()) {
throw std::invalid_argument("MutableRootStateView received a vector with the wrong size.");
}
}
MutableRootStateView(
mfem::Vector &&,
const utils::blocks::form_layout<Form> &
) = delete;
MutableRootStateView(
mfem::Vector &,
utils::blocks::form_layout<Form> &&
) = delete;
MutableRootStateView(
mfem::Vector &,
const utils::blocks::form_layout<Form> &&
) = delete;
template <typename Term> [[nodiscard]] mfem::Vector block(const Term &term) const {
constexpr auto valueBlock = utils::blocks::get_value_block<Form>(term);
return mfem::Vector(m_state.GetData() + m_layout.offset(valueBlock), m_layout.size(valueBlock));
}
template <models::ModelSpecification Specification>
requires(
stellarEquilibriumSpecificationCompilationComplete<Specification> &&
StellarEquilibriumSpecificationCompilation<Specification>::GeneratedValueBlocks::size == 1 &&
utils::blocks::contains_type_v<
typename detail::SingleRootBlock<
typename StellarEquilibriumSpecificationCompilation<Specification>::GeneratedValueBlocks>::Type,
typename Form::value_blocks>
)
[[nodiscard]] mfem::Vector generatedCoordinate() const {
using Block = typename detail::SingleRootBlock<
typename StellarEquilibriumSpecificationCompilation<Specification>::GeneratedValueBlocks>::Type;
return block(detail::RootValueTerm<Block>{});
}
[[nodiscard]] mfem::Vector &vector() const noexcept {
return m_state;
}
private:
mfem::Vector &m_state;
const utils::blocks::form_layout<Form> &m_layout;
};
template <typename Form> class ResidualView final {
public:
ResidualView(
mfem::Vector &residual,
const utils::blocks::form_layout<Form> &layout
)
: m_residual(residual),
m_layout(layout) {
if (residual.Size() != layout.residual_offsets().Last()) {
throw std::invalid_argument("ResidualView received a vector with the wrong size.");
}
}
ResidualView(
mfem::Vector &&,
const utils::blocks::form_layout<Form> &
) = delete;
ResidualView(
mfem::Vector &,
utils::blocks::form_layout<Form> &&
) = delete;
ResidualView(
mfem::Vector &,
const utils::blocks::form_layout<Form> &&
) = delete;
template <typename Term> [[nodiscard]] mfem::Vector block(const Term &term) const {
constexpr auto residualBlock = utils::blocks::get_residual_block<Form>(term);
return mfem::Vector(m_residual.GetData() + m_layout.offset(residualBlock), m_layout.size(residualBlock));
}
template <models::ModelSpecification Specification>
requires(
stellarEquilibriumSpecificationCompilationComplete<Specification> &&
StellarEquilibriumSpecificationCompilation<Specification>::GeneratedResidualBlocks::size == 1 &&
utils::blocks::contains_type_v<
typename detail::SingleRootBlock<typename StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedResidualBlocks>::Type,
typename Form::residual_blocks>
)
[[nodiscard]] mfem::Vector constraintResidual() const {
using Block = typename detail::SingleRootBlock<
typename StellarEquilibriumSpecificationCompilation<Specification>::GeneratedResidualBlocks>::Type;
return block(detail::RootResidualTerm<Block>{});
}
template <typename Term>
void assign(
const Term &term,
const mfem::Vector &source
) const {
mfem::Vector destination = block(term);
if (destination.Size() != source.Size()) {
throw std::invalid_argument("ResidualView block assignment has the wrong size.");
}
destination = source;
destination.SyncAliasMemory(m_residual);
}
[[nodiscard]] mfem::Vector &vector() const noexcept {
return m_residual;
}
private:
mfem::Vector &m_residual;
const utils::blocks::form_layout<Form> &m_layout;
};
template <models::SpecifiedModelType Model, typename Form, typename JacobianForm>
requires utils::blocks::valid_jacobian_form<Form, JacobianForm>
class CompiledRootManifest final {
public:
using ModelType = Model;
using FormType = Form;
using JacobianType = JacobianForm;
using Layout = utils::blocks::form_layout<Form>;
using StateView = RootStateView<Form>;
using MutableStateView = MutableRootStateView<Form>;
using DirectionView = RootStateView<Form>;
using RootResidualView = ResidualView<Form>;
static constexpr bool hasCompleteEquilibriumCompiler =
detail::manifestMatchesCompiledEquilibriumSystem<Model, Form, JacobianForm>();
static constexpr models::ModelCompilationClass compilationClass =
hasCompleteEquilibriumCompiler ? models::EquilibriumSystemCompilation::complete_equilibrium_system
: models::EquilibriumSystemCompilation::equation_contributions_only;
static constexpr bool symbolicallySquare = Model::symbolicallySquare;
/*
* Every descriptor and scale is folded from the model's canonical
* specification pack. There is intentionally no scalar-input or
* constraint-combination constructor.
*/
CompiledRootManifest(
const std::array<
int,
Form::value_block_count> &valueSizes,
const std::array<
int,
Form::residual_block_count> &residualSizes,
const Model &model,
const int replacedSurfaceRowCount
)
requires CompilableRootManifestFor<
Model,
Form>
: m_layout(
valueSizes,
residualSizes
),
m_valueBlocks(
detail::makeModelBlockDescriptors<RootBlockKind::value>(
m_layout.value_offsets(),
model,
typename Form::value_blocks{}
)
),
m_residualBlocks(
detail::makeModelBlockDescriptors<RootBlockKind::residual>(
m_layout.residual_offsets(),
model,
typename Form::residual_blocks{}
)
),
m_replacements(
detail::CompileRootManifestContributions<
Model,
typename Model::SpecificationTypes>::
template MakeReplacements<Form>(
model,
replacedSurfaceRowCount
)
),
m_constraints(
detail::CompileRootManifestContributions<
Model,
typename Model::SpecificationTypes>::template MakeConstraints<Form>(model)
) {
if (replacedSurfaceRowCount < 0) {
throw std::invalid_argument(
"An equilibrium-system manifest cannot contain a negative replacement-row count."
);
}
ValidateNumericalMetadata();
if constexpr (compilationClass == models::EquilibriumSystemCompilation::complete_equilibrium_system) {
if (m_layout.value_offsets().Last() != m_layout.residual_offsets().Last()) {
throw std::invalid_argument(
"A complete equilibrium system must have equal state and equation dimensions."
);
}
}
}
[[nodiscard]] const Layout &layout() const noexcept {
return m_layout;
}
[[nodiscard]] MutableStateView stateView(mfem::Vector &state) const & {
return {state, m_layout};
}
[[nodiscard]] StateView stateView(const mfem::Vector &state) const & {
return {state, m_layout};
}
[[nodiscard]] StateView stateView(mfem::Vector &&) const & = delete;
[[nodiscard]] StateView stateView(const mfem::Vector &&) const & = delete;
[[nodiscard]] MutableStateView stateView(mfem::Vector &) const && = delete;
[[nodiscard]] StateView stateView(const mfem::Vector &) const && = delete;
[[nodiscard]] StateView stateView(mfem::Vector &&) const && = delete;
[[nodiscard]] StateView stateView(const mfem::Vector &&) const && = delete;
[[nodiscard]] DirectionView directionView(const mfem::Vector &direction) const & {
return {direction, m_layout};
}
[[nodiscard]] DirectionView directionView(mfem::Vector &&) const & = delete;
[[nodiscard]] DirectionView directionView(const mfem::Vector &&) const & = delete;
[[nodiscard]] DirectionView directionView(const mfem::Vector &) const && = delete;
[[nodiscard]] DirectionView directionView(mfem::Vector &&) const && = delete;
[[nodiscard]] DirectionView directionView(const mfem::Vector &&) const && = delete;
[[nodiscard]] RootResidualView residualView(mfem::Vector &residual) const & {
return {residual, m_layout};
}
[[nodiscard]] RootResidualView residualView(mfem::Vector &&) const & = delete;
[[nodiscard]] RootResidualView residualView(const mfem::Vector &&) const & = delete;
[[nodiscard]] RootResidualView residualView(mfem::Vector &) const && = delete;
[[nodiscard]] RootResidualView residualView(mfem::Vector &&) const && = delete;
[[nodiscard]] RootResidualView residualView(const mfem::Vector &&) const && = delete;
[[nodiscard]] std::span<const RootBlockDescriptor> valueBlocks() const noexcept {
return m_valueBlocks;
}
[[nodiscard]] std::span<const RootBlockDescriptor> residualBlocks() const noexcept {
return m_residualBlocks;
}
[[nodiscard]] std::span<const RootRowReplacementDescriptor> rowReplacements() const noexcept {
return m_replacements;
}
[[nodiscard]] std::span<const RootConstraintDescriptor> constraints() const noexcept {
return m_constraints;
}
template <models::ModelSpecification Specification>
requires RootManifestSpecificationDescriptorFor<
Specification,
Model>
[[nodiscard]] const RootConstraintDescriptor &specification() const {
constexpr auto requestedKey = models::SpecificationTraits<Specification>::key;
const auto descriptor = std::find_if(
m_constraints.begin(), m_constraints.end(), [requestedKey](const RootConstraintDescriptor &candidate) {
return candidate.role == requestedKey.role && candidate.stableId == requestedKey.stableName;
}
);
if (descriptor == m_constraints.end()) {
throw std::logic_error("The requested model specification has no root-manifest equation descriptor.");
}
return *descriptor;
}
[[nodiscard]] static constexpr std::span<const models::RuntimeSpecificationDescriptor>
specificationDescriptors() noexcept {
return Model::runtimeSpecificationDescriptors();
}
[[nodiscard]] RootConstraintReport fixedMassReport(const double achievedMass) const {
const RootConstraintDescriptor &descriptor = specification<models::FixedTotalMass>();
const double residual = achievedMass - descriptor.target;
return {
.descriptor = descriptor,
.achieved = achievedMass,
.dimensionalResidual = residual,
.scaledResidual = residual / descriptor.residualScale
};
}
private:
void ValidateNumericalMetadata() const {
for (const RootConstraintDescriptor &constraint : m_constraints) {
if (!std::isfinite(constraint.target)) {
throw std::invalid_argument("An equilibrium-system manifest constraint requires a finite target.");
}
if (constraint.carrierTarget.has_value() && !std::isfinite(*constraint.carrierTarget)) {
throw std::invalid_argument(
"An equilibrium-system manifest constraint requires a finite carrier target."
);
}
if (!std::isfinite(constraint.residualScale) || constraint.residualScale <= 0.0) {
throw std::invalid_argument(
"An equilibrium-system manifest constraint requires a finite, positive residual scale."
);
}
}
for (const RootBlockDescriptor &block : m_valueBlocks) {
if (!std::isfinite(block.scale) || block.scale <= 0.0) {
throw std::invalid_argument(
"An equilibrium-system manifest block requires a finite, positive scale."
);
}
}
for (const RootBlockDescriptor &block : m_residualBlocks) {
if (!std::isfinite(block.scale) || block.scale <= 0.0) {
throw std::invalid_argument(
"An equilibrium-system manifest block requires a finite, positive scale."
);
}
}
}
Layout m_layout;
std::array<RootBlockDescriptor, Form::value_block_count> m_valueBlocks;
std::array<RootBlockDescriptor, Form::residual_block_count> m_residualBlocks;
std::array<RootRowReplacementDescriptor, detail::rootReplacementCount<Model>> m_replacements;
std::array<RootConstraintDescriptor, detail::rootConstraintCount<Model>> m_constraints;
};
// Physics-facing names for the public equilibrium-system boundary. The
// root-oriented names remain available while existing solver consumers
// migrate, but new APIs should expose these aliases.
using EquilibriumBlockKind = RootBlockKind;
using EquilibriumBlockProvenance = RootBlockProvenance;
using EquilibriumEquationInjection = RootRowInjection;
using EquilibriumGeneratedVariablePolicy = RootColumnPolicy;
using EquilibriumScalePolicy = RootScalePolicy;
using EquilibriumBlockDescriptor = RootBlockDescriptor;
using EquilibriumEquationReplacementDescriptor = RootRowReplacementDescriptor;
using EquilibriumSpecificationDescriptor = RootConstraintDescriptor;
using EquilibriumSpecificationReport = RootConstraintReport;
template <typename Form> using EquilibriumStateView = RootStateView<Form>;
template <typename Form> using MutableEquilibriumStateView = MutableRootStateView<Form>;
template <typename Form> using EquilibriumResidualView = ResidualView<Form>;
template <models::SpecifiedModelType Model, typename Form, typename JacobianForm>
requires utils::blocks::valid_jacobian_form<Form, JacobianForm>
using EquilibriumSystemManifest = CompiledRootManifest<Model, Form, JacobianForm>;
} // namespace mean_field::operators