#include #include #include #include #include #include #include import mean_field; import test_helpers; namespace { namespace eos = mean_field::eos; namespace field = mean_field::field; namespace surface = mean_field::surface; struct Entropy final : eos::ThermodynamicQuantity { static constexpr std::string_view identifier = "entropy"; }; struct ElectronFraction final : eos::ThermodynamicQuantity { static constexpr std::string_view identifier = "electron_fraction"; }; struct EntropyField final { static constexpr std::string_view name = "entropy"; }; struct ElectronFractionField final { static constexpr std::string_view name = "electron_fraction"; }; using SpecificEnthalpyFromPressureEntropyAndElectronFraction = eos::Relation; class GeneralStellarMatterEquationOfState final { public: using Relations = eos::RelationCatalog; [[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate( SpecificEnthalpyFromPressureEntropyAndElectronFraction, const eos::PressureValue pressure, const eos::QuantityValue entropy, const eos::QuantityValue electronFraction ) const noexcept { return eos::SpecificEnthalpyValue{ 2.0 * pressure.value() + 3.0 * entropy.value() + 5.0 * electronFraction.value() }; } [[nodiscard]] constexpr eos::PartialDerivative< eos::quantity::SpecificEnthalpy, Entropy> partialDerivative( SpecificEnthalpyFromPressureEntropyAndElectronFraction, eos::WithRespectTo, eos::PressureValue, eos::QuantityValue, eos::QuantityValue ) const noexcept { return eos::PartialDerivative{3.0}; } [[nodiscard]] constexpr eos::PartialDerivative< eos::quantity::SpecificEnthalpy, ElectronFraction> partialDerivative( SpecificEnthalpyFromPressureEntropyAndElectronFraction, eos::WithRespectTo, eos::PressureValue, eos::QuantityValue, eos::QuantityValue ) const noexcept { return eos::PartialDerivative{5.0}; } }; class GeneralEquationOfStateWithoutElectronFractionPartial final { public: using Relations = eos::RelationCatalog; [[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate( SpecificEnthalpyFromPressureEntropyAndElectronFraction, const eos::PressureValue pressure, const eos::QuantityValue entropy, const eos::QuantityValue electronFraction ) const noexcept { return eos::SpecificEnthalpyValue{pressure.value() + entropy.value() + electronFraction.value()}; } [[nodiscard]] constexpr eos::PartialDerivative< eos::quantity::SpecificEnthalpy, Entropy> partialDerivative( SpecificEnthalpyFromPressureEntropyAndElectronFraction, eos::WithRespectTo, eos::PressureValue, eos::QuantityValue, eos::QuantityValue ) const noexcept { return eos::PartialDerivative{1.0}; } }; using SpecificEnthalpyFromPressureAndEntropy = eos::Relation; class AmbiguousSurfaceEquationOfState final { public: using Relations = eos::RelationCatalog; [[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate( eos::SpecificEnthalpyFromPressure, const eos::PressureValue pressure ) const noexcept { return eos::SpecificEnthalpyValue{pressure.value()}; } [[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate( SpecificEnthalpyFromPressureAndEntropy, const eos::PressureValue pressure, const eos::QuantityValue entropy ) const noexcept { return eos::SpecificEnthalpyValue{pressure.value() + entropy.value()}; } [[nodiscard]] constexpr eos::PartialDerivative< eos::quantity::SpecificEnthalpy, Entropy> partialDerivative( SpecificEnthalpyFromPressureAndEntropy, eos::WithRespectTo, eos::PressureValue, eos::QuantityValue ) const noexcept { return eos::PartialDerivative{1.0}; } }; class DensityOnlyEquationOfState final { public: using Relations = eos::RelationCatalog; [[nodiscard]] constexpr eos::PressureValue evaluate( eos::PressureFromDensity, const eos::DensityValue density ) const noexcept { return eos::PressureValue{density.value()}; } }; using GeneralSurfaceFormulation = surface::SurfaceConstraintFormulation< eos::quantity::SpecificEnthalpy, field::Enthalpy, surface::SurfaceStateBindings< surface::SurfaceStateBinding, surface::SurfaceStateBinding, surface::SurfaceStateBinding>>; struct PolytropicSurfaceState final { double specificEnthalpy; [[nodiscard]] eos::SpecificEnthalpyValue value(eos::quantity::SpecificEnthalpy) const noexcept { return eos::SpecificEnthalpyValue{specificEnthalpy}; } }; struct GeneralSurfaceState final { double specificEnthalpy; double entropy; double electronFraction; [[nodiscard]] eos::SpecificEnthalpyValue value(eos::quantity::SpecificEnthalpy) const noexcept { return eos::SpecificEnthalpyValue{specificEnthalpy}; } [[nodiscard]] eos::QuantityValue value(Entropy) const noexcept { return eos::QuantityValue{entropy}; } [[nodiscard]] eos::QuantityValue value(ElectronFraction) const noexcept { return eos::QuantityValue{electronFraction}; } }; template concept HasTargetEnthalpy = requires(const Candidate &candidate) { candidate.targetEnthalpy; }; } // namespace TEST_CASE( "Constant Pressure Surface Prescribes Only A Pressure Quantity", tags::surface_prescription_type_contract ) { STATIC_CHECK(std::same_as); STATIC_CHECK(std::constructible_from); STATIC_CHECK_FALSE(std::constructible_from); STATIC_CHECK_FALSE(std::constructible_from); STATIC_CHECK(std::same_as); STATIC_CHECK(std::is_trivially_copyable_v); STATIC_CHECK(std::is_trivially_copyable_v); STATIC_CHECK(std::is_trivially_copyable_v); const surface::ConstantPressureSurface pressureSurface{eos::PressureValue{0.03125}}; CHECK(pressureSurface.targetPressure() == eos::PressureValue{0.03125}); CHECK(pressureSurface.descriptor().targetPressure == 0.03125); CHECK_THROWS_AS(surface::ConstantPressureSurface{eos::PressureValue{-0.1}}, std::invalid_argument); CHECK_THROWS_AS( surface::ConstantPressureSurface{eos::PressureValue{std::numeric_limits::infinity()}}, std::invalid_argument ); } TEST_CASE( "Polytropic EOS Resolves Constant Surface Pressure Through Its Enthalpy Relation", tags::surface_constraint_compilation ) { using Formulation = surface::BarotropicSurfaceFormulation; STATIC_CHECK(surface::PressureSurfaceCompilable); STATIC_CHECK_FALSE(surface::PressureSurfaceCompilable); const eos::Polytrope equationOfState(3.0, 0.25); const surface::ConstantPressureSurface pressureSurface{eos::PressureValue{0.03125}}; const auto constraint = surface::compilePressureSurfaceConstraint(pressureSurface, equationOfState); using Constraint = std::remove_cvref_t; using Dependencies = Constraint::SurfaceDependencies; STATIC_CHECK(std::is_trivially_copyable_v); STATIC_CHECK(std::same_as); STATIC_CHECK(std::same_as); STATIC_CHECK(std::same_as>); STATIC_CHECK_FALSE(HasTargetEnthalpy); const double requiredSpecificEnthalpy = eos::evaluate(equationOfState, pressureSurface.targetPressure()).value(); const PolytropicSurfaceState state{requiredSpecificEnthalpy}; const PolytropicSurfaceState variation{-0.19}; CHECK(constraint.targetPressure() == eos::PressureValue{0.03125}); CHECK(constraint.residual(state) == 0.0); CHECK(constraint.jacobianAction(state, variation) == -0.19); const auto runtimeDependencies = constraint.runtimeDependencies(); REQUIRE(runtimeDependencies.stateFields.size() == 1); CHECK(runtimeDependencies.residualRowField == surface::surfaceFieldId); CHECK(runtimeDependencies.stateFields[0] == surface::surfaceFieldId); } TEST_CASE( "General EOS Resolves Constant Surface Pressure With Local Composition", tags::surface_constraint_compilation ) { STATIC_CHECK(surface::PressureSurfaceCompilable); STATIC_CHECK_FALSE( surface::PressureSurfaceCompilable ); STATIC_CHECK_FALSE( surface::PressureSurfaceCompilable< GeneralSurfaceFormulation, GeneralEquationOfStateWithoutElectronFractionPartial> ); STATIC_CHECK_FALSE(surface::PressureSurfaceCompilable); const GeneralStellarMatterEquationOfState equationOfState; const surface::ConstantPressureSurface pressureSurface{eos::PressureValue{0.4}}; const auto constraint = surface::compilePressureSurfaceConstraint(pressureSurface, equationOfState); using Constraint = std::remove_cvref_t; using Dependencies = Constraint::SurfaceDependencies; STATIC_CHECK(std::same_as); STATIC_CHECK( std::same_as< Dependencies::StateFieldTypes, field::TypeList> ); constexpr GeneralSurfaceState firstSurface{ .specificEnthalpy = 2.0 * 0.4 + 3.0 * 0.2 + 5.0 * 0.1, .entropy = 0.2, .electronFraction = 0.1 }; constexpr GeneralSurfaceState secondSurface{ .specificEnthalpy = 2.0 * 0.4 + 3.0 * 0.3 + 5.0 * 0.1, .entropy = 0.3, .electronFraction = 0.1 }; CHECK(firstSurface.specificEnthalpy != secondSurface.specificEnthalpy); CHECK(constraint.residual(firstSurface) == 0.0); CHECK(constraint.residual(secondSurface) == 0.0); const auto runtimeDependencies = constraint.runtimeDependencies(); REQUIRE(runtimeDependencies.stateFields.size() == 3); CHECK(runtimeDependencies.stateFields[0] == surface::surfaceFieldId); CHECK(runtimeDependencies.stateFields[1] == surface::surfaceFieldId); CHECK(runtimeDependencies.stateFields[2] == surface::surfaceFieldId); } TEST_CASE( "General EOS Pressure Surface Jacobian Includes Every Local State Dependency", tags::surface_constraint_jacobian ) { const GeneralStellarMatterEquationOfState equationOfState; const surface::ConstantPressureSurface pressureSurface{eos::PressureValue{0.4}}; const auto constraint = surface::compilePressureSurfaceConstraint(pressureSurface, equationOfState); constexpr GeneralSurfaceState state{.specificEnthalpy = 1.7, .entropy = 0.2, .electronFraction = 0.1}; constexpr GeneralSurfaceState variation{.specificEnthalpy = 0.7, .entropy = -0.2, .electronFraction = 0.05}; constexpr double step = 1.0e-7; const GeneralSurfaceState forward{ .specificEnthalpy = state.specificEnthalpy + step * variation.specificEnthalpy, .entropy = state.entropy + step * variation.entropy, .electronFraction = state.electronFraction + step * variation.electronFraction }; const GeneralSurfaceState backward{ .specificEnthalpy = state.specificEnthalpy - step * variation.specificEnthalpy, .entropy = state.entropy - step * variation.entropy, .electronFraction = state.electronFraction - step * variation.electronFraction }; const double finiteDifference = (constraint.residual(forward) - constraint.residual(backward)) / (2.0 * step); const double jacobianAction = constraint.jacobianAction(state, variation); CHECK(jacobianAction == variation.specificEnthalpy - 3.0 * variation.entropy - 5.0 * variation.electronFraction); CHECK_THAT(finiteDifference, Catch::Matchers::WithinAbs(jacobianAction, 2.0e-9)); }