// Preserve the independent identities, finite differences, and surface conventions from MeanField's barotrope tests. #include #include #include #include #include #include "serif/eos/evaluation.hpp" #include "serif/eos/models/polytropic.hpp" namespace { namespace eos = serif::eos; namespace quantity = serif::dimensions; using Catch::Matchers::WithinRel; /** @brief Evaluate a centered difference independently of the EOS derivative implementation. */ template [[nodiscard]] double centered_derivative(Function&& function, const double value, const double step) { return (function(value + step) - function(value - step)) / (2.0 * step); } } TEST_CASE("MeanField polytropic round trips preserve the original strict identities", "[unit][eos][meanfield]") { const eos::models::Polytrope model{3.0, 1.5}; for (const double density : {1.0e-6, 1.0e-3, 0.1, 0.7, 2.0}) { CAPTURE(density); const double pressure = eos::evaluate(model, quantity::DensityValue{density}).value(); const double enthalpy = eos::evaluate(model, quantity::DensityValue{density}).value(); CHECK_THAT(eos::evaluate(model, quantity::SpecificEnthalpyValue{enthalpy}).value(), WithinRel(density, 2.0e-14)); CHECK_THAT(eos::evaluate(model, quantity::SpecificEnthalpyValue{enthalpy}).value(), WithinRel(pressure, 2.0e-14)); CHECK_THAT(eos::evaluate(model, quantity::PressureValue{pressure}).value(), WithinRel(enthalpy, 2.0e-14)); CHECK_THAT(pressure, WithinRel(density * enthalpy / 4.0, 2.0e-14)); CHECK_THAT((eos::partial_derivative(model, quantity::SpecificEnthalpyValue{enthalpy}).value()), WithinRel(density, 2.0e-14)); CHECK_THAT((eos::partial_derivative(model, quantity::DensityValue{density}).value()), WithinRel(enthalpy / 3.0, 2.0e-14)); } } TEST_CASE("MeanField polytropic thermodynamic identities span integer and fractional indices", "[unit][eos][meanfield]") { for (const double index : {1.0, 1.5, 3.0}) { const eos::models::Polytrope model{index, 0.73}; CHECK(model.polytropic_index() == index); CHECK(model.polytropic_constant() == 0.73); CHECK(model.enthalpy_scale() == (index + 1.0) * 0.73); for (const double density : {1.0e-4, 0.02, 0.37, 2.4}) { CAPTURE(index, density); const double pressure = eos::evaluate(model, quantity::DensityValue{density}).value(); const double enthalpy = eos::evaluate(model, quantity::DensityValue{density}).value(); const double recovered_density = eos::evaluate(model, quantity::SpecificEnthalpyValue{enthalpy}).value(); const double recovered_pressure = eos::evaluate(model, quantity::SpecificEnthalpyValue{enthalpy}).value(); CHECK_THAT(pressure, WithinRel(0.73 * std::pow(density, 1.0 + 1.0 / index), 2.0e-13)); CHECK_THAT(enthalpy, WithinRel((index + 1.0) * 0.73 * std::pow(density, 1.0 / index), 2.0e-13)); CHECK_THAT(recovered_density, WithinRel(density, 5.0e-13)); CHECK_THAT(recovered_pressure, WithinRel(pressure, 5.0e-13)); CHECK_THAT(recovered_pressure, WithinRel(density * enthalpy / (index + 1.0), 5.0e-13)); CHECK((eos::partial_derivative(model, quantity::SpecificEnthalpyValue{enthalpy}).value()) == recovered_density); CHECK_THAT((eos::partial_derivative(model, quantity::DensityValue{density}).value()), WithinRel(enthalpy / index, 5.0e-13)); } } } TEST_CASE("MeanField cubic polytrope derivatives retain the strict centered difference tolerance", "[unit][eos][meanfield]") { const eos::models::Polytrope model{3.0, 1.5}; for (const double enthalpy : {0.05, 0.2, 0.7, 1.4}) { CAPTURE(enthalpy); const double step = 1.0e-6 * std::max(1.0, enthalpy); const double density_derivative = centered_derivative([&](const double value) { return eos::evaluate(model, quantity::SpecificEnthalpyValue{value}).value(); }, enthalpy, step); const double pressure_derivative = centered_derivative([&](const double value) { return eos::evaluate(model, quantity::SpecificEnthalpyValue{value}).value(); }, enthalpy, step); CHECK_THAT(density_derivative, WithinRel((eos::partial_derivative(model, quantity::SpecificEnthalpyValue{enthalpy}).value()), 5.0e-10)); CHECK_THAT(pressure_derivative, WithinRel((eos::partial_derivative(model, quantity::SpecificEnthalpyValue{enthalpy}).value()), 5.0e-10)); } } TEST_CASE("MeanField pressure and density derivatives match centered differences for every original index", "[unit][eos][meanfield]") { for (const double index : {1.0, 1.5, 3.0}) { const eos::models::Polytrope model{index, 0.61}; for (const double value : {0.2, 0.73, 1.8}) { CAPTURE(index, value); const double step = 2.0e-6 * std::max(1.0, std::abs(value)); const double pressure_enthalpy = centered_derivative([&](const double enthalpy) { return eos::evaluate(model, quantity::SpecificEnthalpyValue{enthalpy}).value(); }, value, step); const double pressure_density = centered_derivative([&](const double density) { return eos::evaluate(model, quantity::DensityValue{density}).value(); }, value, step); const double density_enthalpy = centered_derivative([&](const double enthalpy) { return eos::evaluate(model, quantity::SpecificEnthalpyValue{enthalpy}).value(); }, value, step); CHECK_THAT(pressure_enthalpy, WithinRel((eos::partial_derivative(model, quantity::SpecificEnthalpyValue{value}).value()), 5.0e-8)); CHECK_THAT(pressure_density, WithinRel((eos::partial_derivative(model, quantity::DensityValue{value}).value()), 5.0e-8)); CHECK_THAT(density_enthalpy, WithinRel((eos::partial_derivative(model, quantity::SpecificEnthalpyValue{value}).value()), 5.0e-8)); } } } TEST_CASE("MeanField polytropic surface keeps the n one right derivative and zero exterior", "[unit][eos][meanfield]") { for (const double index : {1.0, 1.5, 3.0}) { const eos::models::Polytrope model{index, 0.47}; for (const double enthalpy : {0.0, -0.3, -1.0}) { CAPTURE(index, enthalpy); CHECK(eos::evaluate(model, quantity::SpecificEnthalpyValue{enthalpy}).value() == 0.0); CHECK(eos::evaluate(model, quantity::SpecificEnthalpyValue{enthalpy}).value() == 0.0); CHECK((eos::partial_derivative(model, quantity::SpecificEnthalpyValue{enthalpy}).value()) == 0.0); const double density_derivative = index == 1.0 && enthalpy == 0.0 ? 1.0 / model.enthalpy_scale() : 0.0; CHECK((eos::partial_derivative(model, quantity::SpecificEnthalpyValue{enthalpy}).value()) == density_derivative); } CHECK(eos::evaluate(model, quantity::DensityValue{0.0}).value() == 0.0); CHECK(eos::evaluate(model, quantity::DensityValue{0.0}).value() == 0.0); CHECK((eos::partial_derivative(model, quantity::DensityValue{0.0}).value()) == 0.0); } }