#include #include #include #include #include #include import mean_field; import test_helpers; namespace { struct StellarModelExtensionTracker final { int structureValidationCount{0}; int surfaceValidationCount{0}; int surfaceResolutionCount{0}; const mean_field::eos::EquationOfState *structureEquationOfState{nullptr}; const mean_field::eos::EquationOfState *surfaceValidationEquationOfState{nullptr}; const mean_field::eos::EquationOfState *surfaceResolutionEquationOfState{nullptr}; }; class StellarModelTestStructure final : public mean_field::models::structure::StructureBase { public: explicit StellarModelTestStructure(std::shared_ptr tracker) : m_tracker(std::move(tracker)), m_equationOfState( 3.0, 0.25 ) { } [[nodiscard]] const mean_field::eos::EquationOfState &equationOfState() const noexcept override { m_tracker->structureEquationOfState = &m_equationOfState; return m_equationOfState; } [[nodiscard]] double targetMass() const noexcept override { return 2.5; } [[nodiscard]] mean_field::models::structure::StructureSeed makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const override { mean_field::models::structure::StructureSeed seed; seed.radius.SetSize(2); seed.density.SetSize(2); seed.enthalpy.SetSize(2); seed.radius(0) = 0.0; seed.radius(1) = 1.0; seed.density(0) = request.centralDensity; seed.density(1) = 0.0; seed.enthalpy(0) = 1.0; seed.enthalpy(1) = 0.0; seed.stellarRadius = 1.0; seed.centralDensity = request.centralDensity; seed.centralEnthalpy = 1.0; return seed; } void validate() const override { ++m_tracker->structureValidationCount; } private: std::shared_ptr m_tracker; mean_field::eos::Polytrope m_equationOfState; }; class StellarModelTestSurface final : public mean_field::surface::SurfaceBase { public: explicit StellarModelTestSurface(std::shared_ptr tracker) : m_tracker(std::move(tracker)) { } [[nodiscard]] mean_field::surface::ResolvedSurfaceCondition resolve(const mean_field::eos::EquationOfState &equationOfState) const override { ++m_tracker->surfaceResolutionCount; m_tracker->surfaceResolutionEquationOfState = &equationOfState; return mean_field::surface::ResolvedSurfaceCondition{0.375}; } void validate(const mean_field::eos::EquationOfState &equationOfState) const override { ++m_tracker->surfaceValidationCount; m_tracker->surfaceValidationEquationOfState = &equationOfState; } private: std::shared_ptr m_tracker; }; } // namespace TEST_CASE( "Stellar Model Owns Structure And Surface Prescriptions", tags::barotrope &tags::unit &tags::model ) { STATIC_REQUIRE_FALSE(std::is_copy_constructible_v); STATIC_REQUIRE_FALSE(std::is_copy_assignable_v); STATIC_REQUIRE(std::is_nothrow_move_constructible_v); STATIC_REQUIRE(std::is_nothrow_move_assignable_v); mean_field::models::StellarModel model{ mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, mean_field::surface::Isobaric{0.0} }; CHECK(model.targetMass() == 1.0); CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.0); CHECK( dynamic_cast(&model.structurePrescription()) != nullptr ); CHECK(dynamic_cast(&model.surfacePrescription()) != nullptr); } TEST_CASE( "Stellar Model Delegates Seed Construction To Its Structure", tags::barotrope &tags::unit &tags::model ) { mean_field::models::StellarModel model{ mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, mean_field::surface::Isobaric{} }; const mean_field::models::structure::StructureSeed seed = model.makeInitialSeed({.centralDensity = 2.0, .radialSampleCount = 64}); CHECK(seed.radius.Size() == 64); CHECK(seed.density.Size() == 64); CHECK(seed.enthalpy.Size() == 64); CHECK(seed.centralDensity == 2.0); CHECK(seed.stellarRadius > 0.0); CHECK(seed.density(0) == 2.0); CHECK(seed.density(63) == 0.0); CHECK(seed.enthalpy(63) == 0.0); } TEST_CASE( "Moving A Stellar Model Preserves Stable Prescription Addresses", tags::barotrope &tags::unit &tags::model ) { mean_field::models::StellarModel originalModel{ mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, mean_field::surface::Isobaric{} }; const mean_field::models::structure::StructureBase *structureAddress = &originalModel.structurePrescription(); const mean_field::surface::SurfaceBase *surfaceAddress = &originalModel.surfacePrescription(); const mean_field::eos::EquationOfState *equationOfStateAddress = &originalModel.equationOfState(); mean_field::models::StellarModel movedModel{std::move(originalModel)}; CHECK(&movedModel.structurePrescription() == structureAddress); CHECK(&movedModel.surfacePrescription() == surfaceAddress); CHECK(&movedModel.equationOfState() == equationOfStateAddress); CHECK(movedModel.targetMass() == 1.0); } TEST_CASE( "Stellar Model Resolves A Positive Isobaric Surface", tags::barotrope &tags::unit &tags::model ) { constexpr double targetPressure = 0.03125; mean_field::models::StellarModel model{ mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, mean_field::surface::Isobaric{targetPressure} }; const double targetEnthalpy = model.resolvedSurfaceCondition().targetEnthalpy; CHECK(targetEnthalpy > 0.0); CHECK( std::abs(model.equationOfState().pressure_from_enthalpy(targetEnthalpy) - targetPressure) < 64.0 * std::numeric_limits::epsilon() ); } TEST_CASE( "Stellar Model Supports Custom Structure And Surface Prescriptions", tags::barotrope &tags::unit &tags::model ) { const auto tracker = std::make_shared(); mean_field::models::StellarModel model{StellarModelTestStructure{tracker}, StellarModelTestSurface{tracker}}; REQUIRE(tracker->structureValidationCount == 1); REQUIRE(tracker->surfaceValidationCount == 1); REQUIRE(tracker->surfaceResolutionCount == 1); CHECK(dynamic_cast(&model.structurePrescription()) != nullptr); CHECK(dynamic_cast(&model.surfacePrescription()) != nullptr); const mean_field::eos::EquationOfState *ownedEquationOfState = &model.equationOfState(); CHECK(tracker->structureEquationOfState == ownedEquationOfState); CHECK(tracker->surfaceValidationEquationOfState == ownedEquationOfState); CHECK(tracker->surfaceResolutionEquationOfState == ownedEquationOfState); CHECK(model.targetMass() == 2.5); CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.375); } TEST_CASE( "Move Assignment Preserves Stellar Model Prescription Addresses", tags::barotrope &tags::unit &tags::model ) { mean_field::models::StellarModel sourceModel{ mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.25}, mean_field::surface::Isobaric{0.0} }; mean_field::models::StellarModel destinationModel{ mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{2.0, 0.5}, 4.0}, mean_field::surface::Isobaric{0.02} }; const mean_field::models::structure::StructureBase *sourceStructureAddress = &sourceModel.structurePrescription(); const mean_field::surface::SurfaceBase *sourceSurfaceAddress = &sourceModel.surfacePrescription(); const mean_field::eos::EquationOfState *sourceEquationOfStateAddress = &sourceModel.equationOfState(); const double sourceTargetEnthalpy = sourceModel.resolvedSurfaceCondition().targetEnthalpy; destinationModel = std::move(sourceModel); CHECK(&destinationModel.structurePrescription() == sourceStructureAddress); CHECK(&destinationModel.surfacePrescription() == sourceSurfaceAddress); CHECK(&destinationModel.equationOfState() == sourceEquationOfStateAddress); CHECK(destinationModel.targetMass() == 1.25); CHECK(destinationModel.resolvedSurfaceCondition().targetEnthalpy == sourceTargetEnthalpy); }