#include #include #include #include #include #include #include #include #include import mean_field; import test_helpers; namespace { namespace eos = mean_field::eos; class LinearPressureEquationOfState final { public: using Relations = eos::RelationCatalog; [[nodiscard]] constexpr eos::PressureValue evaluate( eos::PressureFromDensity, const eos::DensityValue density ) const noexcept { return eos::PressureValue{2.0 * density.value() + 0.5}; } }; struct DensityAlias final : eos::ThermodynamicQuantity { static constexpr std::string_view identifier = "density"; }; class AmbiguouslyIdentifiedEquationOfState final { public: using Relations = eos::RelationCatalog>; [[nodiscard]] constexpr eos::QuantityValue evaluate( eos::Relation< DensityAlias, eos::quantity::Density>, const eos::DensityValue density ) const noexcept { return eos::QuantityValue{density.value()}; } }; [[nodiscard]] std::expected< eos::PressureValue, eos::EvaluationError> pressureAtDensity( const eos::EquationOfStateView equationOfState, const eos::DensityValue density ) { return equationOfState.tryEvaluate(density); } [[nodiscard]] const eos::RuntimeRelationDescriptor *findRelation( const eos::EquationOfStateView equationOfState, const eos::ThermodynamicQuantityId output, const eos::ThermodynamicQuantityId input ) { for (const eos::RuntimeRelationDescriptor &relation : equationOfState.relations()) { if (relation.outputQuantity == output && relation.inputQuantities.size() == 1 && relation.inputQuantities[0] == input) { return std::addressof(relation); } } return nullptr; } } // namespace TEST_CASE( "Runtime EOS View Generates The Polytropic Relation Catalog", tags::equation_of_state_runtime_contract ) { STATIC_CHECK(eos::RuntimeEquationOfStateModel); STATIC_CHECK(eos::RuntimeEquationOfStateModel); STATIC_CHECK(eos::EquationOfStateModel); STATIC_CHECK_FALSE(eos::RuntimeEquationOfStateModel); STATIC_CHECK(std::is_trivially_copyable_v); STATIC_CHECK_FALSE(std::constructible_from); const eos::Polytrope equationOfState(3.0, 0.25); const eos::Polytrope secondEquationOfState(1.5, 0.73); const eos::EquationOfStateView view{equationOfState}; const eos::EquationOfStateView secondView{secondEquationOfState}; REQUIRE(view.relations().size() == eos::Polytrope::Relations::size); CHECK(view.relations().data() == secondView.relations().data()); CHECK(eos::thermodynamicQuantityId.name() == "density"); CHECK(eos::thermodynamicQuantityId.name() == "pressure"); CHECK(eos::thermodynamicQuantityId.name() == "specific_enthalpy"); const eos::RuntimeRelationDescriptor *pressureFromDensity = findRelation( view, eos::thermodynamicQuantityId, eos::thermodynamicQuantityId ); REQUIRE(pressureFromDensity != nullptr); CHECK(pressureFromDensity->hasPartialDerivative(0)); const eos::RuntimeRelationDescriptor *specificEnthalpyFromPressure = findRelation( view, eos::thermodynamicQuantityId, eos::thermodynamicQuantityId ); REQUIRE(specificEnthalpyFromPressure != nullptr); CHECK_FALSE(specificEnthalpyFromPressure->hasPartialDerivative(0)); const eos::RuntimeRelationDescriptor *pressureFromSpecificEnthalpy = findRelation( view, eos::thermodynamicQuantityId, eos::thermodynamicQuantityId ); REQUIRE(pressureFromSpecificEnthalpy != nullptr); CHECK(pressureFromSpecificEnthalpy->hasPartialDerivative(0)); const eos::RuntimeRelationDescriptor *specificEnthalpyFromDensity = findRelation( view, eos::thermodynamicQuantityId, eos::thermodynamicQuantityId ); REQUIRE(specificEnthalpyFromDensity != nullptr); CHECK_FALSE(specificEnthalpyFromDensity->hasPartialDerivative(0)); const eos::RuntimeRelationDescriptor *densityFromSpecificEnthalpy = findRelation( view, eos::thermodynamicQuantityId, eos::thermodynamicQuantityId ); REQUIRE(densityFromSpecificEnthalpy != nullptr); CHECK(densityFromSpecificEnthalpy->hasPartialDerivative(0)); } TEST_CASE( "Runtime EOS View Matches Typed Polytropic Evaluation", tags::equation_of_state_runtime_compatibility ) { const eos::Polytrope equationOfState(3.0, 0.25); const eos::EquationOfStateView view{equationOfState}; const eos::DensityValue density{0.7}; const eos::SpecificEnthalpyValue specificEnthalpy{0.9}; const eos::PressureValue pressure{0.04}; const auto runtimePressureFromDensity = view.tryEvaluate(density); const auto runtimePressureFromSpecificEnthalpy = view.tryEvaluate(specificEnthalpy); const auto runtimeSpecificEnthalpyFromDensity = view.tryEvaluate(density); const auto runtimeSpecificEnthalpyFromPressure = view.tryEvaluate(pressure); const auto runtimeDensityFromSpecificEnthalpy = view.tryEvaluate(specificEnthalpy); REQUIRE(runtimePressureFromDensity.has_value()); REQUIRE(runtimePressureFromSpecificEnthalpy.has_value()); REQUIRE(runtimeSpecificEnthalpyFromDensity.has_value()); REQUIRE(runtimeSpecificEnthalpyFromPressure.has_value()); REQUIRE(runtimeDensityFromSpecificEnthalpy.has_value()); CHECK( runtimePressureFromDensity->value() == eos::evaluate(equationOfState, density).value() ); CHECK( runtimePressureFromSpecificEnthalpy->value() == eos::evaluate(equationOfState, specificEnthalpy).value() ); CHECK( runtimeSpecificEnthalpyFromDensity->value() == eos::evaluate(equationOfState, density).value() ); CHECK( runtimeSpecificEnthalpyFromPressure->value() == eos::evaluate(equationOfState, pressure).value() ); CHECK( runtimeDensityFromSpecificEnthalpy->value() == eos::evaluate(equationOfState, specificEnthalpy).value() ); const std::array runtimeDensityInput{ eos::RuntimeQuantityValue{eos::thermodynamicQuantityId, density.value()} }; const auto erasedPressureFromDensity = view.tryEvaluate( eos::thermodynamicQuantityId, std::span{runtimeDensityInput} ); REQUIRE(erasedPressureFromDensity.has_value()); CHECK(erasedPressureFromDensity->quantity == eos::thermodynamicQuantityId); CHECK(erasedPressureFromDensity->value == runtimePressureFromDensity->value()); const auto runtimePressureDerivative = view.tryPartialDerivative(specificEnthalpy); const auto runtimeDensityDerivative = view.tryPartialDerivative(specificEnthalpy); const auto runtimePressureDensityDerivative = view.tryPartialDerivative(density); REQUIRE(runtimePressureDerivative.has_value()); REQUIRE(runtimeDensityDerivative.has_value()); REQUIRE(runtimePressureDensityDerivative.has_value()); CHECK( runtimePressureDerivative->value() == eos::partialDerivative( equationOfState, specificEnthalpy ) .value() ); CHECK( runtimeDensityDerivative->value() == eos::partialDerivative( equationOfState, specificEnthalpy ) .value() ); CHECK( runtimePressureDensityDerivative->value() == eos::partialDerivative(equationOfState, density).value() ); const auto erasedPressureDensityDerivative = view.tryPartialDerivative( eos::thermodynamicQuantityId, eos::thermodynamicQuantityId, std::span{runtimeDensityInput} ); REQUIRE(erasedPressureDensityDerivative.has_value()); CHECK(*erasedPressureDensityDerivative == runtimePressureDensityDerivative->value()); } TEST_CASE( "Runtime EOS View Reports Unsupported And Invalid Requests", tags::equation_of_state_runtime_contract ) { const eos::Polytrope equationOfState(3.0, 0.25); const eos::EquationOfStateView view{equationOfState}; constexpr eos::ThermodynamicQuantityId temperature{"temperature"}; const std::array densityInput{eos::RuntimeQuantityValue{eos::thermodynamicQuantityId, 0.7}}; const std::array pressureInput{ eos::RuntimeQuantityValue{eos::thermodynamicQuantityId, 0.04} }; const std::array noInputs{}; const auto unsupportedOutput = view.tryEvaluate(temperature, std::span{densityInput}); REQUIRE_FALSE(unsupportedOutput.has_value()); CHECK(unsupportedOutput.error().code() == eos::EvaluationErrorCode::unsupported_relation); const auto wrongInputCount = view.tryEvaluate( eos::thermodynamicQuantityId, std::span{noInputs} ); REQUIRE_FALSE(wrongInputCount.has_value()); CHECK(wrongInputCount.error().code() == eos::EvaluationErrorCode::wrong_input_count); const auto wrongInputQuantity = view.tryEvaluate( eos::thermodynamicQuantityId, std::span{pressureInput} ); REQUIRE_FALSE(wrongInputQuantity.has_value()); CHECK(wrongInputQuantity.error().code() == eos::EvaluationErrorCode::wrong_input_quantity); const auto unsupportedDerivative = view.tryPartialDerivative( eos::thermodynamicQuantityId, eos::thermodynamicQuantityId, std::span{pressureInput} ); REQUIRE_FALSE(unsupportedDerivative.has_value()); CHECK(unsupportedDerivative.error().code() == eos::EvaluationErrorCode::unsupported_derivative); const auto invalidDensity = view.tryEvaluate(eos::DensityValue{-0.1}); REQUIRE_FALSE(invalidDensity.has_value()); CHECK(invalidDensity.error().code() == eos::EvaluationErrorCode::outside_domain); const auto nonfiniteDensity = view.tryEvaluate(eos::DensityValue{std::numeric_limits::quiet_NaN()}); REQUIRE_FALSE(nonfiniteDensity.has_value()); CHECK(nonfiniteDensity.error().code() == eos::EvaluationErrorCode::nonfinite_input); } TEST_CASE( "One Runtime EOS Function Accepts Heterogeneous Concrete Models", tags::equation_of_state_runtime_compatibility ) { const eos::Polytrope polytrope(3.0, 0.25); const LinearPressureEquationOfState linearEquationOfState; const std::array views{eos::EquationOfStateView{polytrope}, eos::EquationOfStateView{linearEquationOfState}}; const eos::DensityValue density{0.7}; const auto polytropicPressure = pressureAtDensity(views[0], density); const auto linearPressure = pressureAtDensity(views[1], density); REQUIRE(polytropicPressure.has_value()); REQUIRE(linearPressure.has_value()); CHECK(polytropicPressure->value() == eos::evaluate(polytrope, density).value()); CHECK(linearPressure->value() == 1.9); } TEST_CASE( "Runtime EOS View Remains Valid When Stable Ownership Moves", tags::equation_of_state_runtime_contract ) { auto owner = std::make_unique(3.0, 0.25); const eos::EquationOfStateView view{*owner}; auto movedOwner = std::move(owner); const auto pressure = view.tryEvaluate(eos::DensityValue{0.7}); REQUIRE(movedOwner != nullptr); REQUIRE(pressure.has_value()); CHECK(pressure->value() == eos::evaluate(*movedOwner, eos::DensityValue{0.7}).value()); }