feat(surface): major work on implementing surface constraints in a presciption agnostic manner

This commit is contained in:
2026-08-30 16:41:14 -04:00
parent 36adfa1174
commit 0a7f18c5c7
95 changed files with 30144 additions and 25766 deletions

View File

@@ -0,0 +1,66 @@
module;
export module mean_field:surface.compiled;
export import :eos.pressure_surface;
export import :surface.constant;
export import :surface.dependencies;
export namespace mean_field::surface {
template <
eos::EquationOfStateModel EquationOfState,
SurfaceConstraintFormulationType Formulation,
eos::ThermodynamicRelationType SelectedRelation,
typename Dependencies>
class CompiledPressureSurfaceConstraint final {
public:
using PhysicalCondition = ConstantPressureSurface;
using PhysicalQuantity = eos::quantity::Pressure;
using CarrierQuantity = typename Formulation::CarrierQuantity;
using CarrierField = typename Formulation::CarrierField;
using Relation = SelectedRelation;
using SurfaceDependencies = Dependencies;
CompiledPressureSurfaceConstraint(
const ConstantPressureSurface condition,
const EquationOfState &equationOfState
) noexcept
: m_condition(condition),
m_resolvedRelation(
equationOfState,
condition.targetPressure()
) {
}
[[nodiscard]] eos::PressureValue targetPressure() const noexcept {
return m_condition.targetPressure();
}
[[nodiscard]] PressureSurfaceDescriptor descriptor() const noexcept {
return m_condition.descriptor();
}
[[nodiscard]] static constexpr RuntimeSurfaceConstraintDependencies runtimeDependencies() noexcept {
return SurfaceDependencies::runtimeDescription();
}
template <typename SurfaceState> [[nodiscard]] double residual(const SurfaceState &state) const {
return state.value(CarrierQuantity{}).value() - m_resolvedRelation.requiredCarrierValue(state).value();
}
template <
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] double jacobianAction(
const SurfaceState &state,
const SurfaceVariation &variation
) const {
return variation.value(CarrierQuantity{}).value() -
m_resolvedRelation.carrierCorrectionJacobianAction(state, variation);
}
private:
ConstantPressureSurface m_condition;
eos::ResolvedPressureSurfaceRelation<EquationOfState, Relation> m_resolvedRelation;
};
} // namespace mean_field::surface

View File

