// Ported from MeanField tests/physics/polytropic_eos_relations.cpp. #include #include #include #include #include #include #include "serif/eos/evaluation.hpp" #include "serif/eos/exceptions.hpp" #include "serif/eos/models/polytropic.hpp" #include "serif/tests/test_tags.hpp" namespace eos = serif::eos; namespace dimensions = serif::dimensions; TEST_CASE( "Polytropic EOS Typed Relations Preserve Analytic Values", tags::unit & tags::eos ) { constexpr std::array polytropic_indices{1.0, 1.5, 3.0}; constexpr std::array polytropic_constants{0.25, 0.73}; constexpr std::array densities{0.0, 1.0e-6, 0.2, 2.0}; constexpr std::array specific_enthalpies{-0.3, 0.0, 0.2, 1.7}; constexpr std::array pressures{0.0, 1.0e-8, 0.3, 4.0}; for (const double polytropic_index : polytropic_indices) { for (const double polytropic_constant : polytropic_constants) { const eos::models::Polytrope equation_of_state(polytropic_index, polytropic_constant); for (const double density : densities) { CAPTURE(polytropic_index, polytropic_constant, density); const double expected_pressure = polytropic_constant * std::pow(density, 1.0 + 1.0 / polytropic_index); const double expected_specific_enthalpy = (polytropic_index + 1.0) * polytropic_constant * std::pow(density, 1.0 / polytropic_index); CHECK( eos::evaluate(equation_of_state, dimensions::DensityValue{density}).value() == expected_pressure ); CHECK( eos::evaluate(equation_of_state, dimensions::DensityValue{density}) .value() == expected_specific_enthalpy ); CHECK_THAT( (eos::partial_derivative( equation_of_state, dimensions::DensityValue{density} ) .value()), Catch::Matchers::WithinRel( density == 0.0 ? 0.0 : expected_specific_enthalpy / polytropic_index, 2.0e-15 ) ); } for (const double specific_enthalpy : specific_enthalpies) { CAPTURE(polytropic_index, polytropic_constant, specific_enthalpy); const double expected_density = specific_enthalpy <= 0.0 ? 0.0 : std::pow( specific_enthalpy / ((polytropic_index + 1.0) * polytropic_constant), polytropic_index ); CHECK( eos::evaluate( equation_of_state, dimensions::SpecificEnthalpyValue{specific_enthalpy} ) .value() == expected_density ); CHECK( eos::evaluate( equation_of_state, dimensions::SpecificEnthalpyValue{specific_enthalpy} ) .value() == (specific_enthalpy <= 0.0 ? 0.0 : expected_density * specific_enthalpy / (polytropic_index + 1.0)) ); CHECK( (eos::partial_derivative( equation_of_state, dimensions::SpecificEnthalpyValue{specific_enthalpy} ) .value() == expected_density) ); } for (const double pressure : pressures) { CAPTURE(polytropic_index, polytropic_constant, pressure); const double index_plus_one = polytropic_index + 1.0; const double expected_specific_enthalpy = index_plus_one * std::pow(polytropic_constant, polytropic_index / index_plus_one) * std::pow(pressure, 1.0 / index_plus_one); CHECK( eos::evaluate(equation_of_state, dimensions::PressureValue{pressure}) .value() == expected_specific_enthalpy ); } } } } TEST_CASE( "Typed Polytropic EOS Preserves Domain And Exterior Semantics", tags::unit & tags::eos ) { const eos::models::Polytrope equation_of_state(3.0, 0.75); try { static_cast(eos::evaluate(equation_of_state, dimensions::DensityValue{-0.1})); FAIL("A negative density must be rejected."); } catch (const eos::EOSEvaluationError& error) { CHECK(std::string_view{error.what()}.find("EOS_INVALID_THERMODYNAMIC_INPUT") != std::string_view::npos); } try { static_cast(eos::evaluate( equation_of_state, dimensions::PressureValue{std::numeric_limits::quiet_NaN()} )); FAIL("A nonfinite pressure must be rejected."); } catch (const eos::EOSEvaluationError& error) { CHECK(std::string_view{error.what()}.find("EOS_INVALID_THERMODYNAMIC_INPUT") != std::string_view::npos); } constexpr double exterior_specific_enthalpy = -0.3; CHECK( eos::evaluate( equation_of_state, dimensions::SpecificEnthalpyValue{exterior_specific_enthalpy} ) .value() == 0.0 ); CHECK( eos::evaluate( equation_of_state, dimensions::SpecificEnthalpyValue{exterior_specific_enthalpy} ) .value() == 0.0 ); CHECK( (eos::partial_derivative( equation_of_state, dimensions::SpecificEnthalpyValue{exterior_specific_enthalpy} ) .value() == 0.0) ); CHECK( (eos::partial_derivative( equation_of_state, dimensions::SpecificEnthalpyValue{exterior_specific_enthalpy} ) .value() == 0.0) ); } TEST_CASE("Density based polytropic EOS supplies surface density and bulk Newton curvature", tags::unit & tags::eos) { for (const double index : {1.0, 1.5, 2.0, 2.5, 3.0}) { const eos::models::Polytrope model(index, 0.73); for (const double density : {1.0e-6, 0.2, 2.0}) { CAPTURE(index, density); const dimensions::DensityValue state{density}; const auto pressure = eos::evaluate(model, state); const double recovered = eos::evaluate(model, pressure).value(); CHECK_THAT(recovered, Catch::Matchers::WithinRel(density, 3.0e-15)); const double step = density * 1.0e-4; const double plus = eos::partial_derivative( model, dimensions::DensityValue{density + step}).value(); const double minus = eos::partial_derivative( model, dimensions::DensityValue{density - step}).value(); const double derivative = eos::second_partial_derivative(model, state).value(); CHECK_THAT(derivative, Catch::Matchers::WithinRel((plus - minus) / (2.0 * step), 3.0e-9)); } CHECK(eos::evaluate(model, dimensions::PressureValue{0.0}).value() == 0.0); if (index == 1.0) { CHECK((eos::second_partial_derivative( model, dimensions::DensityValue{0.0}).value() == 1.46)); } else { CHECK_THROWS_AS((eos::second_partial_derivative( model, dimensions::DensityValue{0.0})), eos::EOSEvaluationError); } for (const double invalid : {-0.1, std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}) { CHECK_THROWS_AS(eos::evaluate(model, dimensions::PressureValue{invalid}), eos::EOSEvaluationError); CHECK_THROWS_AS((eos::second_partial_derivative( model, dimensions::DensityValue{invalid})), eos::EOSEvaluationError); } } }