feat(surface): major work on implementing surface constraints in a presciption agnostic manner
This commit is contained in:
@@ -8,115 +8,202 @@
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
TEST_CASE("Polytropic EOS Satisfies Its Analytic Identities",
|
||||
tags::barotrope_eos_unit) {
|
||||
constexpr double polytropic_index = 3.0;
|
||||
constexpr double polytropic_constant = 1.5;
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Satisfies Its Analytic Identities",
|
||||
tags::barotrope_eos_unit
|
||||
) {
|
||||
using namespace mean_field::eos;
|
||||
|
||||
const mean_field::eos::Polytrope barotrope(polytropic_index,
|
||||
polytropic_constant);
|
||||
constexpr double polytropic_index = 3.0;
|
||||
constexpr double polytropic_constant = 1.5;
|
||||
|
||||
const std::array<double, 5> densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0};
|
||||
const Polytrope barotrope(polytropic_index, polytropic_constant);
|
||||
|
||||
for (const double density : densities) {
|
||||
const double pressure = barotrope.pressure_from_density(density);
|
||||
using densityV = DensityValue;
|
||||
using pressureV = PressureValue;
|
||||
using enthalpyV = SpecificEnthalpyValue;
|
||||
|
||||
const double enthalpy = barotrope.enthalpy_from_density(density);
|
||||
constexpr std::array<densityV, 5> densities{
|
||||
densityV{1.0e-6}, densityV{1.0e-3}, densityV{0.1}, densityV{0.7}, densityV{2.0}
|
||||
};
|
||||
|
||||
const double reconstructed_density =
|
||||
barotrope.density_from_enthalpy(enthalpy);
|
||||
for (const densityV density : densities) {
|
||||
const pressureV pressure = evaluate<quantity::Pressure>(barotrope, density);
|
||||
|
||||
const double reconstructed_pressure =
|
||||
barotrope.pressure_from_enthalpy(enthalpy);
|
||||
const enthalpyV enthalpy = evaluate<quantity::SpecificEnthalpy>(barotrope, density);
|
||||
const densityV reconstructed_density = evaluate<quantity::Density>(barotrope, enthalpy);
|
||||
|
||||
const double reconstructed_enthalpy =
|
||||
barotrope.enthalpy_from_pressure(pressure);
|
||||
const pressureV reconstructed_pressure = evaluate<quantity::Pressure>(barotrope, enthalpy);
|
||||
|
||||
CHECK_THAT(reconstructed_density,
|
||||
Catch::Matchers::WithinRel(density, 2.0e-14));
|
||||
const enthalpyV reconstructed_enthalpy = evaluate<quantity::SpecificEnthalpy>(barotrope, pressure);
|
||||
|
||||
CHECK_THAT(reconstructed_pressure,
|
||||
Catch::Matchers::WithinRel(pressure, 2.0e-14));
|
||||
CHECK_THAT(reconstructed_density.value(), Catch::Matchers::WithinRel(density.value(), 2.0e-14));
|
||||
|
||||
CHECK_THAT(reconstructed_enthalpy,
|
||||
Catch::Matchers::WithinRel(enthalpy, 2.0e-14));
|
||||
CHECK_THAT(reconstructed_pressure.value(), Catch::Matchers::WithinRel(pressure.value(), 2.0e-14));
|
||||
|
||||
CHECK_THAT(pressure,
|
||||
Catch::Matchers::WithinRel(
|
||||
density * enthalpy / (polytropic_index + 1.0), 2.0e-14));
|
||||
CHECK_THAT(reconstructed_enthalpy.value(), Catch::Matchers::WithinRel(enthalpy.value(), 2.0e-14));
|
||||
|
||||
CHECK_THAT(barotrope.pressure_derivative_from_enthalpy(enthalpy),
|
||||
Catch::Matchers::WithinRel(density, 2.0e-14));
|
||||
CHECK_THAT(
|
||||
pressure.value(),
|
||||
Catch::Matchers::WithinRel(density.value() * enthalpy.value() / (polytropic_index + 1.0), 2.0e-14)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
barotrope.pressure_derivative_from_density(density),
|
||||
Catch::Matchers::WithinRel(enthalpy / polytropic_index, 2.0e-14));
|
||||
}
|
||||
CHECK_THAT(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{enthalpy}
|
||||
)
|
||||
.value()),
|
||||
Catch::Matchers::WithinRel(density.value(), 2.0e-14)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::Density>(
|
||||
barotrope, mean_field::eos::DensityValue{density}
|
||||
)
|
||||
.value()),
|
||||
Catch::Matchers::WithinRel(enthalpy.value() / polytropic_index, 2.0e-14)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Polytropic EOS Derivatives Match Centered Differences",
|
||||
tags::barotrope_eos_jacobian) {
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Derivatives Match Centered Differences",
|
||||
tags::barotrope_eos_jacobian
|
||||
) {
|
||||
using namespace mean_field::eos;
|
||||
|
||||
const std::array<double, 4> enthalpies{0.05, 0.2, 0.7, 1.4};
|
||||
const Polytrope barotrope(3.0, 1.5);
|
||||
|
||||
for (const double enthalpy : enthalpies) {
|
||||
const double step = 1.0e-6 * std::max(1.0, enthalpy);
|
||||
using densityV = DensityValue;
|
||||
using pressureV = PressureValue;
|
||||
using enthalpyV = SpecificEnthalpyValue;
|
||||
|
||||
const double density_difference =
|
||||
(barotrope.density_from_enthalpy(enthalpy + step) -
|
||||
barotrope.density_from_enthalpy(enthalpy - step)) /
|
||||
(2.0 * step);
|
||||
constexpr std::array<enthalpyV, 4> enthalpies{enthalpyV{0.05}, enthalpyV{0.2}, enthalpyV{0.7}, enthalpyV{1.4}};
|
||||
|
||||
const double pressure_difference =
|
||||
(barotrope.pressure_from_enthalpy(enthalpy + step) -
|
||||
barotrope.pressure_from_enthalpy(enthalpy - step)) /
|
||||
(2.0 * step);
|
||||
for (const enthalpyV enthalpy : enthalpies) {
|
||||
const enthalpyV step = enthalpyV{1.0e-6} * std::max(1.0, enthalpy.value());
|
||||
|
||||
CHECK_THAT(
|
||||
density_difference,
|
||||
Catch::Matchers::WithinRel(
|
||||
barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10));
|
||||
const densityV density_difference = (evaluate<quantity::Density>(barotrope, enthalpy + step) -
|
||||
evaluate<quantity::Density>(barotrope, enthalpy - step)) /
|
||||
(2.0 * step.value());
|
||||
|
||||
CHECK_THAT(
|
||||
pressure_difference,
|
||||
Catch::Matchers::WithinRel(
|
||||
barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10));
|
||||
}
|
||||
const pressureV pressure_difference = (evaluate<quantity::Pressure>(barotrope, enthalpy + step) -
|
||||
evaluate<quantity::Pressure>(barotrope, enthalpy - step)) /
|
||||
(2.0 * step.value());
|
||||
|
||||
CHECK_THAT(
|
||||
density_difference.value(),
|
||||
Catch::Matchers::WithinRel(
|
||||
mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, enthalpy
|
||||
)
|
||||
.value(),
|
||||
5.0e-10
|
||||
)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
pressure_difference.value(),
|
||||
Catch::Matchers::WithinRel(
|
||||
mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, enthalpy
|
||||
)
|
||||
.value(),
|
||||
5.0e-10
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Polytropic EOS Has An Exact Zero Density Surface",
|
||||
tags::barotrope_eos_unit) {
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Has An Exact Zero Density Surface",
|
||||
tags::barotrope_eos_unit
|
||||
) {
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.5);
|
||||
|
||||
CHECK(barotrope.density_from_enthalpy(-1.0) == 0.0);
|
||||
CHECK(barotrope.density_from_enthalpy(0.0) == 0.0);
|
||||
CHECK(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
|
||||
)
|
||||
.value() == 0.0
|
||||
);
|
||||
CHECK(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
|
||||
)
|
||||
.value() == 0.0
|
||||
);
|
||||
|
||||
CHECK(barotrope.pressure_from_enthalpy(-1.0) == 0.0);
|
||||
CHECK(barotrope.pressure_from_enthalpy(0.0) == 0.0);
|
||||
CHECK(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
|
||||
)
|
||||
.value() == 0.0
|
||||
);
|
||||
CHECK(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
|
||||
)
|
||||
.value() == 0.0
|
||||
);
|
||||
|
||||
CHECK(barotrope.density_derivative_from_enthalpy(-1.0) == 0.0);
|
||||
CHECK(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{-1.0}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
|
||||
CHECK(barotrope.density_derivative_from_enthalpy(0.0) == 0.0);
|
||||
CHECK(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Density, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
|
||||
CHECK(barotrope.pressure_derivative_from_enthalpy(0.0) == 0.0);
|
||||
CHECK(
|
||||
(mean_field::eos::partialDerivative<
|
||||
mean_field::eos::quantity::Pressure, mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::SpecificEnthalpyValue{0.0}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE("Polytropic EOS Rejects Invalid Material Parameters",
|
||||
tags::barotrope_eos_unit) {
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument);
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Rejects Invalid Material Parameters",
|
||||
tags::barotrope_eos_unit
|
||||
) {
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(0.5, 1.0), std::invalid_argument);
|
||||
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, 0.0), std::invalid_argument);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0),
|
||||
std::invalid_argument);
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(std::numeric_limits<double>::infinity(), 1.0), std::invalid_argument);
|
||||
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.0);
|
||||
const mean_field::eos::Polytrope barotrope(3.0, 1.0);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.pressure_from_density(-1.0), std::domain_error);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(barotrope, mean_field::eos::DensityValue{-1.0}),
|
||||
std::domain_error
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.enthalpy_from_density(-1.0), std::domain_error);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::DensityValue{-1.0}
|
||||
),
|
||||
std::domain_error
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(barotrope.enthalpy_from_pressure(-1.0), std::domain_error);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::eos::evaluate<mean_field::eos::quantity::SpecificEnthalpy>(
|
||||
barotrope, mean_field::eos::PressureValue{-1.0}
|
||||
),
|
||||
std::domain_error
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
125
tests/physics/equation_of_state_consumer_contracts.cpp
Normal file
125
tests/physics/equation_of_state_consumer_contracts.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace eos = mean_field::eos;
|
||||
|
||||
class DensityClosureEquationOfState final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::DensityFromSpecificEnthalpy>;
|
||||
|
||||
[[nodiscard]] constexpr eos::DensityValue evaluate(
|
||||
eos::DensityFromSpecificEnthalpy,
|
||||
const eos::SpecificEnthalpyValue specificEnthalpy
|
||||
) const noexcept {
|
||||
return eos::DensityValue{specificEnthalpy.value()};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr eos::PartialDerivative<
|
||||
eos::quantity::Density,
|
||||
eos::quantity::SpecificEnthalpy>
|
||||
partialDerivative(
|
||||
eos::DensityFromSpecificEnthalpy,
|
||||
eos::WithRespectTo<eos::quantity::SpecificEnthalpy>,
|
||||
eos::SpecificEnthalpyValue
|
||||
) const noexcept {
|
||||
return eos::PartialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>{1.0};
|
||||
}
|
||||
};
|
||||
|
||||
class DensityClosureWithoutDerivative final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::DensityFromSpecificEnthalpy>;
|
||||
|
||||
[[nodiscard]] constexpr eos::DensityValue evaluate(
|
||||
eos::DensityFromSpecificEnthalpy,
|
||||
const eos::SpecificEnthalpyValue specificEnthalpy
|
||||
) const noexcept {
|
||||
return eos::DensityValue{specificEnthalpy.value()};
|
||||
}
|
||||
};
|
||||
|
||||
class EnthalpyPressureEquationOfState final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::PressureFromSpecificEnthalpy>;
|
||||
|
||||
[[nodiscard]] constexpr eos::PressureValue evaluate(
|
||||
eos::PressureFromSpecificEnthalpy,
|
||||
const eos::SpecificEnthalpyValue specificEnthalpy
|
||||
) const noexcept {
|
||||
return eos::PressureValue{2.0 * specificEnthalpy.value()};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr eos::PartialDerivative<
|
||||
eos::quantity::Pressure,
|
||||
eos::quantity::SpecificEnthalpy>
|
||||
partialDerivative(
|
||||
eos::PressureFromSpecificEnthalpy,
|
||||
eos::WithRespectTo<eos::quantity::SpecificEnthalpy>,
|
||||
eos::SpecificEnthalpyValue
|
||||
) const noexcept {
|
||||
return eos::PartialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>{2.0};
|
||||
}
|
||||
};
|
||||
|
||||
class DensitySeedEquationOfState final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::SpecificEnthalpyFromDensity>;
|
||||
|
||||
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
|
||||
eos::SpecificEnthalpyFromDensity,
|
||||
const eos::DensityValue density
|
||||
) const noexcept {
|
||||
return eos::SpecificEnthalpyValue{3.0 * density.value()};
|
||||
}
|
||||
};
|
||||
|
||||
class GeneralEquationOfStateWithoutCurrentConsumerRelations final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::SpecificEnthalpyFromPressure>;
|
||||
|
||||
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
|
||||
eos::SpecificEnthalpyFromPressure,
|
||||
const eos::PressureValue pressure
|
||||
) const noexcept {
|
||||
return eos::SpecificEnthalpyValue{pressure.value()};
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure EOS Requires Density And Its Enthalpy Derivative",
|
||||
tags::barotropic_closure_equation_of_state_contract
|
||||
) {
|
||||
STATIC_CHECK(eos::BarotropicClosureEquationOfState<eos::Polytrope>);
|
||||
STATIC_CHECK(eos::BarotropicClosureEquationOfState<DensityClosureEquationOfState>);
|
||||
STATIC_CHECK(eos::EquationOfStateModel<DensityClosureWithoutDerivative>);
|
||||
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<DensityClosureWithoutDerivative>);
|
||||
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<EnthalpyPressureEquationOfState>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force EOS Requires Pressure And Its Enthalpy Derivative",
|
||||
tags::pressure_force_equation_of_state_contract
|
||||
) {
|
||||
STATIC_CHECK(eos::PressureForceEquationOfState<eos::Polytrope>);
|
||||
STATIC_CHECK(eos::PressureForceEquationOfState<EnthalpyPressureEquationOfState>);
|
||||
STATIC_CHECK_FALSE(eos::PressureForceEquationOfState<DensityClosureEquationOfState>);
|
||||
STATIC_CHECK_FALSE(eos::PressureForceEquationOfState<DensityClosureWithoutDerivative>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Structure Seed EOS Requires Enthalpy From Density",
|
||||
tags::structure_seed_equation_of_state_contract
|
||||
) {
|
||||
STATIC_CHECK(eos::StructureSeedEquationOfState<eos::Polytrope>);
|
||||
STATIC_CHECK(eos::StructureSeedEquationOfState<DensitySeedEquationOfState>);
|
||||
STATIC_CHECK_FALSE(eos::StructureSeedEquationOfState<DensityClosureEquationOfState>);
|
||||
|
||||
STATIC_CHECK(eos::EquationOfStateModel<GeneralEquationOfStateWithoutCurrentConsumerRelations>);
|
||||
STATIC_CHECK_FALSE(eos::StructureSeedEquationOfState<GeneralEquationOfStateWithoutCurrentConsumerRelations>);
|
||||
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<GeneralEquationOfStateWithoutCurrentConsumerRelations>);
|
||||
STATIC_CHECK_FALSE(eos::PressureForceEquationOfState<GeneralEquationOfStateWithoutCurrentConsumerRelations>);
|
||||
}
|
||||
322
tests/physics/equation_of_state_runtime_view.cpp
Normal file
322
tests/physics/equation_of_state_runtime_view.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace eos = mean_field::eos;
|
||||
|
||||
class LinearPressureEquationOfState final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::PressureFromDensity>;
|
||||
|
||||
[[nodiscard]] constexpr eos::PressureValue evaluate(
|
||||
eos::PressureFromDensity,
|
||||
const eos::DensityValue density
|
||||
) const noexcept {
|
||||
return eos::PressureValue{2.0 * density.value() + 0.5};
|
||||
}
|
||||
};
|
||||
|
||||
struct DensityAlias final : eos::ThermodynamicQuantity {
|
||||
static constexpr std::string_view identifier = "density";
|
||||
};
|
||||
|
||||
class AmbiguouslyIdentifiedEquationOfState final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::Relation<DensityAlias, eos::quantity::Density>>;
|
||||
|
||||
[[nodiscard]] constexpr eos::QuantityValue<DensityAlias> evaluate(
|
||||
eos::Relation<
|
||||
DensityAlias,
|
||||
eos::quantity::Density>,
|
||||
const eos::DensityValue density
|
||||
) const noexcept {
|
||||
return eos::QuantityValue<DensityAlias>{density.value()};
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] std::expected<
|
||||
eos::PressureValue,
|
||||
eos::EvaluationError>
|
||||
pressureAtDensity(
|
||||
const eos::EquationOfStateView equationOfState,
|
||||
const eos::DensityValue density
|
||||
) {
|
||||
return equationOfState.tryEvaluate<eos::quantity::Pressure>(density);
|
||||
}
|
||||
|
||||
[[nodiscard]] const eos::RuntimeRelationDescriptor *findRelation(
|
||||
const eos::EquationOfStateView equationOfState,
|
||||
const eos::ThermodynamicQuantityId output,
|
||||
const eos::ThermodynamicQuantityId input
|
||||
) {
|
||||
for (const eos::RuntimeRelationDescriptor &relation : equationOfState.relations()) {
|
||||
if (relation.outputQuantity == output && relation.inputQuantities.size() == 1 &&
|
||||
relation.inputQuantities[0] == input) {
|
||||
return std::addressof(relation);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Runtime EOS View Generates The Polytropic Relation Catalog",
|
||||
tags::equation_of_state_runtime_contract
|
||||
) {
|
||||
STATIC_CHECK(eos::RuntimeEquationOfStateModel<eos::Polytrope>);
|
||||
STATIC_CHECK(eos::RuntimeEquationOfStateModel<LinearPressureEquationOfState>);
|
||||
STATIC_CHECK(eos::EquationOfStateModel<AmbiguouslyIdentifiedEquationOfState>);
|
||||
STATIC_CHECK_FALSE(eos::RuntimeEquationOfStateModel<AmbiguouslyIdentifiedEquationOfState>);
|
||||
STATIC_CHECK(std::is_trivially_copyable_v<eos::EquationOfStateView>);
|
||||
STATIC_CHECK_FALSE(std::constructible_from<eos::EquationOfStateView, eos::Polytrope &&>);
|
||||
|
||||
const eos::Polytrope equationOfState(3.0, 0.25);
|
||||
const eos::Polytrope secondEquationOfState(1.5, 0.73);
|
||||
|
||||
const eos::EquationOfStateView view{equationOfState};
|
||||
const eos::EquationOfStateView secondView{secondEquationOfState};
|
||||
|
||||
REQUIRE(view.relations().size() == eos::Polytrope::Relations::size);
|
||||
CHECK(view.relations().data() == secondView.relations().data());
|
||||
|
||||
CHECK(eos::thermodynamicQuantityId<eos::quantity::Density>.name() == "density");
|
||||
CHECK(eos::thermodynamicQuantityId<eos::quantity::Pressure>.name() == "pressure");
|
||||
CHECK(eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>.name() == "specific_enthalpy");
|
||||
|
||||
const eos::RuntimeRelationDescriptor *pressureFromDensity = findRelation(
|
||||
view, eos::thermodynamicQuantityId<eos::quantity::Pressure>,
|
||||
eos::thermodynamicQuantityId<eos::quantity::Density>
|
||||
);
|
||||
|
||||
REQUIRE(pressureFromDensity != nullptr);
|
||||
CHECK(pressureFromDensity->hasPartialDerivative(0));
|
||||
|
||||
const eos::RuntimeRelationDescriptor *specificEnthalpyFromPressure = findRelation(
|
||||
view, eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>,
|
||||
eos::thermodynamicQuantityId<eos::quantity::Pressure>
|
||||
);
|
||||
|
||||
REQUIRE(specificEnthalpyFromPressure != nullptr);
|
||||
CHECK_FALSE(specificEnthalpyFromPressure->hasPartialDerivative(0));
|
||||
|
||||
const eos::RuntimeRelationDescriptor *pressureFromSpecificEnthalpy = findRelation(
|
||||
view, eos::thermodynamicQuantityId<eos::quantity::Pressure>,
|
||||
eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>
|
||||
);
|
||||
|
||||
REQUIRE(pressureFromSpecificEnthalpy != nullptr);
|
||||
CHECK(pressureFromSpecificEnthalpy->hasPartialDerivative(0));
|
||||
|
||||
const eos::RuntimeRelationDescriptor *specificEnthalpyFromDensity = findRelation(
|
||||
view, eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>,
|
||||
eos::thermodynamicQuantityId<eos::quantity::Density>
|
||||
);
|
||||
|
||||
REQUIRE(specificEnthalpyFromDensity != nullptr);
|
||||
CHECK_FALSE(specificEnthalpyFromDensity->hasPartialDerivative(0));
|
||||
|
||||
const eos::RuntimeRelationDescriptor *densityFromSpecificEnthalpy = findRelation(
|
||||
view, eos::thermodynamicQuantityId<eos::quantity::Density>,
|
||||
eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>
|
||||
);
|
||||
|
||||
REQUIRE(densityFromSpecificEnthalpy != nullptr);
|
||||
CHECK(densityFromSpecificEnthalpy->hasPartialDerivative(0));
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Runtime EOS View Matches Typed Polytropic Evaluation",
|
||||
tags::equation_of_state_runtime_compatibility
|
||||
) {
|
||||
const eos::Polytrope equationOfState(3.0, 0.25);
|
||||
const eos::EquationOfStateView view{equationOfState};
|
||||
|
||||
const eos::DensityValue density{0.7};
|
||||
const eos::SpecificEnthalpyValue specificEnthalpy{0.9};
|
||||
const eos::PressureValue pressure{0.04};
|
||||
|
||||
const auto runtimePressureFromDensity = view.tryEvaluate<eos::quantity::Pressure>(density);
|
||||
const auto runtimePressureFromSpecificEnthalpy = view.tryEvaluate<eos::quantity::Pressure>(specificEnthalpy);
|
||||
const auto runtimeSpecificEnthalpyFromDensity = view.tryEvaluate<eos::quantity::SpecificEnthalpy>(density);
|
||||
const auto runtimeSpecificEnthalpyFromPressure = view.tryEvaluate<eos::quantity::SpecificEnthalpy>(pressure);
|
||||
const auto runtimeDensityFromSpecificEnthalpy = view.tryEvaluate<eos::quantity::Density>(specificEnthalpy);
|
||||
|
||||
REQUIRE(runtimePressureFromDensity.has_value());
|
||||
REQUIRE(runtimePressureFromSpecificEnthalpy.has_value());
|
||||
REQUIRE(runtimeSpecificEnthalpyFromDensity.has_value());
|
||||
REQUIRE(runtimeSpecificEnthalpyFromPressure.has_value());
|
||||
REQUIRE(runtimeDensityFromSpecificEnthalpy.has_value());
|
||||
|
||||
CHECK(
|
||||
runtimePressureFromDensity->value() == eos::evaluate<eos::quantity::Pressure>(equationOfState, density).value()
|
||||
);
|
||||
CHECK(
|
||||
runtimePressureFromSpecificEnthalpy->value() ==
|
||||
eos::evaluate<eos::quantity::Pressure>(equationOfState, specificEnthalpy).value()
|
||||
);
|
||||
CHECK(
|
||||
runtimeSpecificEnthalpyFromDensity->value() ==
|
||||
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, density).value()
|
||||
);
|
||||
CHECK(
|
||||
runtimeSpecificEnthalpyFromPressure->value() ==
|
||||
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, pressure).value()
|
||||
);
|
||||
CHECK(
|
||||
runtimeDensityFromSpecificEnthalpy->value() ==
|
||||
eos::evaluate<eos::quantity::Density>(equationOfState, specificEnthalpy).value()
|
||||
);
|
||||
|
||||
const std::array runtimeDensityInput{
|
||||
eos::RuntimeQuantityValue{eos::thermodynamicQuantityId<eos::quantity::Density>, density.value()}
|
||||
};
|
||||
|
||||
const auto erasedPressureFromDensity = view.tryEvaluate(
|
||||
eos::thermodynamicQuantityId<eos::quantity::Pressure>,
|
||||
std::span<const eos::RuntimeQuantityValue>{runtimeDensityInput}
|
||||
);
|
||||
|
||||
REQUIRE(erasedPressureFromDensity.has_value());
|
||||
CHECK(erasedPressureFromDensity->quantity == eos::thermodynamicQuantityId<eos::quantity::Pressure>);
|
||||
CHECK(erasedPressureFromDensity->value == runtimePressureFromDensity->value());
|
||||
|
||||
const auto runtimePressureDerivative =
|
||||
view.tryPartialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(specificEnthalpy);
|
||||
|
||||
const auto runtimeDensityDerivative =
|
||||
view.tryPartialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(specificEnthalpy);
|
||||
|
||||
const auto runtimePressureDensityDerivative =
|
||||
view.tryPartialDerivative<eos::quantity::Pressure, eos::quantity::Density>(density);
|
||||
|
||||
REQUIRE(runtimePressureDerivative.has_value());
|
||||
REQUIRE(runtimeDensityDerivative.has_value());
|
||||
REQUIRE(runtimePressureDensityDerivative.has_value());
|
||||
|
||||
CHECK(
|
||||
runtimePressureDerivative->value() ==
|
||||
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, specificEnthalpy
|
||||
)
|
||||
.value()
|
||||
);
|
||||
CHECK(
|
||||
runtimeDensityDerivative->value() ==
|
||||
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, specificEnthalpy
|
||||
)
|
||||
.value()
|
||||
);
|
||||
CHECK(
|
||||
runtimePressureDensityDerivative->value() ==
|
||||
eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(equationOfState, density).value()
|
||||
);
|
||||
|
||||
const auto erasedPressureDensityDerivative = view.tryPartialDerivative(
|
||||
eos::thermodynamicQuantityId<eos::quantity::Pressure>, eos::thermodynamicQuantityId<eos::quantity::Density>,
|
||||
std::span<const eos::RuntimeQuantityValue>{runtimeDensityInput}
|
||||
);
|
||||
|
||||
REQUIRE(erasedPressureDensityDerivative.has_value());
|
||||
CHECK(*erasedPressureDensityDerivative == runtimePressureDensityDerivative->value());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Runtime EOS View Reports Unsupported And Invalid Requests",
|
||||
tags::equation_of_state_runtime_contract
|
||||
) {
|
||||
const eos::Polytrope equationOfState(3.0, 0.25);
|
||||
const eos::EquationOfStateView view{equationOfState};
|
||||
|
||||
constexpr eos::ThermodynamicQuantityId temperature{"temperature"};
|
||||
|
||||
const std::array densityInput{eos::RuntimeQuantityValue{eos::thermodynamicQuantityId<eos::quantity::Density>, 0.7}};
|
||||
|
||||
const std::array pressureInput{
|
||||
eos::RuntimeQuantityValue{eos::thermodynamicQuantityId<eos::quantity::Pressure>, 0.04}
|
||||
};
|
||||
|
||||
const std::array<eos::RuntimeQuantityValue, 0> noInputs{};
|
||||
|
||||
const auto unsupportedOutput =
|
||||
view.tryEvaluate(temperature, std::span<const eos::RuntimeQuantityValue>{densityInput});
|
||||
REQUIRE_FALSE(unsupportedOutput.has_value());
|
||||
CHECK(unsupportedOutput.error().code() == eos::EvaluationErrorCode::unsupported_relation);
|
||||
|
||||
const auto wrongInputCount = view.tryEvaluate(
|
||||
eos::thermodynamicQuantityId<eos::quantity::Pressure>, std::span<const eos::RuntimeQuantityValue>{noInputs}
|
||||
);
|
||||
REQUIRE_FALSE(wrongInputCount.has_value());
|
||||
CHECK(wrongInputCount.error().code() == eos::EvaluationErrorCode::wrong_input_count);
|
||||
|
||||
const auto wrongInputQuantity = view.tryEvaluate(
|
||||
eos::thermodynamicQuantityId<eos::quantity::Density>, std::span<const eos::RuntimeQuantityValue>{pressureInput}
|
||||
);
|
||||
REQUIRE_FALSE(wrongInputQuantity.has_value());
|
||||
CHECK(wrongInputQuantity.error().code() == eos::EvaluationErrorCode::wrong_input_quantity);
|
||||
|
||||
const auto unsupportedDerivative = view.tryPartialDerivative(
|
||||
eos::thermodynamicQuantityId<eos::quantity::SpecificEnthalpy>,
|
||||
eos::thermodynamicQuantityId<eos::quantity::Pressure>, std::span<const eos::RuntimeQuantityValue>{pressureInput}
|
||||
);
|
||||
REQUIRE_FALSE(unsupportedDerivative.has_value());
|
||||
CHECK(unsupportedDerivative.error().code() == eos::EvaluationErrorCode::unsupported_derivative);
|
||||
|
||||
const auto invalidDensity = view.tryEvaluate<eos::quantity::Pressure>(eos::DensityValue{-0.1});
|
||||
REQUIRE_FALSE(invalidDensity.has_value());
|
||||
CHECK(invalidDensity.error().code() == eos::EvaluationErrorCode::outside_domain);
|
||||
|
||||
const auto nonfiniteDensity =
|
||||
view.tryEvaluate<eos::quantity::Pressure>(eos::DensityValue{std::numeric_limits<double>::quiet_NaN()});
|
||||
REQUIRE_FALSE(nonfiniteDensity.has_value());
|
||||
CHECK(nonfiniteDensity.error().code() == eos::EvaluationErrorCode::nonfinite_input);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"One Runtime EOS Function Accepts Heterogeneous Concrete Models",
|
||||
tags::equation_of_state_runtime_compatibility
|
||||
) {
|
||||
const eos::Polytrope polytrope(3.0, 0.25);
|
||||
const LinearPressureEquationOfState linearEquationOfState;
|
||||
|
||||
const std::array views{eos::EquationOfStateView{polytrope}, eos::EquationOfStateView{linearEquationOfState}};
|
||||
|
||||
const eos::DensityValue density{0.7};
|
||||
|
||||
const auto polytropicPressure = pressureAtDensity(views[0], density);
|
||||
const auto linearPressure = pressureAtDensity(views[1], density);
|
||||
|
||||
REQUIRE(polytropicPressure.has_value());
|
||||
REQUIRE(linearPressure.has_value());
|
||||
|
||||
CHECK(polytropicPressure->value() == eos::evaluate<eos::quantity::Pressure>(polytrope, density).value());
|
||||
CHECK(linearPressure->value() == 1.9);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Runtime EOS View Remains Valid When Stable Ownership Moves",
|
||||
tags::equation_of_state_runtime_contract
|
||||
) {
|
||||
auto owner = std::make_unique<const eos::Polytrope>(3.0, 0.25);
|
||||
const eos::EquationOfStateView view{*owner};
|
||||
|
||||
auto movedOwner = std::move(owner);
|
||||
|
||||
const auto pressure = view.tryEvaluate<eos::quantity::Pressure>(eos::DensityValue{0.7});
|
||||
|
||||
REQUIRE(movedOwner != nullptr);
|
||||
REQUIRE(pressure.has_value());
|
||||
CHECK(pressure->value() == eos::evaluate<eos::quantity::Pressure>(*movedOwner, eos::DensityValue{0.7}).value());
|
||||
}
|
||||
228
tests/physics/equation_of_state_type_system.cpp
Normal file
228
tests/physics/equation_of_state_type_system.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include <concepts>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace eos = mean_field::eos;
|
||||
|
||||
struct Entropy final : eos::ThermodynamicQuantity { };
|
||||
struct ElectronFraction final : eos::ThermodynamicQuantity { };
|
||||
|
||||
using SpecificEnthalpyFromPressureAndEntropy =
|
||||
eos::Relation<eos::quantity::SpecificEnthalpy, eos::quantity::Pressure, Entropy>;
|
||||
|
||||
class CompleteEquationOfState final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<
|
||||
eos::PressureFromDensity,
|
||||
eos::SpecificEnthalpyFromPressure,
|
||||
SpecificEnthalpyFromPressureAndEntropy>;
|
||||
|
||||
[[nodiscard]] constexpr eos::PressureValue evaluate(
|
||||
eos::PressureFromDensity,
|
||||
const eos::DensityValue density
|
||||
) const noexcept {
|
||||
return eos::PressureValue{2.0 * density.value()};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
|
||||
eos::SpecificEnthalpyFromPressure,
|
||||
const eos::PressureValue pressure
|
||||
) const noexcept {
|
||||
return eos::SpecificEnthalpyValue{3.0 * pressure.value()};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
|
||||
SpecificEnthalpyFromPressureAndEntropy,
|
||||
const eos::PressureValue pressure,
|
||||
const eos::QuantityValue<Entropy> entropy
|
||||
) const noexcept {
|
||||
return eos::SpecificEnthalpyValue{3.0 * pressure.value() + 5.0 * entropy.value()};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr eos::PartialDerivative<
|
||||
eos::quantity::SpecificEnthalpy,
|
||||
Entropy>
|
||||
partialDerivative(
|
||||
SpecificEnthalpyFromPressureAndEntropy,
|
||||
eos::WithRespectTo<Entropy>,
|
||||
eos::PressureValue,
|
||||
eos::QuantityValue<Entropy>
|
||||
) const noexcept {
|
||||
return eos::PartialDerivative<eos::quantity::SpecificEnthalpy, Entropy>{5.0};
|
||||
}
|
||||
};
|
||||
|
||||
class MissingRelationImplementation final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::PressureFromDensity, eos::SpecificEnthalpyFromPressure>;
|
||||
|
||||
[[nodiscard]] eos::PressureValue evaluate(
|
||||
eos::PressureFromDensity,
|
||||
eos::DensityValue density
|
||||
) const {
|
||||
return eos::PressureValue{density.value()};
|
||||
}
|
||||
};
|
||||
|
||||
class IncorrectRelationOutput final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::PressureFromDensity>;
|
||||
|
||||
[[nodiscard]] eos::DensityValue evaluate(
|
||||
eos::PressureFromDensity,
|
||||
eos::DensityValue density
|
||||
) const {
|
||||
return density;
|
||||
}
|
||||
};
|
||||
|
||||
class InvalidRelationCatalog final {
|
||||
public:
|
||||
using Relations = eos::RelationCatalog<eos::Relation<double, eos::quantity::Density>>;
|
||||
};
|
||||
|
||||
template <typename EquationOfState>
|
||||
concept CanEvaluateDensityFromSpecificEnthalpy = requires(const EquationOfState &equationOfState) {
|
||||
eos::evaluate<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{1.0});
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Thermodynamic Values Preserve Physical Quantity Types",
|
||||
tags::equation_of_state_quantity_types
|
||||
) {
|
||||
STATIC_CHECK(eos::ThermodynamicQuantityType<eos::quantity::Density>);
|
||||
STATIC_CHECK(eos::ThermodynamicQuantityType<eos::quantity::Pressure>);
|
||||
STATIC_CHECK(eos::ThermodynamicQuantityType<eos::quantity::SpecificEnthalpy>);
|
||||
STATIC_CHECK_FALSE(eos::ThermodynamicQuantityType<const eos::quantity::Pressure>);
|
||||
|
||||
STATIC_CHECK_FALSE(std::same_as<eos::DensityValue, eos::PressureValue>);
|
||||
STATIC_CHECK_FALSE(std::same_as<eos::PressureValue, eos::SpecificEnthalpyValue>);
|
||||
STATIC_CHECK_FALSE(std::is_convertible_v<double, eos::PressureValue>);
|
||||
STATIC_CHECK_FALSE(std::is_constructible_v<eos::PressureValue, eos::DensityValue>);
|
||||
|
||||
STATIC_CHECK(std::is_trivially_copyable_v<eos::DensityValue>);
|
||||
STATIC_CHECK(std::is_standard_layout_v<eos::DensityValue>);
|
||||
STATIC_CHECK(sizeof(eos::DensityValue) == sizeof(double));
|
||||
STATIC_CHECK(sizeof(eos::PressureValue) == sizeof(double));
|
||||
STATIC_CHECK(sizeof(eos::SpecificEnthalpyValue) == sizeof(double));
|
||||
STATIC_CHECK(std::is_empty_v<eos::PressureFromDensity>);
|
||||
|
||||
constexpr eos::DensityValue density{-0.25};
|
||||
STATIC_CHECK(density.value() == -0.25);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Thermodynamic Derivatives Preserve Numerator And Denominator Types",
|
||||
tags::equation_of_state_quantity_types
|
||||
) {
|
||||
using PressureByDensity = eos::PartialDerivative<eos::quantity::Pressure, eos::quantity::Density>;
|
||||
|
||||
using PressureBySpecificEnthalpy = eos::PartialDerivative<eos::quantity::Pressure, eos::quantity::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::equation_of_state_relation_contract
|
||||
) {
|
||||
using ValidCatalog = eos::RelationCatalog<eos::PressureFromDensity, eos::SpecificEnthalpyFromPressure>;
|
||||
|
||||
using DuplicateCatalog = eos::RelationCatalog<eos::PressureFromDensity, eos::PressureFromDensity>;
|
||||
|
||||
using InvalidRelation = eos::Relation<double, eos::quantity::Density>;
|
||||
using InvalidCatalog = eos::RelationCatalog<InvalidRelation>;
|
||||
using RepeatedInputRelation =
|
||||
eos::Relation<eos::quantity::Pressure, eos::quantity::Density, eos::quantity::Density>;
|
||||
using RepeatedInputCatalog = eos::RelationCatalog<RepeatedInputRelation>;
|
||||
|
||||
STATIC_CHECK(eos::ValidRelationCatalog<ValidCatalog>);
|
||||
STATIC_CHECK_FALSE(eos::ValidRelationCatalog<DuplicateCatalog>);
|
||||
STATIC_CHECK_FALSE(eos::ValidRelationCatalog<InvalidCatalog>);
|
||||
STATIC_CHECK_FALSE(eos::ValidRelationCatalog<RepeatedInputCatalog>);
|
||||
STATIC_CHECK_FALSE(eos::ValidRelationCatalog<eos::RelationCatalog<>>);
|
||||
|
||||
STATIC_CHECK(eos::relationCatalogContains<ValidCatalog, eos::PressureFromDensity>);
|
||||
STATIC_CHECK_FALSE(eos::relationCatalogContains<ValidCatalog, eos::DensityFromSpecificEnthalpy>);
|
||||
STATIC_CHECK(eos::relationContainsInput<eos::PressureFromDensity, eos::quantity::Density>);
|
||||
STATIC_CHECK_FALSE(eos::relationContainsInput<eos::PressureFromDensity, eos::quantity::Pressure>);
|
||||
|
||||
STATIC_CHECK(std::same_as<eos::RelationOutputT<eos::PressureFromDensity>, eos::quantity::Pressure>);
|
||||
STATIC_CHECK(std::same_as<eos::RelationInputT<0, eos::PressureFromDensity>, eos::quantity::Density>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"EOS Model Contract Requires Every Declared Relation",
|
||||
tags::equation_of_state_relation_contract
|
||||
) {
|
||||
STATIC_CHECK(eos::EquationOfStateModel<CompleteEquationOfState>);
|
||||
STATIC_CHECK_FALSE(eos::EquationOfStateModel<MissingRelationImplementation>);
|
||||
STATIC_CHECK_FALSE(eos::EquationOfStateModel<IncorrectRelationOutput>);
|
||||
STATIC_CHECK_FALSE(eos::EquationOfStateModel<InvalidRelationCatalog>);
|
||||
|
||||
STATIC_CHECK(eos::SupportsRelation<CompleteEquationOfState, eos::PressureFromDensity>);
|
||||
STATIC_CHECK_FALSE(eos::SupportsRelation<CompleteEquationOfState, eos::DensityFromSpecificEnthalpy>);
|
||||
STATIC_CHECK_FALSE(CanEvaluateDensityFromSpecificEnthalpy<CompleteEquationOfState>);
|
||||
STATIC_CHECK(
|
||||
eos::SupportsPartialDerivative<CompleteEquationOfState, SpecificEnthalpyFromPressureAndEntropy, Entropy>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
eos::SupportsPartialDerivative<
|
||||
CompleteEquationOfState, SpecificEnthalpyFromPressureAndEntropy, ElectronFraction>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"EOS Evaluation Selects Relations From Typed Inputs",
|
||||
tags::equation_of_state_relation_contract
|
||||
) {
|
||||
constexpr CompleteEquationOfState equationOfState;
|
||||
|
||||
constexpr eos::PressureValue pressure =
|
||||
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{1.25});
|
||||
|
||||
constexpr eos::SpecificEnthalpyValue specificEnthalpy = eos::evaluate<eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, eos::PressureValue{0.5}, eos::QuantityValue<Entropy>{0.2}
|
||||
);
|
||||
|
||||
constexpr auto entropyDerivative = eos::partialDerivative<eos::quantity::SpecificEnthalpy, Entropy>(
|
||||
equationOfState, eos::PressureValue{0.5}, eos::QuantityValue<Entropy>{0.2}
|
||||
);
|
||||
|
||||
STATIC_CHECK(noexcept(eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{1.25})));
|
||||
STATIC_CHECK(
|
||||
noexcept(eos::partialDerivative<eos::quantity::SpecificEnthalpy, Entropy>(
|
||||
equationOfState, eos::PressureValue{0.5}, eos::QuantityValue<Entropy>{0.2}
|
||||
))
|
||||
);
|
||||
|
||||
STATIC_CHECK(pressure.value() == 2.5);
|
||||
STATIC_CHECK(specificEnthalpy.value() == 2.5);
|
||||
STATIC_CHECK(entropyDerivative.value() == 5.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"EOS Evaluation Errors Retain A Structured Cause",
|
||||
tags::equation_of_state_relation_contract
|
||||
) {
|
||||
const eos::EvaluationError error(
|
||||
eos::EvaluationErrorCode::outside_domain, "Density is outside the relation domain."
|
||||
);
|
||||
|
||||
CHECK(error.code() == eos::EvaluationErrorCode::outside_domain);
|
||||
CHECK(std::string_view{error.what()} == "Density is outside the relation domain.");
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
156
tests/physics/polytropic_eos_characterization.cpp
Normal file
156
tests/physics/polytropic_eos_characterization.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace eos = mean_field::eos;
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Pressure To Specific Enthalpy Relation Is Characterized",
|
||||
tags::polytropic_eos_characterization
|
||||
) {
|
||||
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
|
||||
constexpr std::array<double, 3> polytropicConstants{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 polytropicIndex : polytropicIndices) {
|
||||
for (const double polytropicConstant : polytropicConstants) {
|
||||
const mean_field::eos::Polytrope equationOfState(polytropicIndex, polytropicConstant);
|
||||
|
||||
for (const double pressure : pressures) {
|
||||
CAPTURE(polytropicIndex, polytropicConstant, pressure);
|
||||
|
||||
const double indexPlusOne = polytropicIndex + 1.0;
|
||||
const double expectedEnthalpy = indexPlusOne *
|
||||
std::pow(polytropicConstant, polytropicIndex / indexPlusOne) *
|
||||
std::pow(pressure, 1.0 / indexPlusOne);
|
||||
|
||||
const double enthalpy =
|
||||
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::PressureValue{pressure})
|
||||
.value();
|
||||
|
||||
if (pressure == 0.0) {
|
||||
CHECK(enthalpy == 0.0);
|
||||
} else {
|
||||
CHECK_THAT(enthalpy, Catch::Matchers::WithinRel(expectedEnthalpy, 5.0e-14));
|
||||
|
||||
const double recoveredPressure =
|
||||
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::SpecificEnthalpyValue{enthalpy})
|
||||
.value();
|
||||
|
||||
CHECK_THAT(recoveredPressure, Catch::Matchers::WithinRel(pressure, 5.0e-13));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Domain Contract Covers Every Relation",
|
||||
tags::polytropic_eos_characterization
|
||||
) {
|
||||
constexpr double infinity = std::numeric_limits<double>::infinity();
|
||||
constexpr double quietNaN = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
for (const double invalidIndex : std::array<double, 4>{0.999, infinity, -infinity, quietNaN}) {
|
||||
CAPTURE(invalidIndex);
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(invalidIndex, 1.0), std::invalid_argument);
|
||||
}
|
||||
|
||||
for (const double invalidConstant : std::array<double, 5>{0.0, -0.1, infinity, -infinity, quietNaN}) {
|
||||
CAPTURE(invalidConstant);
|
||||
CHECK_THROWS_AS(mean_field::eos::Polytrope(3.0, invalidConstant), std::invalid_argument);
|
||||
}
|
||||
|
||||
const mean_field::eos::Polytrope equationOfState(3.0, 0.75);
|
||||
|
||||
constexpr double negativeDensity = -0.1;
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{negativeDensity}), std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::DensityValue{negativeDensity}),
|
||||
std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
|
||||
equationOfState, eos::DensityValue{negativeDensity}
|
||||
)),
|
||||
std::domain_error
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::PressureValue{-0.1}), std::domain_error
|
||||
);
|
||||
|
||||
constexpr double exteriorEnthalpy = -0.1;
|
||||
CHECK(
|
||||
eos::evaluate<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{exteriorEnthalpy}).value() ==
|
||||
0.0
|
||||
);
|
||||
CHECK(
|
||||
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::SpecificEnthalpyValue{exteriorEnthalpy}).value() ==
|
||||
0.0
|
||||
);
|
||||
CHECK(
|
||||
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, eos::SpecificEnthalpyValue{exteriorEnthalpy}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
CHECK(
|
||||
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, eos::SpecificEnthalpyValue{exteriorEnthalpy}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
|
||||
for (const double nonfiniteValue : std::array<double, 3>{infinity, -infinity, quietNaN}) {
|
||||
CAPTURE(nonfiniteValue);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::DensityValue{nonfiniteValue}),
|
||||
std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::DensityValue{nonfiniteValue}),
|
||||
std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
|
||||
equationOfState, eos::DensityValue{nonfiniteValue}
|
||||
)),
|
||||
std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::PressureValue{nonfiniteValue}),
|
||||
std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{nonfiniteValue}),
|
||||
std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::SpecificEnthalpyValue{nonfiniteValue}),
|
||||
std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, eos::SpecificEnthalpyValue{nonfiniteValue}
|
||||
)),
|
||||
std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, eos::SpecificEnthalpyValue{nonfiniteValue}
|
||||
)),
|
||||
std::domain_error
|
||||
);
|
||||
}
|
||||
}
|
||||
200
tests/physics/polytropic_eos_relations.cpp
Normal file
200
tests/physics/polytropic_eos_relations.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace eos = mean_field::eos;
|
||||
|
||||
template <typename Candidate>
|
||||
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<Polytrope>);
|
||||
STATIC_CHECK_FALSE(std::is_polymorphic_v<Polytrope>);
|
||||
STATIC_CHECK_FALSE(HasAnyUnaryEquationOfStateConversion<Polytrope>);
|
||||
STATIC_CHECK(Polytrope::Relations::size == 5);
|
||||
|
||||
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::PressureFromDensity>);
|
||||
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::PressureFromSpecificEnthalpy>);
|
||||
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::SpecificEnthalpyFromDensity>);
|
||||
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::SpecificEnthalpyFromPressure>);
|
||||
STATIC_CHECK(eos::SupportsRelation<Polytrope, eos::DensityFromSpecificEnthalpy>);
|
||||
|
||||
STATIC_CHECK(eos::SupportsPartialDerivative<Polytrope, eos::PressureFromDensity, eos::quantity::Density>);
|
||||
STATIC_CHECK(
|
||||
eos::SupportsPartialDerivative<Polytrope, eos::PressureFromSpecificEnthalpy, eos::quantity::SpecificEnthalpy>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
eos::SupportsPartialDerivative<Polytrope, eos::DensityFromSpecificEnthalpy, eos::quantity::SpecificEnthalpy>
|
||||
);
|
||||
|
||||
STATIC_CHECK_FALSE(
|
||||
eos::SupportsPartialDerivative<Polytrope, eos::SpecificEnthalpyFromPressure, eos::quantity::Pressure>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
eos::SupportsPartialDerivative<Polytrope, eos::SpecificEnthalpyFromDensity, eos::quantity::Density>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Polytropic EOS Typed Relations Preserve Analytic Values",
|
||||
tags::polytropic_eos_characterization
|
||||
) {
|
||||
constexpr std::array<double, 3> polytropicIndices{1.0, 1.5, 3.0};
|
||||
constexpr std::array<double, 2> polytropicConstants{0.25, 0.73};
|
||||
constexpr std::array<double, 4> densities{0.0, 1.0e-6, 0.2, 2.0};
|
||||
constexpr std::array<double, 4> specificEnthalpies{-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 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<eos::quantity::Pressure>(equationOfState, eos::DensityValue{density}).value() ==
|
||||
expectedPressure
|
||||
);
|
||||
|
||||
CHECK(
|
||||
eos::evaluate<eos::quantity::SpecificEnthalpy>(equationOfState, eos::DensityValue{density})
|
||||
.value() == expectedSpecificEnthalpy
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::Density>(
|
||||
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<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{specificEnthalpy})
|
||||
.value() == expectedDensity
|
||||
);
|
||||
|
||||
CHECK(
|
||||
eos::evaluate<eos::quantity::Pressure>(
|
||||
equationOfState, eos::SpecificEnthalpyValue{specificEnthalpy}
|
||||
)
|
||||
.value() ==
|
||||
(specificEnthalpy <= 0.0 ? 0.0 : expectedDensity * specificEnthalpy / (polytropicIndex + 1.0))
|
||||
);
|
||||
|
||||
CHECK(
|
||||
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
|
||||
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<eos::quantity::SpecificEnthalpy>(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<void>(eos::evaluate<eos::quantity::Pressure>(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<void>(eos::evaluate<eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, eos::PressureValue{std::numeric_limits<double>::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<eos::quantity::Density>(equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy})
|
||||
.value() == 0.0
|
||||
);
|
||||
|
||||
CHECK(
|
||||
eos::evaluate<eos::quantity::Pressure>(equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy})
|
||||
.value() == 0.0
|
||||
);
|
||||
|
||||
CHECK(
|
||||
(eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
|
||||
CHECK(
|
||||
(eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
|
||||
equationOfState, eos::SpecificEnthalpyValue{exteriorSpecificEnthalpy}
|
||||
)
|
||||
.value() == 0.0)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user