@@ -0,0 +1,143 @@
module;
#include <cstddef>
#include <tuple>
#include <type_traits>
export module mean_field:surface.compiler;
export import :surface.compiled;
export namespace mean_field::surface {
namespace detail {
template <typename RelationType, typename Formulation, typename EquationOfState>
struct PressureSurfaceRelationMatches : std::false_type { };
template <typename OutputQuantity, typename... InputQuantities, typename Formulation, typename EquationOfState>
struct PressureSurfaceRelationMatches<
eos::Relation<OutputQuantity, InputQuantities...>,
Formulation,
EquationOfState>
: std::bool_constant<
std::same_as<OutputQuantity, typename Formulation::CarrierQuantity> &&
(std::same_as<eos::quantity::Pressure, InputQuantities> || ...) &&
((std::same_as<eos::quantity::Pressure, InputQuantities> ||
(surfaceBindingCount<typename Formulation::StateBindings, InputQuantities> == 1 &&
eos::SupportsPartialDerivative<
EquationOfState,
eos::Relation<OutputQuantity, InputQuantities...>,
InputQuantities>)) &&
...)> { };
template <typename Catalog, typename Formulation, typename EquationOfState>
struct MatchingPressureSurfaceRelations;
template <typename... Relations, typename Formulation, typename EquationOfState>
struct MatchingPressureSurfaceRelations<eos::RelationCatalog<Relations...>, Formulation, EquationOfState> {
using Tuple = decltype(std::tuple_cat(
std::conditional_t<
PressureSurfaceRelationMatches<Relations, Formulation, EquationOfState>::value,
std::tuple<Relations>,
std::tuple<>>{}...
));
static constexpr std::size_t count = std::tuple_size_v<Tuple>;
};
template <std::size_t Count, typename Tuple> struct UniquePressureSurfaceRelation {
using Type = void;
};
template <typename Tuple> struct UniquePressureSurfaceRelation<1, Tuple> {
using Type = std::tuple_element_t<0, Tuple>;
};
template <typename Dependencies, typename Field> struct AppendSurfaceDependency;
template <typename RowField, typename... StateFields, typename Field>
struct AppendSurfaceDependency<SurfaceConstraintDependencies<RowField, StateFields...>, Field> {
using Type = SurfaceConstraintDependencies<RowField, StateFields..., Field>;
};
template <typename Dependencies, typename InputQuantity, typename Bindings>
struct AppendPressureSurfaceInputDependency {
using Type =
typename AppendSurfaceDependency<Dependencies, SurfaceFieldForQuantityT<Bindings, InputQuantity>>::Type;
};
template <typename Dependencies, typename Bindings>
struct AppendPressureSurfaceInputDependency<Dependencies, eos::quantity::Pressure, Bindings> {
using Type = Dependencies;
};
template <typename Dependencies, typename Bindings, typename... InputQuantities>
struct AppendPressureSurfaceInputDependencies;
template <typename Dependencies, typename Bindings>
struct AppendPressureSurfaceInputDependencies<Dependencies, Bindings> {
using Type = Dependencies;
};
template <typename Dependencies, typename Bindings, typename FirstInput, typename... RemainingInputs>
struct AppendPressureSurfaceInputDependencies<Dependencies, Bindings, FirstInput, RemainingInputs...> {
using WithFirst = typename AppendPressureSurfaceInputDependency<Dependencies, FirstInput, Bindings>::Type;
using Type = typename AppendPressureSurfaceInputDependencies<WithFirst, Bindings, RemainingInputs...>::Type;
};
template <typename RelationType, typename Formulation> struct PressureSurfaceDependenciesForRelation;
template <typename OutputQuantity, typename... InputQuantities, typename Formulation>
struct PressureSurfaceDependenciesForRelation<eos::Relation<OutputQuantity, InputQuantities...>, Formulation> {
using InitialDependencies =
SurfaceConstraintDependencies<typename Formulation::CarrierField, typename Formulation::CarrierField>;
using Type = typename AppendPressureSurfaceInputDependencies<
InitialDependencies,
typename Formulation::StateBindings,
InputQuantities...>::Type;
};
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
struct PressureSurfaceCompilation {
using Matches =
MatchingPressureSurfaceRelations<typename EquationOfState::Relations, Formulation, EquationOfState>;
using Relation = typename UniquePressureSurfaceRelation<Matches::count, typename Matches::Tuple>::Type;
};
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
requires(PressureSurfaceCompilation<Formulation, EquationOfState>::Matches::count == 1)
struct CompiledPressureSurfaceConstraintType {
using Compilation = PressureSurfaceCompilation<Formulation, EquationOfState>;
using Relation = typename Compilation::Relation;
using Dependencies = typename PressureSurfaceDependenciesForRelation<Relation, Formulation>::Type;
using Type = CompiledPressureSurfaceConstraint<EquationOfState, Formulation, Relation, Dependencies>;
};
} // namespace detail
template <typename Formulation, typename EquationOfState>
concept PressureSurfaceCompilable =
SurfaceConstraintFormulationType<Formulation> && eos::EquationOfStateModel<EquationOfState> &&
(detail::PressureSurfaceCompilation<std::remove_cvref_t<Formulation>, std::remove_cvref_t<EquationOfState>>::
Matches::count == 1);
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
requires PressureSurfaceCompilable<Formulation, EquationOfState>
using CompiledPressureSurfaceConstraintT = typename detail::CompiledPressureSurfaceConstraintType<
std::remove_cvref_t<Formulation>,
std::remove_cvref_t<EquationOfState>>::Type;
template <
SurfaceConstraintFormulationType Formulation,
eos::EquationOfStateModel EquationOfState>
requires PressureSurfaceCompilable<
Formulation,
EquationOfState>
[[nodiscard]] CompiledPressureSurfaceConstraintT<
Formulation,
EquationOfState>
compilePressureSurfaceConstraint(
const ConstantPressureSurface condition,
const EquationOfState &equationOfState
) noexcept {
return CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>{condition, equationOfState};
}
} // namespace mean_field::surface

