#include #include #include #include #include #include #include import mean_field; import test_helpers; namespace { namespace eos = mean_field::eos; template concept HasAnyUnaryEquationOfStateConversion = requires(const Candidate &candidate, const double value) { candidate.pressure_from_density(value); } || requires(const Candidate &candidate, const double value) { candidate.pressure_from_enthalpy(value); } || requires(const Candidate &candidate, const double value) { candidate.enthalpy_from_density(value); } || requires(const Candidate &candidate, const double value) { candidate.enthalpy_from_pressure(value); } || requires(const Candidate &candidate, const double value) { candidate.density_from_enthalpy(value); } || requires(const Candidate &candidate, const double value) { candidate.density_derivative_from_enthalpy(value); } || requires(const Candidate &candidate, const double value) { candidate.pressure_derivative_from_enthalpy(value); } || requires(const Candidate &candidate, const double value) { candidate.pressure_derivative_from_density(value); }; } // namespace TEST_CASE( "Polytropic EOS Declares Its Thermodynamic Relation Contract", tags::polytropic_eos_relation_contract ) { using Polytrope = eos::Polytrope; STATIC_CHECK(eos::EquationOfStateModel); STATIC_CHECK_FALSE(std::is_polymorphic_v); STATIC_CHECK_FALSE(HasAnyUnaryEquationOfStateConversion); STATIC_CHECK(Polytrope::Relations::size == 5); STATIC_CHECK(eos::SupportsRelation); STATIC_CHECK(eos::SupportsRelation); STATIC_CHECK(eos::SupportsRelation); STATIC_CHECK(eos::SupportsRelation); STATIC_CHECK(eos::SupportsRelation); STATIC_CHECK(eos::SupportsPartialDerivative); STATIC_CHECK( eos::SupportsPartialDerivative ); STATIC_CHECK( eos::SupportsPartialDerivative ); STATIC_CHECK_FALSE( eos::SupportsPartialDerivative ); STATIC_CHECK_FALSE( eos::SupportsPartialDerivative ); } TEST_CASE( "Polytropic EOS Typed Relations Preserve Analytic Values", tags::polytropic_eos_characterization ) { constexpr std::array polytropicIndices{1.0, 1.5, 3.0}; constexpr std::array polytropicConstants{0.25, 0.73}; constexpr std::array densities{0.0, 1.0e-6, 0.2, 2.0}; constexpr std::array specificEnthalpies{-0.3, 0.0, 0.2, 1.7}; constexpr std::array pressures{0.0, 1.0e-8, 0.3, 4.0}; for (const double polytropicIndex : polytropicIndices) { for (const double polytropicConstant : polytropicConstants) { const eos::Polytrope equationOfState(polytropicIndex, polytropicConstant); for (const double density : densities) { CAPTURE(polytropicIndex, polytropicConstant, density); const double expectedPressure = polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex); const double expectedSpecificEnthalpy = (polytropicIndex + 1.0) * polytropicConstant * std::pow(density, 1.0 / polytropicIndex); CHECK( eos::evaluate(equationOfState, eos::DensityValue{density}).value() == expectedPressure ); CHECK( eos::evaluate(equationOfState, eos::DensityValue{density}) .value() == expectedSpecificEnthalpy ); CHECK_THAT( (eos::partialDerivative( equationOfState, eos::DensityValue{density} ) .value()), Catch::Matchers::WithinRel( density == 0.0 ? 0.0 : expectedSpecificEnthalpy / polytropicIndex, 2.0e-15 ) ); } for (const double specificEnthalpy : specificEnthalpies) { CAPTURE(polytropicIndex, polytropicConstant, specificEnthalpy); const double expectedDensity = specificEnthalpy <= 0.0 ? 0.0 : std::pow(specificEnthalpy / ((polytropicIndex + 1.0) * polytropicConstant), polytropicIndex); CHECK( eos::evaluate(equationOfState, eos::SpecificEnthalpyValue{specificEnthalpy}) .value() == expectedDensity ); CHECK( eos::evaluate( equationOfState, eos::SpecificEnthalpyValue{specificEnthalpy} ) .value() == (specificEnthalpy <= 0.0 ? 0.0 : expectedDensity * specificEnthalpy / (polytropicIndex + 1.0)) ); CHECK( (eos::partialDerivative( equationOfState, eos::SpecificEnthalpyValue{specificEnthalpy} ) .value() == expectedDensity) ); } for (const double pressure : pressures) { CAPTURE(polytropicIndex, polytropicConstant, pressure); const double indexPlusOne = polytropicIndex + 1.0; const double expectedSpecificEnthalpy = indexPlusOne * std::pow(polytropicConstant, polytropicIndex / indexPlusOne) * std::pow(pressure, 1.0 / indexPlusOne); CHECK( eos::evaluate(equationOfState, eos::PressureValue{pressure}) .value() == expectedSpecificEnthalpy ); } } } } TEST_CASE( "Typed Polytropic EOS Preserves Domain And Exterior Semantics", tags::polytropic_eos_relation_contract ) { const eos::Polytrope equationOfState(3.0, 0.75); try { static_cast(eos::evaluate(equationOfState, eos::DensityValue{-0.1})); FAIL("A negative density must be rejected."); } catch (const eos::EvaluationError &error) { CHECK(error.code() == eos::EvaluationErrorCode::outside_domain); } try { static_cast(eos::evaluate( equationOfState, eos::PressureValue{std::numeric_limits::quiet_NaN()} )); FAIL("A nonfinite pressure must be rejected."); } catch (const eos::EvaluationError &error) { CHECK(error.code() == eos::EvaluationErrorCode::nonfinite_input); } constexpr double exteriorSpecificEnthalpy = -0.3; CHECK( eos::evaluate(equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy}) .value() == 0.0 ); CHECK( eos::evaluate(equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy}) .value() == 0.0 ); CHECK( (eos::partialDerivative( equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy} ) .value() == 0.0) ); CHECK( (eos::partialDerivative( equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy} ) .value() == 0.0) ); }