The test suite is extensive. Note that LLMs were used to aid in the development of the test suite. All LLM generated code was manually reviewed.
197 lines
8.7 KiB
C++
197 lines
8.7 KiB
C++
// Ported from MeanField tests/physics/polytropic_eos_relations.cpp.
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <string_view>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
|
|
#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<double, 3> polytropic_indices{1.0, 1.5, 3.0};
|
|
constexpr std::array<double, 2> polytropic_constants{0.25, 0.73};
|
|
constexpr std::array<double, 4> densities{0.0, 1.0e-6, 0.2, 2.0};
|
|
constexpr std::array<double, 4> specific_enthalpies{-0.3, 0.0, 0.2, 1.7};
|
|
constexpr std::array<double, 4> 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<dimensions::Pressure>(equation_of_state, dimensions::DensityValue{density}).value() ==
|
|
expected_pressure
|
|
);
|
|
|
|
CHECK(
|
|
eos::evaluate<dimensions::SpecificEnthalpy>(equation_of_state, dimensions::DensityValue{density})
|
|
.value() == expected_specific_enthalpy
|
|
);
|
|
|
|
CHECK_THAT(
|
|
(eos::partial_derivative<dimensions::Pressure, dimensions::MassDensity>(
|
|
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<dimensions::MassDensity>(
|
|
equation_of_state, dimensions::SpecificEnthalpyValue{specific_enthalpy}
|
|
)
|
|
.value() == expected_density
|
|
);
|
|
|
|
CHECK(
|
|
eos::evaluate<dimensions::Pressure>(
|
|
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<dimensions::Pressure, dimensions::SpecificEnthalpy>(
|
|
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<dimensions::SpecificEnthalpy>(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<void>(eos::evaluate<dimensions::Pressure>(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<void>(eos::evaluate<dimensions::SpecificEnthalpy>(
|
|
equation_of_state, dimensions::PressureValue{std::numeric_limits<double>::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<dimensions::MassDensity>(
|
|
equation_of_state, dimensions::SpecificEnthalpyValue{exterior_specific_enthalpy}
|
|
)
|
|
.value() == 0.0
|
|
);
|
|
|
|
CHECK(
|
|
eos::evaluate<dimensions::Pressure>(
|
|
equation_of_state, dimensions::SpecificEnthalpyValue{exterior_specific_enthalpy}
|
|
)
|
|
.value() == 0.0
|
|
);
|
|
|
|
CHECK(
|
|
(eos::partial_derivative<dimensions::MassDensity, dimensions::SpecificEnthalpy>(
|
|
equation_of_state, dimensions::SpecificEnthalpyValue{exterior_specific_enthalpy}
|
|
)
|
|
.value() == 0.0)
|
|
);
|
|
|
|
CHECK(
|
|
(eos::partial_derivative<dimensions::Pressure, dimensions::SpecificEnthalpy>(
|
|
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<dimensions::Pressure>(model, state);
|
|
const double recovered = eos::evaluate<dimensions::MassDensity>(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<dimensions::Pressure, dimensions::MassDensity>(
|
|
model, dimensions::DensityValue{density + step}).value();
|
|
const double minus = eos::partial_derivative<dimensions::Pressure, dimensions::MassDensity>(
|
|
model, dimensions::DensityValue{density - step}).value();
|
|
const double derivative = eos::second_partial_derivative<dimensions::Pressure, dimensions::MassDensity, dimensions::MassDensity>(model, state).value();
|
|
CHECK_THAT(derivative, Catch::Matchers::WithinRel((plus - minus) / (2.0 * step), 3.0e-9));
|
|
}
|
|
CHECK(eos::evaluate<dimensions::MassDensity>(model, dimensions::PressureValue{0.0}).value() == 0.0);
|
|
if (index == 1.0) {
|
|
CHECK((eos::second_partial_derivative<dimensions::Pressure, dimensions::MassDensity, dimensions::MassDensity>(
|
|
model, dimensions::DensityValue{0.0}).value() == 1.46));
|
|
} else {
|
|
CHECK_THROWS_AS((eos::second_partial_derivative<dimensions::Pressure, dimensions::MassDensity, dimensions::MassDensity>(
|
|
model, dimensions::DensityValue{0.0})), eos::EOSEvaluationError);
|
|
}
|
|
for (const double invalid : {-0.1, std::numeric_limits<double>::infinity(), std::numeric_limits<double>::quiet_NaN()}) {
|
|
CHECK_THROWS_AS(eos::evaluate<dimensions::MassDensity>(model, dimensions::PressureValue{invalid}), eos::EOSEvaluationError);
|
|
CHECK_THROWS_AS((eos::second_partial_derivative<dimensions::Pressure, dimensions::MassDensity, dimensions::MassDensity>(
|
|
model, dimensions::DensityValue{invalid})), eos::EOSEvaluationError);
|
|
}
|
|
}
|
|
}
|