View File

@@ -0,0 +1,65 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
#include <type_traits>
export module mean_field:surface.constant;
export import :eos.quantities;
export namespace mean_field::surface {
struct PressureSurfaceDescriptor final {
double targetPressure;
};
/*
* The only physical surface prescription currently supported by
* MeanField. It says nothing about which thermodynamic variable appears
* in a nonlinear state vector; resolving pressure into that representation
* is an EOS responsibility.
*/
class ConstantPressureSurface final {
public:
using PhysicalQuantity = eos::quantity::Pressure;
using TargetValue = eos::PressureValue;
explicit ConstantPressureSurface(const TargetValue targetPressure) : m_targetPressure(targetPressure) {
if (!std::isfinite(targetPressure.value())) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be finite. Instead P = {} was provided.",
targetPressure.value()
)
);
}
if (targetPressure.value() < 0.0) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be non-negative. Instead P = {} was provided.",
targetPressure.value()
)
);
}
}
[[nodiscard]] TargetValue targetPressure() const noexcept {
return m_targetPressure;
}
[[nodiscard]] PressureSurfaceDescriptor descriptor() const noexcept {
return PressureSurfaceDescriptor{.targetPressure = m_targetPressure.value()};
}
private:
TargetValue m_targetPressure;
};
template <typename Candidate>
concept ConstantPressureSurfaceType = std::same_as<std::remove_cvref_t<Candidate>, ConstantPressureSurface>;
// Familiar physical terminology retained as a synonym, not as a second
// surface-condition type.
using Isobaric = ConstantPressureSurface;
} // namespace mean_field::surface

View File

