1454 lines
63 KiB
C++
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
|