test(tests): added test suite
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.
This commit is contained in:
135
tests/eos/barotrope_meanfield.cpp
Normal file
135
tests/eos/barotrope_meanfield.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// Preserve the independent identities, finite differences, and surface conventions from MeanField's barotrope tests.
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#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 <typename Function>
|
||||
[[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<quantity::Pressure>(model, quantity::DensityValue{density}).value();
|
||||
const double enthalpy = eos::evaluate<quantity::SpecificEnthalpy>(model, quantity::DensityValue{density}).value();
|
||||
CHECK_THAT(eos::evaluate<quantity::MassDensity>(model, quantity::SpecificEnthalpyValue{enthalpy}).value(), WithinRel(density, 2.0e-14));
|
||||
CHECK_THAT(eos::evaluate<quantity::Pressure>(model, quantity::SpecificEnthalpyValue{enthalpy}).value(), WithinRel(pressure, 2.0e-14));
|
||||
CHECK_THAT(eos::evaluate<quantity::SpecificEnthalpy>(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<quantity::Pressure, quantity::SpecificEnthalpy>(model, quantity::SpecificEnthalpyValue{enthalpy}).value()), WithinRel(density, 2.0e-14));
|
||||
CHECK_THAT((eos::partial_derivative<quantity::Pressure, quantity::MassDensity>(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<quantity::Pressure>(model, quantity::DensityValue{density}).value();
|
||||
const double enthalpy = eos::evaluate<quantity::SpecificEnthalpy>(model, quantity::DensityValue{density}).value();
|
||||
const double recovered_density = eos::evaluate<quantity::MassDensity>(model, quantity::SpecificEnthalpyValue{enthalpy}).value();
|
||||
const double recovered_pressure = eos::evaluate<quantity::Pressure>(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<quantity::Pressure, quantity::SpecificEnthalpy>(model, quantity::SpecificEnthalpyValue{enthalpy}).value()) == recovered_density);
|
||||
CHECK_THAT((eos::partial_derivative<quantity::Pressure, quantity::MassDensity>(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<quantity::MassDensity>(model, quantity::SpecificEnthalpyValue{value}).value();
|
||||
}, enthalpy, step);
|
||||
const double pressure_derivative = centered_derivative([&](const double value)
|
||||
{
|
||||
return eos::evaluate<quantity::Pressure>(model, quantity::SpecificEnthalpyValue{value}).value();
|
||||
}, enthalpy, step);
|
||||
CHECK_THAT(density_derivative, WithinRel((eos::partial_derivative<quantity::MassDensity, quantity::SpecificEnthalpy>(model, quantity::SpecificEnthalpyValue{enthalpy}).value()), 5.0e-10));
|
||||
CHECK_THAT(pressure_derivative, WithinRel((eos::partial_derivative<quantity::Pressure, quantity::SpecificEnthalpy>(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<quantity::Pressure>(model, quantity::SpecificEnthalpyValue{enthalpy}).value();
|
||||
}, value, step);
|
||||
const double pressure_density = centered_derivative([&](const double density)
|
||||
{
|
||||
return eos::evaluate<quantity::Pressure>(model, quantity::DensityValue{density}).value();
|
||||
}, value, step);
|
||||
const double density_enthalpy = centered_derivative([&](const double enthalpy)
|
||||
{
|
||||
return eos::evaluate<quantity::MassDensity>(model, quantity::SpecificEnthalpyValue{enthalpy}).value();
|
||||
}, value, step);
|
||||
CHECK_THAT(pressure_enthalpy, WithinRel((eos::partial_derivative<quantity::Pressure, quantity::SpecificEnthalpy>(model, quantity::SpecificEnthalpyValue{value}).value()), 5.0e-8));
|
||||
CHECK_THAT(pressure_density, WithinRel((eos::partial_derivative<quantity::Pressure, quantity::MassDensity>(model, quantity::DensityValue{value}).value()), 5.0e-8));
|
||||
CHECK_THAT(density_enthalpy, WithinRel((eos::partial_derivative<quantity::MassDensity, quantity::SpecificEnthalpy>(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<quantity::MassDensity>(model, quantity::SpecificEnthalpyValue{enthalpy}).value() == 0.0);
|
||||
CHECK(eos::evaluate<quantity::Pressure>(model, quantity::SpecificEnthalpyValue{enthalpy}).value() == 0.0);
|
||||
CHECK((eos::partial_derivative<quantity::Pressure, quantity::SpecificEnthalpy>(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<quantity::MassDensity, quantity::SpecificEnthalpy>(model, quantity::SpecificEnthalpyValue{enthalpy}).value()) == density_derivative);
|
||||
}
|
||||
CHECK(eos::evaluate<quantity::Pressure>(model, quantity::DensityValue{0.0}).value() == 0.0);
|
||||
CHECK(eos::evaluate<quantity::SpecificEnthalpy>(model, quantity::DensityValue{0.0}).value() == 0.0);
|
||||
CHECK((eos::partial_derivative<quantity::Pressure, quantity::MassDensity>(model, quantity::DensityValue{0.0}).value()) == 0.0);
|
||||
}
|
||||
}
|
||||
237
tests/eos/equation_of_state_type_system.cpp
Normal file
237
tests/eos/equation_of_state_type_system.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
// Ported from MeanField tests/physics/equation_of_state_type_system.cpp.
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "serif/dimensions/type_alias.hpp"
|
||||
#include "serif/eos/evaluation.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
|
||||
namespace eos = serif::eos;
|
||||
namespace dimensions = serif::dimensions;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Entropy final : dimensions::ThermodynamicQuantity {};
|
||||
struct ElectronFraction final : dimensions::ThermodynamicQuantity {};
|
||||
|
||||
using SpecificEnthalpyFromPressureAndEntropy =
|
||||
eos::relations::Relation<dimensions::SpecificEnthalpy, dimensions::Pressure, Entropy>;
|
||||
|
||||
class CompleteEquationOfState final {
|
||||
public:
|
||||
using Relations = eos::relations::RelationCatalog<
|
||||
eos::relations::PressureFromDensity,
|
||||
eos::relations::SpecificEnthalpyFromPressure,
|
||||
SpecificEnthalpyFromPressureAndEntropy>;
|
||||
|
||||
[[nodiscard]] constexpr dimensions::PressureValue evaluate(
|
||||
eos::relations::PressureFromDensity,
|
||||
const dimensions::DensityValue density
|
||||
) const noexcept {
|
||||
return dimensions::PressureValue{2.0 * density.value()};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr dimensions::SpecificEnthalpyValue evaluate(
|
||||
eos::relations::SpecificEnthalpyFromPressure,
|
||||
const dimensions::PressureValue pressure
|
||||
) const noexcept {
|
||||
return dimensions::SpecificEnthalpyValue{3.0 * pressure.value()};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr dimensions::SpecificEnthalpyValue evaluate(
|
||||
SpecificEnthalpyFromPressureAndEntropy,
|
||||
const dimensions::PressureValue pressure,
|
||||
const dimensions::QuantityValue<Entropy> entropy
|
||||
) const noexcept {
|
||||
return dimensions::SpecificEnthalpyValue{3.0 * pressure.value() + 5.0 * entropy.value()};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr dimensions::PartialDerivative<dimensions::SpecificEnthalpy, Entropy> partial_derivative(
|
||||
SpecificEnthalpyFromPressureAndEntropy,
|
||||
dimensions::WithRespectTo<Entropy>,
|
||||
dimensions::PressureValue,
|
||||
dimensions::QuantityValue<Entropy>
|
||||
) const noexcept {
|
||||
return dimensions::PartialDerivative<dimensions::SpecificEnthalpy, Entropy>{5.0};
|
||||
}
|
||||
};
|
||||
|
||||
class MissingRelationImplementation final {
|
||||
public:
|
||||
using Relations = eos::relations::
|
||||
RelationCatalog<eos::relations::PressureFromDensity, eos::relations::SpecificEnthalpyFromPressure>;
|
||||
|
||||
[[nodiscard]] dimensions::PressureValue evaluate(
|
||||
eos::relations::PressureFromDensity,
|
||||
dimensions::DensityValue density
|
||||
) const {
|
||||
return dimensions::PressureValue{density.value()};
|
||||
}
|
||||
};
|
||||
|
||||
class IncorrectRelationOutput final {
|
||||
public:
|
||||
using Relations = eos::relations::RelationCatalog<eos::relations::PressureFromDensity>;
|
||||
|
||||
[[nodiscard]] dimensions::DensityValue evaluate(
|
||||
eos::relations::PressureFromDensity,
|
||||
dimensions::DensityValue density
|
||||
) const {
|
||||
return density;
|
||||
}
|
||||
};
|
||||
|
||||
class InvalidRelationCatalog final {
|
||||
public:
|
||||
using Relations = eos::relations::RelationCatalog<eos::relations::Relation<double, dimensions::MassDensity>>;
|
||||
};
|
||||
|
||||
template <typename EquationOfState>
|
||||
concept CanEvaluateDensityFromSpecificEnthalpy = requires(const EquationOfState& equation_of_state) {
|
||||
eos::evaluate<dimensions::MassDensity>(equation_of_state, dimensions::SpecificEnthalpyValue{1.0});
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Thermodynamic Values Preserve Physical Quantity Types",
|
||||
tags::unit & tags::eos
|
||||
) {
|
||||
STATIC_CHECK(dimensions::ThermodynamicQuantityType<dimensions::MassDensity>);
|
||||
STATIC_CHECK(dimensions::ThermodynamicQuantityType<dimensions::Pressure>);
|
||||
STATIC_CHECK(dimensions::ThermodynamicQuantityType<dimensions::SpecificEnthalpy>);
|
||||
STATIC_CHECK_FALSE(dimensions::ThermodynamicQuantityType<const dimensions::Pressure>);
|
||||
|
||||
STATIC_CHECK_FALSE(std::same_as<dimensions::DensityValue, dimensions::PressureValue>);
|
||||
STATIC_CHECK_FALSE(std::same_as<dimensions::PressureValue, dimensions::SpecificEnthalpyValue>);
|
||||
STATIC_CHECK_FALSE(std::is_convertible_v<double, dimensions::PressureValue>);
|
||||
STATIC_CHECK_FALSE(std::is_constructible_v<dimensions::PressureValue, dimensions::DensityValue>);
|
||||
|
||||
STATIC_CHECK(std::is_trivially_copyable_v<dimensions::DensityValue>);
|
||||
STATIC_CHECK(std::is_standard_layout_v<dimensions::DensityValue>);
|
||||
STATIC_CHECK(sizeof(dimensions::DensityValue) == sizeof(double));
|
||||
STATIC_CHECK(sizeof(dimensions::PressureValue) == sizeof(double));
|
||||
STATIC_CHECK(sizeof(dimensions::SpecificEnthalpyValue) == sizeof(double));
|
||||
STATIC_CHECK(std::is_empty_v<eos::relations::PressureFromDensity>);
|
||||
|
||||
constexpr dimensions::DensityValue density{-0.25};
|
||||
STATIC_CHECK(density.value() == -0.25);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Thermodynamic Derivatives Preserve Numerator And Denominator Types",
|
||||
tags::unit & tags::eos
|
||||
) {
|
||||
using PressureByDensity = dimensions::PartialDerivative<dimensions::Pressure, dimensions::MassDensity>;
|
||||
|
||||
using PressureBySpecificEnthalpy =
|
||||
dimensions::PartialDerivative<dimensions::Pressure, dimensions::SpecificEnthalpy>;
|
||||
|
||||
STATIC_CHECK_FALSE(std::same_as<PressureByDensity, PressureBySpecificEnthalpy>);
|
||||
STATIC_CHECK_FALSE(std::is_convertible_v<PressureByDensity, PressureBySpecificEnthalpy>);
|
||||
STATIC_CHECK(std::is_trivially_copyable_v<PressureByDensity>);
|
||||
STATIC_CHECK(std::is_standard_layout_v<PressureByDensity>);
|
||||
STATIC_CHECK(sizeof(PressureByDensity) == sizeof(double));
|
||||
|
||||
constexpr PressureByDensity derivative{1.75};
|
||||
STATIC_CHECK(derivative.value() == 1.75);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"EOS Relation Catalog Rejects Invalid And Duplicate Relations",
|
||||
tags::unit & tags::eos
|
||||
) {
|
||||
using ValidCatalog = eos::relations::RelationCatalog<
|
||||
eos::relations::PressureFromDensity, eos::relations::SpecificEnthalpyFromPressure>;
|
||||
|
||||
using DuplicateCatalog =
|
||||
eos::relations::RelationCatalog<eos::relations::PressureFromDensity, eos::relations::PressureFromDensity>;
|
||||
|
||||
using InvalidRelation = eos::relations::Relation<double, dimensions::MassDensity>;
|
||||
using InvalidCatalog = eos::relations::RelationCatalog<InvalidRelation>;
|
||||
using RepeatedInputRelation =
|
||||
eos::relations::Relation<dimensions::Pressure, dimensions::MassDensity, dimensions::MassDensity>;
|
||||
using RepeatedInputCatalog = eos::relations::RelationCatalog<RepeatedInputRelation>;
|
||||
|
||||
STATIC_CHECK(eos::relations::ValidRelationCatalog<ValidCatalog>);
|
||||
STATIC_CHECK_FALSE(eos::relations::ValidRelationCatalog<DuplicateCatalog>);
|
||||
STATIC_CHECK_FALSE(eos::relations::ValidRelationCatalog<InvalidCatalog>);
|
||||
STATIC_CHECK_FALSE(eos::relations::ValidRelationCatalog<RepeatedInputCatalog>);
|
||||
STATIC_CHECK_FALSE(eos::relations::ValidRelationCatalog<eos::relations::RelationCatalog<>>);
|
||||
|
||||
STATIC_CHECK(
|
||||
eos::relations::relation_catalog_contains_relation_v<ValidCatalog, eos::relations::PressureFromDensity>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
eos::relations::relation_catalog_contains_relation_v<ValidCatalog, eos::relations::DensityFromSpecificEnthalpy>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
eos::relations::relation_contains_input_v<eos::relations::PressureFromDensity, dimensions::MassDensity>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
eos::relations::relation_contains_input_v<eos::relations::PressureFromDensity, dimensions::Pressure>
|
||||
);
|
||||
|
||||
STATIC_CHECK(std::same_as<eos::relations::PressureFromDensity::OutputQuantity, dimensions::Pressure>);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
dimensions::QuantityAt<0, eos::relations::PressureFromDensity::InputQuantities>::Type,
|
||||
dimensions::MassDensity>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"EOS Model Contract Requires Every Declared Relation",
|
||||
tags::unit & tags::eos
|
||||
) {
|
||||
STATIC_CHECK(eos::models::EOSModel<CompleteEquationOfState>);
|
||||
STATIC_CHECK_FALSE(eos::models::EOSModel<MissingRelationImplementation>);
|
||||
STATIC_CHECK_FALSE(eos::models::EOSModel<IncorrectRelationOutput>);
|
||||
STATIC_CHECK_FALSE(eos::models::EOSModel<InvalidRelationCatalog>);
|
||||
|
||||
STATIC_CHECK(eos::models::EOSSupportingRelation<CompleteEquationOfState, eos::relations::PressureFromDensity>);
|
||||
STATIC_CHECK_FALSE(
|
||||
eos::models::EOSSupportingRelation<CompleteEquationOfState, eos::relations::DensityFromSpecificEnthalpy>
|
||||
);
|
||||
STATIC_CHECK_FALSE(CanEvaluateDensityFromSpecificEnthalpy<CompleteEquationOfState>);
|
||||
STATIC_CHECK(
|
||||
eos::models::EOSSupportingPartialDerivative<
|
||||
CompleteEquationOfState, SpecificEnthalpyFromPressureAndEntropy, Entropy>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
eos::models::EOSSupportingPartialDerivative<
|
||||
CompleteEquationOfState, SpecificEnthalpyFromPressureAndEntropy, ElectronFraction>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"EOS Evaluation Selects Relations From Typed Inputs",
|
||||
tags::unit & tags::eos
|
||||
) {
|
||||
constexpr CompleteEquationOfState equation_of_state;
|
||||
|
||||
constexpr dimensions::PressureValue pressure =
|
||||
eos::evaluate<dimensions::Pressure>(equation_of_state, dimensions::DensityValue{1.25});
|
||||
|
||||
constexpr dimensions::SpecificEnthalpyValue specific_enthalpy = eos::evaluate<dimensions::SpecificEnthalpy>(
|
||||
equation_of_state, dimensions::PressureValue{0.5}, dimensions::QuantityValue<Entropy>{0.2}
|
||||
);
|
||||
|
||||
constexpr auto entropy_derivative = eos::partial_derivative<dimensions::SpecificEnthalpy, Entropy>(
|
||||
equation_of_state, dimensions::PressureValue{0.5}, dimensions::QuantityValue<Entropy>{0.2}
|
||||
);
|
||||
|
||||
STATIC_CHECK(noexcept(eos::evaluate<dimensions::Pressure>(equation_of_state, dimensions::DensityValue{1.25})));
|
||||
STATIC_CHECK(
|
||||
noexcept(eos::partial_derivative<dimensions::SpecificEnthalpy, Entropy>(
|
||||
equation_of_state, dimensions::PressureValue{0.5}, dimensions::QuantityValue<Entropy>{0.2}
|
||||
))
|
||||
);
|
||||
|
||||
STATIC_CHECK(pressure.value() == 2.5);
|
||||
STATIC_CHECK(specific_enthalpy.value() == 2.5);
|
||||
STATIC_CHECK(entropy_derivative.value() == 5.0);
|
||||
}
|
||||
7
tests/eos/meson.build
Normal file
7
tests/eos/meson.build
Normal file
@@ -0,0 +1,7 @@
|
||||
eos_test_sources = files(
|
||||
'barotrope_meanfield.cpp',
|
||||
'equation_of_state_type_system.cpp',
|
||||
'polytropic_eos_characterization.cpp',
|
||||
'polytropic_eos_relations.cpp',
|
||||
'pressure_potential.cpp',
|
||||
)
|
||||
166
tests/eos/polytropic_eos_characterization.cpp
Normal file
166
tests/eos/polytropic_eos_characterization.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
// Ported from MeanField tests/physics/polytropic_eos_characterization.cpp.
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#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 Pressure To Specific Enthalpy Relation Is Characterized",
|
||||
tags::unit & tags::eos
|
||||
) {
|
||||
constexpr std::array<double, 3> polytropic_indices{1.0, 1.5, 3.0};
|
||||
constexpr std::array<double, 3> polytropic_constants{0.25, 0.73, 2.0};
|
||||
constexpr std::array<double, 5> pressures{0.0, 1.0e-12, 1.0e-4, 0.3, 5.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 pressure : pressures) {
|
||||
CAPTURE(polytropic_index, polytropic_constant, pressure);
|
||||
|
||||
const double index_plus_one = polytropic_index + 1.0;
|
||||
const double expected_enthalpy = index_plus_one *
|
||||
std::pow(polytropic_constant, polytropic_index / index_plus_one) *
|
||||
std::pow(pressure, 1.0 / index_plus_one);
|
||||
|
||||
const double enthalpy =
|
||||
eos::evaluate<dimensions::SpecificEnthalpy>(equation_of_state, dimensions::PressureValue{pressure})
|
||||
.value();
|
||||
|
||||
if (pressure == 0.0) {
|
||||
CHECK(enthalpy == 0.0);
|
||||
} else {
|
||||
CHECK_THAT(enthalpy, Catch::Matchers::WithinRel(expected_enthalpy, 5.0e-14));
|
||||
|
||||
const double recovered_pressure = eos::evaluate<dimensions::Pressure>(
|
||||
equation_of_state, dimensions::SpecificEnthalpyValue{enthalpy}
|
||||
)
|
||||
.value();
|
||||
|
||||
CHECK_THAT(recovered_pressure, Catch::Matchers::WithinRel(pressure, 5.0e-13));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Domain Contract Covers Every Relation",
|
||||
tags::unit & tags::eos
|
||||
) {
|
||||
constexpr double infinity = std::numeric_limits<double>::infinity();
|
||||
constexpr double quiet_nan = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
for (const double invalid_index : std::array<double, 4>{0.999, infinity, -infinity, quiet_nan}) {
|
||||
CAPTURE(invalid_index);
|
||||
CHECK_THROWS_AS(eos::models::Polytrope(invalid_index, 1.0), eos::EOSEvaluationError);
|
||||
}
|
||||
|
||||
for (const double invalid_constant : std::array<double, 5>{0.0, -0.1, infinity, -infinity, quiet_nan}) {
|
||||
CAPTURE(invalid_constant);
|
||||
CHECK_THROWS_AS(eos::models::Polytrope(3.0, invalid_constant), eos::EOSEvaluationError);
|
||||
}
|
||||
|
||||
const eos::models::Polytrope equation_of_state(3.0, 0.75);
|
||||
|
||||
constexpr double negative_density = -0.1;
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<dimensions::Pressure>(equation_of_state, dimensions::DensityValue{negative_density}),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<dimensions::SpecificEnthalpy>(equation_of_state, dimensions::DensityValue{negative_density}),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(eos::partial_derivative<dimensions::Pressure, dimensions::MassDensity>(
|
||||
equation_of_state, dimensions::DensityValue{negative_density}
|
||||
)),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<dimensions::SpecificEnthalpy>(equation_of_state, dimensions::PressureValue{-0.1}),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
|
||||
constexpr double exterior_enthalpy = -0.1;
|
||||
CHECK(
|
||||
eos::evaluate<dimensions::MassDensity>(equation_of_state, dimensions::SpecificEnthalpyValue{exterior_enthalpy})
|
||||
.value() == 0.0
|
||||
);
|
||||
CHECK(
|
||||
eos::evaluate<dimensions::Pressure>(equation_of_state, dimensions::SpecificEnthalpyValue{exterior_enthalpy})
|
||||
.value() == 0.0
|
||||
);
|
||||
CHECK(
|
||||
(eos::partial_derivative<dimensions::MassDensity, dimensions::SpecificEnthalpy>(
|
||||
equation_of_state, dimensions::SpecificEnthalpyValue{exterior_enthalpy}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
CHECK(
|
||||
(eos::partial_derivative<dimensions::Pressure, dimensions::SpecificEnthalpy>(
|
||||
equation_of_state, dimensions::SpecificEnthalpyValue{exterior_enthalpy}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
|
||||
for (const double nonfinite_value : std::array<double, 3>{infinity, -infinity, quiet_nan}) {
|
||||
CAPTURE(nonfinite_value);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<dimensions::Pressure>(equation_of_state, dimensions::DensityValue{nonfinite_value}),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<dimensions::SpecificEnthalpy>(equation_of_state, dimensions::DensityValue{nonfinite_value}),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(eos::partial_derivative<dimensions::Pressure, dimensions::MassDensity>(
|
||||
equation_of_state, dimensions::DensityValue{nonfinite_value}
|
||||
)),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<dimensions::SpecificEnthalpy>(equation_of_state, dimensions::PressureValue{nonfinite_value}),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<dimensions::MassDensity>(
|
||||
equation_of_state, dimensions::SpecificEnthalpyValue{nonfinite_value}
|
||||
),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<dimensions::Pressure>(equation_of_state, dimensions::SpecificEnthalpyValue{nonfinite_value}),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(eos::partial_derivative<dimensions::MassDensity, dimensions::SpecificEnthalpy>(
|
||||
equation_of_state, dimensions::SpecificEnthalpyValue{nonfinite_value}
|
||||
)),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(eos::partial_derivative<dimensions::Pressure, dimensions::SpecificEnthalpy>(
|
||||
equation_of_state, dimensions::SpecificEnthalpyValue{nonfinite_value}
|
||||
)),
|
||||
eos::EOSEvaluationError
|
||||
);
|
||||
}
|
||||
}
|
||||
196
tests/eos/polytropic_eos_relations.cpp
Normal file
196
tests/eos/polytropic_eos_relations.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
tests/eos/pressure_potential.cpp
Normal file
33
tests/eos/pressure_potential.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#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"
|
||||
|
||||
namespace eos = serif::eos;
|
||||
namespace dimensions = serif::dimensions;
|
||||
|
||||
TEST_CASE("Polytropic pressure potential obeys its mechanical derivative contract", "[eos][barotropic_density][unit]") {
|
||||
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 double potential = eos::evaluate<dimensions::BarotropicPressurePotential>(model, state).value();
|
||||
CHECK_THAT(potential, Catch::Matchers::WithinRel((index + 1.0) * 0.73 * std::pow(density, 1.0 / index), 2.0e-15));
|
||||
const double step = density * 1.0e-4;
|
||||
const double positive = eos::evaluate<dimensions::BarotropicPressurePotential>(model, dimensions::DensityValue{density + step}).value();
|
||||
const double negative = eos::evaluate<dimensions::BarotropicPressurePotential>(model, dimensions::DensityValue{density - step}).value();
|
||||
const double expected_slope = eos::partial_derivative<dimensions::Pressure, dimensions::MassDensity>(model, state).value() / density;
|
||||
CHECK_THAT((positive - negative) / (2.0 * step), Catch::Matchers::WithinRel(expected_slope, 3.0e-9));
|
||||
}
|
||||
CHECK(eos::evaluate<dimensions::BarotropicPressurePotential>(model, dimensions::DensityValue{0.0}).value() == 0.0);
|
||||
for (const double invalid : {-0.1, std::numeric_limits<double>::infinity(), std::numeric_limits<double>::quiet_NaN()}) {
|
||||
CHECK_THROWS_AS(eos::evaluate<dimensions::BarotropicPressurePotential>(model, dimensions::DensityValue{invalid}), eos::EOSEvaluationError);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user