@@ -0,0 +1,186 @@
module;
#include <array>
#include <concepts>
#include <cstddef>
#include <span>
#include <string_view>
#include <type_traits>
export module mean_field:surface.dependencies;
export import :eos.relations;
export import :field.registry;
export namespace mean_field::surface {
template <typename Candidate>
concept SurfaceFieldType = requires {
{ Candidate::name } -> std::convertible_to<std::string_view>;
} && (std::string_view{Candidate::name}.size() > 0);
class SurfaceFieldId final {
public:
explicit constexpr SurfaceFieldId(const std::string_view name) noexcept : m_name(name) {
}
[[nodiscard]] constexpr std::string_view name() const noexcept {
return m_name;
}
[[nodiscard]] friend constexpr bool operator==(
const SurfaceFieldId &,
const SurfaceFieldId &
) noexcept = default;
private:
std::string_view m_name;
};
template <SurfaceFieldType Field> inline constexpr SurfaceFieldId surfaceFieldId{std::string_view{Field::name}};
template <eos::ThermodynamicQuantityType ThermodynamicQuantity, SurfaceFieldType Field>
struct SurfaceStateBinding final {
using Quantity = ThermodynamicQuantity;
using FieldType = Field;
};
template <typename... Bindings> struct SurfaceStateBindings final { };
namespace detail {
template <typename... Types> struct SurfaceTypesAreUnique : std::true_type { };
template <typename First, typename... Remaining>
struct SurfaceTypesAreUnique<First, Remaining...>
: std::bool_constant<
(!std::same_as<First, Remaining> && ...) && SurfaceTypesAreUnique<Remaining...>::value> { };
template <typename Bindings> struct SurfaceBindingsAreValid : std::false_type { };
template <typename... Bindings>
struct SurfaceBindingsAreValid<SurfaceStateBindings<Bindings...>>
: std::bool_constant<
(sizeof...(Bindings) > 0) &&
(requires {
typename Bindings::Quantity;
typename Bindings::FieldType;
} && ...) &&
(eos::ThermodynamicQuantityType<
typename Bindings::Quantity> && ...) &&
(SurfaceFieldType<typename Bindings::FieldType> && ...) &&
SurfaceTypesAreUnique<
typename Bindings::Quantity...>::value> { };
template <typename Bindings, typename Quantity> struct SurfaceBindingCount;
template <typename Quantity, typename... Bindings>
struct SurfaceBindingCount<SurfaceStateBindings<Bindings...>, Quantity>
: std::integral_constant<
std::size_t,
(std::size_t{0} + ... +
(std::same_as<Quantity, typename Bindings::Quantity> ? std::size_t{1} : std::size_t{0}))> {
};
template <typename Bindings, typename Quantity> struct SurfaceFieldForQuantity;
template <typename Quantity, typename First, typename... Remaining>
struct SurfaceFieldForQuantity<SurfaceStateBindings<First, Remaining...>, Quantity>
: std::conditional_t<
std::same_as<Quantity, typename First::Quantity>,
std::type_identity<typename First::FieldType>,
SurfaceFieldForQuantity<SurfaceStateBindings<Remaining...>, Quantity>> { };
template <typename Candidate, std::size_t CarrierBindingCount>
struct CarrierFieldMatchesSurfaceBinding : std::false_type { };
template <typename Candidate>
struct CarrierFieldMatchesSurfaceBinding<Candidate, 1>
: std::bool_constant<std::same_as<
typename SurfaceFieldForQuantity<
typename Candidate::StateBindings,
typename Candidate::CarrierQuantity>::type,
typename Candidate::CarrierField>> { };
template <
typename Candidate,
bool BindingsAreValid = SurfaceBindingsAreValid<typename Candidate::StateBindings>::value>
struct FormulationBindingsMatchCarrier : std::false_type { };
template <typename Candidate>
struct FormulationBindingsMatchCarrier<Candidate, true>
: CarrierFieldMatchesSurfaceBinding<
Candidate,
SurfaceBindingCount<
typename Candidate::StateBindings,
typename Candidate::CarrierQuantity>::value> { };
template <typename Candidate, typename = void>
struct IsSurfaceConstraintFormulation : std::false_type { };
template <typename Candidate>
struct IsSurfaceConstraintFormulation<
Candidate,
std::void_t<
typename Candidate::CarrierQuantity,
typename Candidate::CarrierField,
typename Candidate::StateBindings>>
: std::bool_constant<
eos::ThermodynamicQuantityType<typename Candidate::CarrierQuantity> &&
SurfaceFieldType<typename Candidate::CarrierField> &&
FormulationBindingsMatchCarrier<Candidate>::value> { };
} // namespace detail
template <typename Candidate>
concept ValidSurfaceStateBindings = detail::SurfaceBindingsAreValid<std::remove_cv_t<Candidate>>::value;
template <ValidSurfaceStateBindings Bindings, typename Quantity>
inline constexpr std::size_t surfaceBindingCount = detail::SurfaceBindingCount<Bindings, Quantity>::value;
template <ValidSurfaceStateBindings Bindings, typename Quantity>
requires(surfaceBindingCount<Bindings, Quantity> == 1)
using SurfaceFieldForQuantityT = typename detail::SurfaceFieldForQuantity<Bindings, Quantity>::type;
template <
eos::ThermodynamicQuantityType CarrierThermodynamicQuantity,
SurfaceFieldType CarrierFieldType,
ValidSurfaceStateBindings Bindings>
requires(
surfaceBindingCount<Bindings, CarrierThermodynamicQuantity> == 1 &&
std::same_as<SurfaceFieldForQuantityT<Bindings, CarrierThermodynamicQuantity>, CarrierFieldType>
)
struct SurfaceConstraintFormulation final {
using CarrierQuantity = CarrierThermodynamicQuantity;
using CarrierField = CarrierFieldType;
using StateBindings = Bindings;
};
using BarotropicSurfaceFormulation = SurfaceConstraintFormulation<
eos::quantity::SpecificEnthalpy,
field::Enthalpy,
SurfaceStateBindings<SurfaceStateBinding<eos::quantity::SpecificEnthalpy, field::Enthalpy>>>;
template <typename Candidate>
concept SurfaceConstraintFormulationType =
detail::IsSurfaceConstraintFormulation<std::remove_cv_t<Candidate>>::value;
struct RuntimeSurfaceConstraintDependencies final {
SurfaceFieldId residualRowField;
std::span<const SurfaceFieldId> stateFields;
};
template <SurfaceFieldType ResidualField, SurfaceFieldType... StateFields>
struct SurfaceConstraintDependencies final {
using RowField = ResidualField;
using StateFieldTypes = field::TypeList<StateFields...>;
inline static constexpr std::array<SurfaceFieldId, sizeof...(StateFields)> runtimeStateFields{
surfaceFieldId<StateFields>...
};
[[nodiscard]] static constexpr RuntimeSurfaceConstraintDependencies runtimeDescription() noexcept {
return RuntimeSurfaceConstraintDependencies{
.residualRowField = surfaceFieldId<ResidualField>,
.stateFields = std::span<const SurfaceFieldId>{runtimeStateFields}
};
}
};
} // namespace mean_field::surface

View File

@@ -1,64 +0,0 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
export module mean_field:surface.isobaric;
export import :surface.base;
export namespace mean_field::surface {
class Isobaric final : public SurfaceBase {
public:
explicit Isobaric(const double targetPressure = 0.0) : m_targetPressure(targetPressure) {
validateTargetPressure();
}
[[nodiscard]] double targetPressure() const noexcept {
return m_targetPressure;
}
[[nodiscard]] ResolvedSurfaceCondition
resolve(const mean_field::eos::EquationOfState &equationOfState) const override {
return ResolvedSurfaceCondition{resolveTargetEnthalpy(equationOfState)};
}
void validate(const mean_field::eos::EquationOfState &equationOfState) const override {
static_cast<void>(resolveTargetEnthalpy(equationOfState));
}
private:
[[nodiscard]] double resolveTargetEnthalpy(const mean_field::eos::EquationOfState &equationOfState) const {
validateTargetPressure();
const double targetEnthalpy = equationOfState.enthalpy_from_pressure(m_targetPressure);
if (!std::isfinite(targetEnthalpy) || targetEnthalpy < 0.0) {
throw std::domain_error(
std::format(
"The equation of state resolved the isobaric "
"target P = {} to the invalid enthalpy h = {}.",
m_targetPressure, targetEnthalpy
)
);
}
return targetEnthalpy;
}
void validateTargetPressure() const {
if (!std::isfinite(m_targetPressure) || m_targetPressure < 0.0) {
throw std::invalid_argument(
std::format(
"The target surface pressure must be finite and "
"non-negative. Instead P = {} was provided.",
m_targetPressure
)
);
}
}
double m_targetPressure;
};
} // namespace mean_field::surface

View File

@@ -1,53 +0,0 @@
module;
#include <cmath>
#include <stdexcept>
export module mean_field:surface.base;
export import :eos.base;
export namespace mean_field::surface {
struct ResolvedSurfaceCondition final {
double targetEnthalpy{0.0};
explicit ResolvedSurfaceCondition(const double requestedTargetEnthalpy)
: targetEnthalpy(requestedTargetEnthalpy) {
if (!std::isfinite(targetEnthalpy) || targetEnthalpy < 0.0) {
throw std::invalid_argument(
"A resolved surface enthalpy must be finite and "
"non-negative."
);
}
}
[[nodiscard]] double residual(const double enthalpy) const {
if (!std::isfinite(enthalpy)) {
throw std::invalid_argument("A surface enthalpy value must be finite.");
}
return enthalpy - targetEnthalpy;
}
[[nodiscard]] static double jacobianAction(const double enthalpyVariation) {
if (!std::isfinite(enthalpyVariation)) {
throw std::invalid_argument("A surface enthalpy variation must be finite.");
}
return enthalpyVariation;
}
};
class SurfaceBase {
public:
virtual ~SurfaceBase() = default;
[[nodiscard]] virtual ResolvedSurfaceCondition
resolve(const mean_field::eos::EquationOfState &equationOfState) const = 0;
virtual void validate(const mean_field::eos::EquationOfState &equationOfState) const = 0;
protected:
SurfaceBase() = default;
};
} // namespace mean_field::surface