From 1f54205996e02aa8db99e21b69f3c41c1acbc021 Mon Sep 17 00:00:00 2001 From: Bertrand Coconnier Date: Wed, 15 Mar 2023 14:13:11 +0100 Subject: [PATCH] Add a unit test for `FGAtmosphere` (#857) * [WIP] Add a unit test for `FGAtmosphere` * Bug fixes * Fix `SetTemperatureSL()` and `SetPressureSL()` which did not update the sea level density and speed of sound. * Make `Reng` a non `static` member (see #666). This was useless and prevented `Reng` from being accessed when JSBSim was compiled as a DLL. * Forgot `JSBSIM_API`. * Increased the code coverage and fixed some bugs in the process. --- src/models/FGAtmosphere.cpp | 25 +- src/models/FGAtmosphere.h | 34 +- tests/unit_tests/CMakeLists.txt | 3 +- tests/unit_tests/FGAtmosphereTest.h | 614 ++++++++++++++++++++++++++++ 4 files changed, 650 insertions(+), 26 deletions(-) create mode 100644 tests/unit_tests/FGAtmosphereTest.h diff --git a/src/models/FGAtmosphere.cpp b/src/models/FGAtmosphere.cpp index ac2251ee61..4816402e74 100644 --- a/src/models/FGAtmosphere.cpp +++ b/src/models/FGAtmosphere.cpp @@ -51,15 +51,11 @@ namespace JSBSim { CLASS IMPLEMENTATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ -// Atmosphere constants in British units converted from the SI values specified in the +// Atmosphere constants in British units converted from the SI values specified in the // ISA document - https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770009539.pdf -double FGAtmosphere::Reng = Rstar / Mair; +const double FGAtmosphere::StdDaySLsoundspeed = sqrt(SHRatio*Reng0*StdDaySLtemperature); -const double FGAtmosphere::StdDaySLsoundspeed = sqrt(SHRatio*Reng*StdDaySLtemperature); - -FGAtmosphere::FGAtmosphere(FGFDMExec* fdmex) : FGModel(fdmex), - PressureAltitude(0.0), // ft - DensityAltitude(0.0) // ft +FGAtmosphere::FGAtmosphere(FGFDMExec* fdmex) : FGModel(fdmex) { Name = "FGAtmosphere"; @@ -80,11 +76,11 @@ bool FGAtmosphere::InitModel(void) { if (!FGModel::InitModel()) return false; - Calculate(0.0); SLtemperature = Temperature = StdDaySLtemperature; SLpressure = Pressure = StdDaySLpressure; SLdensity = Density = Pressure/(Reng*Temperature); SLsoundspeed = Soundspeed = StdDaySLsoundspeed; + Calculate(0.0); return true; } @@ -158,7 +154,7 @@ void FGAtmosphere::Calculate(double altitude) Pressure = ValidatePressure(p, "", true); if (!PropertyManager->HasNode("atmosphere/override/density")) - Density = GetDensity(altitude); + Density = Pressure/(Reng*Temperature); else Density = node->GetDouble("atmosphere/override/density"); @@ -177,6 +173,7 @@ void FGAtmosphere::SetPressureSL(ePressure unit, double pressure) double press = ConvertToPSF(pressure, unit); SLpressure = ValidatePressure(press, "Sea Level pressure"); + SLdensity = GetDensity(0.0); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -204,6 +201,8 @@ void FGAtmosphere::SetTemperatureSL(double t, eTemperature unit) double temp = ConvertToRankine(t, unit); SLtemperature = ValidateTemperature(temp, "Sea Level temperature"); + SLdensity = GetDensity(0.0); + SLsoundspeed = GetSoundSpeed(0.0); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -226,7 +225,7 @@ double FGAtmosphere::ConvertToRankine(double t, eTemperature unit) const targetTemp = t*1.8; break; default: - break; + throw BaseException("Undefined temperature unit given"); } return targetTemp; @@ -252,7 +251,7 @@ double FGAtmosphere::ConvertFromRankine(double t, eTemperature unit) const targetTemp = t/1.8; break; default: - break; + throw BaseException("Undefined temperature unit given"); } return targetTemp; @@ -278,7 +277,7 @@ double FGAtmosphere::ConvertToPSF(double p, ePressure unit) const targetPressure = p*70.7180803; break; default: - throw("Undefined pressure unit given"); + throw BaseException("Undefined pressure unit given"); } return targetPressure; @@ -302,7 +301,7 @@ double FGAtmosphere::ConvertFromPSF(double p, ePressure unit) const targetPressure = p/70.7180803; break; default: - throw("Undefined pressure unit given"); + throw BaseException("Undefined pressure unit given"); } return targetPressure; diff --git a/src/models/FGAtmosphere.h b/src/models/FGAtmosphere.h index e7f0320587..6a4f70feca 100644 --- a/src/models/FGAtmosphere.h +++ b/src/models/FGAtmosphere.h @@ -73,7 +73,7 @@ CLASS DOCUMENTATION CLASS DECLARATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ -class FGAtmosphere : public FGModel { +class JSBSIM_API FGAtmosphere : public FGModel { public: /// Enums for specifying temperature units. @@ -90,7 +90,7 @@ class FGAtmosphere : public FGModel { /** Runs the atmosphere forces model; called by the Executive. Can pass in a value indicating if the executive is directing the simulation to Hold. - @param Holding if true, the executive has been directed to hold the sim from + @param Holding if true, the executive has been directed to hold the sim from advancing time. Some models may ignore this flag, such as the Input model, which may need to be active to listen on a socket for the "Resume" command to be given. @@ -111,7 +111,7 @@ class FGAtmosphere : public FGModel { /// Returns the actual modeled temperature in degrees Rankine at a specified altitude. /// @param altitude The altitude above sea level (ASL) in feet. /// @return Modeled temperature in degrees Rankine at the specified altitude. - virtual double GetTemperature(double altitude) const = 0; + virtual double GetTemperature(double altitude) const = 0; /// Returns the actual, modeled sea level temperature in degrees Rankine. /// @return The modeled temperature in degrees Rankine at sea level. @@ -184,7 +184,7 @@ class FGAtmosphere : public FGModel { /// Returns the speed of sound in ft/sec at a given altitude in ft. virtual double GetSoundSpeed(double altitude) const; - + /// Returns the sea level speed of sound in ft/sec. virtual double GetSoundSpeedSL(void) const { return SLsoundspeed; } @@ -215,15 +215,23 @@ class FGAtmosphere : public FGModel { static const double StdDaySLsoundspeed; protected: - double SLtemperature, SLdensity, SLpressure, SLsoundspeed; // Sea level conditions - double Temperature, Density, Pressure, Soundspeed; // Current actual conditions at altitude - - double PressureAltitude; - double DensityAltitude; + // Sea level conditions + double SLtemperature = 1.8; + double SLdensity = 1.0; + double SLpressure = 1.0; + double SLsoundspeed = 1.0; + // Current actual conditions at altitude + double Temperature = 1.8; + double Density = 0.0; + double Pressure = 0.0; + double Soundspeed = 0.0; + double PressureAltitude = 0.0; + double DensityAltitude = 0.0; static constexpr double SutherlandConstant = 198.72; // deg Rankine static constexpr double Beta = 2.269690E-08; // slug/(sec ft R^0.5) - double Viscosity, KinematicViscosity; + double Viscosity = 0.0; + double KinematicViscosity = 0.0; /// Calculate the atmosphere for the given altitude. virtual void Calculate(double altitude); @@ -244,7 +252,7 @@ class FGAtmosphere : public FGModel { /// Converts to Rankine from one of several unit systems. double ConvertToRankine(double t, eTemperature unit) const; - + /// Converts from Rankine to one of several unit systems. double ConvertFromRankine(double t, eTemperature unit) const; @@ -277,11 +285,13 @@ class FGAtmosphere : public FGModel { */ static constexpr double g0 = 9.80665 / fttom; /// Specific gas constant for air - ft*lbf/slug/R - static double Reng; + static constexpr double Reng0 = Rstar / Mair; //@} static constexpr double SHRatio = 1.4; + double Reng = Reng0; + virtual void bind(void); void Debug(int from) override; }; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 8f83b31542..86d87ce661 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -17,7 +17,8 @@ set(UNIT_TESTS FGColumnVector3Test FGParameterTest FGParameterValueTest FGConditionTest - FGPropertyManagerTest) + FGPropertyManagerTest + FGAtmosphereTest) foreach(test ${UNIT_TESTS}) cxxtest_add_test(${test}1 ${test}.cpp ${CMAKE_CURRENT_SOURCE_DIR}/${test}.h) diff --git a/tests/unit_tests/FGAtmosphereTest.h b/tests/unit_tests/FGAtmosphereTest.h new file mode 100644 index 0000000000..2bf5fb78b2 --- /dev/null +++ b/tests/unit_tests/FGAtmosphereTest.h @@ -0,0 +1,614 @@ +#include +#include + +#include +#include + +const double epsilon = 100. * std::numeric_limits::epsilon(); + +using namespace JSBSim; + +class DummyAtmosphere : public FGAtmosphere +{ +public: + DummyAtmosphere(FGFDMExec* fdm, double t_lapse_rate, double p_lapse_rate) + : FGAtmosphere(fdm), a_t(t_lapse_rate), a_p(p_lapse_rate) + {} + + using FGAtmosphere::GetTemperature; + using FGAtmosphere::GetPressure; + + double GetTemperature(double altitude) const override + { + return ValidateTemperature(SLtemperature+a_t*altitude, "", true); + } + void SetTemperature(double t, double h, eTemperature unit) override + { + SetTemperatureSL(ConvertToRankine(t, unit)-a_t*h, eRankine); + } + double GetPressure(double altitude) const override + { + return ValidatePressure(SLpressure+a_p*altitude, "", true); + } + // Getters for the protected members + static constexpr double GetR(void) { return Reng0; } + static constexpr double GetGamma(void) { return SHRatio; } + static constexpr double GetBeta(void) { return Beta; } + static constexpr double GetSutherlandConstant(void) { return SutherlandConstant; } + static constexpr double GetPSFtoPa(void) { return psftopa; } + static constexpr double GetPSFtoInHg(void) { return psftoinhg; } +private: + double a_t, a_p; +}; + +constexpr double R = DummyAtmosphere::GetR(); +constexpr double gama = DummyAtmosphere::GetGamma(); +constexpr double beta = DummyAtmosphere::GetBeta(); +constexpr double k = DummyAtmosphere::GetSutherlandConstant(); +constexpr double psftopa = DummyAtmosphere::GetPSFtoPa(); +constexpr double psftombar = psftopa/100.; +constexpr double psftoinhg = DummyAtmosphere::GetPSFtoInHg(); + +class FGAtmosphereTest : public CxxTest::TestSuite +{ +public: + void testDefaultValuesBeforeInit() + { + FGFDMExec fdmex; + FGJSBBase::debug_lvl = 2; + auto atm = DummyAtmosphere(&fdmex, 1.0, 1.0); + + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), 1.8); + TS_ASSERT_EQUALS(atm.GetTemperature(), 1.8); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), 1.8); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(), 1.0); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + + TS_ASSERT_EQUALS(atm.GetPressureSL(), 1.0); + TS_ASSERT_EQUALS(atm.GetPressure(), 0.0); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), 1.0); + TS_ASSERT_EQUALS(atm.GetPressureRatio(), 0.0); + + const double rho = 1.0/(R*1.8); + TS_ASSERT_EQUALS(atm.GetDensitySL(), 1.0); + TS_ASSERT_EQUALS(atm.GetDensity(), 0.0); + TS_ASSERT_EQUALS(atm.GetDensity(0.0), rho); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), 0.0); + + const double a = sqrt(gama*R*1.8); + TS_ASSERT_EQUALS(atm.GetSoundSpeedSL(), 1.0); + TS_ASSERT_EQUALS(atm.GetSoundSpeed(), 0.0); + TS_ASSERT_EQUALS(atm.GetSoundSpeed(0.0), a); + TS_ASSERT_EQUALS(atm.GetSoundSpeedRatio(), 0.0); + + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), 0.0); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), 0.0); + + TS_ASSERT_EQUALS(atm.GetAbsoluteViscosity(), 0.0); + TS_ASSERT_EQUALS(atm.GetKinematicViscosity(), 0.0); + } + + void testDefaultValuesAfterInit() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, 1.0, 1.0); + + TS_ASSERT(atm.InitModel()); + + const double T0 = FGAtmosphere::StdDaySLtemperature; + const double P0 = FGAtmosphere::StdDaySLpressure; + + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), T0); + TS_ASSERT_EQUALS(atm.GetTemperature(), T0); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), T0); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(), 1.0); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + TS_ASSERT_EQUALS(atm.GetPressureSL(), P0); + TS_ASSERT_EQUALS(atm.GetPressure(), P0); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), P0); + TS_ASSERT_EQUALS(atm.GetPressureRatio(), 1.0); + + const double SLdensity = P0/(R*T0); + TS_ASSERT_EQUALS(atm.GetDensity(), SLdensity); + TS_ASSERT_EQUALS(atm.GetDensity(0.0), SLdensity); + TS_ASSERT_EQUALS(atm.GetDensitySL(), SLdensity); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), 1.0); + + const double SLsoundspeed = sqrt(gama*R*T0); + TS_ASSERT_EQUALS(atm.GetSoundSpeed(), SLsoundspeed); + TS_ASSERT_EQUALS(atm.GetSoundSpeed(0.0), SLsoundspeed); + TS_ASSERT_EQUALS(atm.GetSoundSpeedSL(), SLsoundspeed); + TS_ASSERT_EQUALS(atm.GetSoundSpeedRatio(), 1.0); + + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), 0.0); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), 0.0); + + const double mu = beta*T0*sqrt(T0)/(k+T0); + const double nu = mu/SLdensity; + TS_ASSERT_DELTA(atm.GetAbsoluteViscosity(), mu, epsilon); + TS_ASSERT_DELTA(atm.GetKinematicViscosity(), nu, epsilon); + } + + void testGetAltitudeParameters() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + constexpr double T0 = FGAtmosphere::StdDaySLtemperature; + constexpr double P0 = FGAtmosphere::StdDaySLpressure; + constexpr double rho0 = P0/(R*T0); + const double a0 = sqrt(gama*R*T0); + const double mu0 = beta*T0*sqrt(T0)/(k+T0); + const double nu0 = mu0/rho0; + + for(double h=-1000.0; h<10000; h+= 1000) { + double T = T0 + 0.1*h; + double P = P0 + 1.0*h; + + TS_ASSERT_DELTA(atm.GetTemperature(h), T, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), T0); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(h), T/T0, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + TS_ASSERT_DELTA(atm.GetPressure(h), P, epsilon); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), P0); + + double rho = P/(R*T); + TS_ASSERT_DELTA(atm.GetDensity(h), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(0.0), rho0, epsilon); + + double a = sqrt(gama*R*T); + TS_ASSERT_DELTA(atm.GetSoundSpeed(h), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(0.0), a0, epsilon); + + // Local values must remain unchanged + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), T0); + TS_ASSERT_EQUALS(atm.GetTemperature(), T0); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(), 1.0); + TS_ASSERT_EQUALS(atm.GetPressureSL(), P0); + TS_ASSERT_EQUALS(atm.GetPressure(), P0); + TS_ASSERT_EQUALS(atm.GetPressureRatio(), 1.0); + TS_ASSERT_DELTA(atm.GetDensity(), rho0, epsilon); + TS_ASSERT_DELTA(atm.GetDensitySL(), rho0, epsilon); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), 1.0); + TS_ASSERT_DELTA(atm.GetSoundSpeed(), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedSL(), a0, epsilon); + TS_ASSERT_EQUALS(atm.GetSoundSpeedRatio(), 1.0); + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), 0.0); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), 0.0); + TS_ASSERT_DELTA(atm.GetAbsoluteViscosity(), mu0, epsilon); + TS_ASSERT_DELTA(atm.GetKinematicViscosity(), nu0, epsilon); + } + } + + void testRun() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + constexpr double T0 = FGAtmosphere::StdDaySLtemperature; + constexpr double P0 = FGAtmosphere::StdDaySLpressure; + constexpr double rho0 = P0/(R*T0); + const double a0 = sqrt(gama*R*T0); + + for(double h=-1000.0; h<10000; h+= 1000) { + atm.in.altitudeASL = h; + TS_ASSERT(atm.Run(false) == false); + + double T = T0 + 0.1*h; + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), T0); + TS_ASSERT_DELTA(atm.GetTemperature(), T, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), T0); + TS_ASSERT_DELTA(atm.GetTemperature(h), T, epsilon); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(), T/T0, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(h), T/T0, epsilon); + + double P = P0 + 1.0*h; + TS_ASSERT_EQUALS(atm.GetPressureSL(), P0); + TS_ASSERT_DELTA(atm.GetPressure(), P, epsilon); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), P0); + TS_ASSERT_DELTA(atm.GetPressure(h), P, epsilon); + TS_ASSERT_DELTA(atm.GetPressureRatio(), P/P0, epsilon); + + double rho = P/(R*T); + TS_ASSERT_DELTA(atm.GetDensity(), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(0.0), rho0, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(h), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensitySL(), rho0, epsilon); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), rho/rho0); + + double a = sqrt(gama*R*T); + TS_ASSERT_DELTA(atm.GetSoundSpeed(), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(0.0), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(h), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedSL(), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedRatio(), a/a0, epsilon); + + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), h); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), h); + + double mu = beta*T*sqrt(T)/(k+T); + double nu = mu/rho; + TS_ASSERT_DELTA(atm.GetAbsoluteViscosity(), mu, epsilon); + TS_ASSERT_DELTA(atm.GetKinematicViscosity(), nu, epsilon); + } + } + + void testTemperatureOverride() + { + FGFDMExec fdmex; + auto pm = fdmex.GetPropertyManager(); + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + constexpr double T0 = FGAtmosphere::StdDaySLtemperature; + constexpr double P0 = FGAtmosphere::StdDaySLpressure; + constexpr double rho0 = P0/(R*T0); + const double a0 = sqrt(gama*R*T0); + + auto t_node = pm->GetNode("atmosphere/override/temperature", true); + const double T = 300.0; + t_node->setDoubleValue(T); + + for(double h=-1000.0; h<10000; h+= 1000) { + atm.in.altitudeASL = h; + TS_ASSERT(atm.Run(false) == false); + + double Tz = T0+0.1*h; + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), T0); + TS_ASSERT_DELTA(atm.GetTemperature(), T, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), T0); + TS_ASSERT_DELTA(atm.GetTemperature(h), Tz, epsilon); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(), T/T0, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(h), 1.0+0.1*h/T0, epsilon); + + double P = P0 + 1.0*h; + TS_ASSERT_EQUALS(atm.GetPressureSL(), P0); + TS_ASSERT_DELTA(atm.GetPressure(), P, epsilon); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), P0); + TS_ASSERT_DELTA(atm.GetPressure(h), P, epsilon); + TS_ASSERT_DELTA(atm.GetPressureRatio(), P/P0, epsilon); + + double rho = P/(R*T); + TS_ASSERT_DELTA(atm.GetDensity(), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(0.0), rho0, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(h), P/(R*Tz), epsilon); + TS_ASSERT_DELTA(atm.GetDensitySL(), rho0, epsilon); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), rho/rho0); + + double a = sqrt(gama*R*T); + TS_ASSERT_DELTA(atm.GetSoundSpeed(), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(0.0), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(h), sqrt(gama*R*Tz), epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedSL(), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedRatio(), a/a0, epsilon); + + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), h); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), h); + + double mu = beta*T*sqrt(T)/(k+T); + double nu = mu/rho; + TS_ASSERT_DELTA(atm.GetAbsoluteViscosity(), mu, epsilon); + TS_ASSERT_DELTA(atm.GetKinematicViscosity(), nu, epsilon); + } + } + + void testPressureOverride() + { + FGFDMExec fdmex; + auto pm = fdmex.GetPropertyManager(); + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + constexpr double T0 = FGAtmosphere::StdDaySLtemperature; + constexpr double P0 = FGAtmosphere::StdDaySLpressure; + constexpr double rho0 = P0/(R*T0); + const double a0 = sqrt(gama*R*T0); + + auto p_node = pm->GetNode("atmosphere/override/pressure", true); + const double P = 3000.0; + p_node->setDoubleValue(P); + + for(double h=-1000.0; h<10000; h+= 1000) { + atm.in.altitudeASL = h; + TS_ASSERT(atm.Run(false) == false); + + double T = T0 + 0.1*h; + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), T0); + TS_ASSERT_DELTA(atm.GetTemperature(), T, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), T0); + TS_ASSERT_DELTA(atm.GetTemperature(h), T, epsilon); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(), T/T0, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(h), T/T0, epsilon); + + TS_ASSERT_EQUALS(atm.GetPressureSL(), P0); + TS_ASSERT_DELTA(atm.GetPressure(), P, epsilon); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), P0); + TS_ASSERT_DELTA(atm.GetPressure(h), P0+h, epsilon); + TS_ASSERT_DELTA(atm.GetPressureRatio(), P/P0, epsilon); + + double rho = P/(R*T); + TS_ASSERT_DELTA(atm.GetDensity(), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(0.0), rho0, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(h), (P0+h)/(R*T), epsilon); + TS_ASSERT_DELTA(atm.GetDensitySL(), rho0, epsilon); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), rho/rho0); + + double a = sqrt(gama*R*T); + TS_ASSERT_DELTA(atm.GetSoundSpeed(), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(0.0), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(h), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedSL(), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedRatio(), a/a0, epsilon); + + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), h); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), h); + + double mu = beta*T*sqrt(T)/(k+T); + double nu = mu/rho; + TS_ASSERT_DELTA(atm.GetAbsoluteViscosity(), mu, epsilon); + TS_ASSERT_DELTA(atm.GetKinematicViscosity(), nu, epsilon); + } + } + + void testDensityOverride() + { + FGFDMExec fdmex; + auto pm = fdmex.GetPropertyManager(); + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + constexpr double T0 = FGAtmosphere::StdDaySLtemperature; + constexpr double P0 = FGAtmosphere::StdDaySLpressure; + constexpr double rho0 = P0/(R*T0); + const double a0 = sqrt(gama*R*T0); + + auto rho_node = pm->GetNode("atmosphere/override/density", true); + const double rho = 3000.0; + rho_node->setDoubleValue(rho); + + for(double h=-1000.0; h<10000; h+= 1000) { + atm.in.altitudeASL = h; + TS_ASSERT(atm.Run(false) == false); + + double T = T0 + 0.1*h; + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), T0); + TS_ASSERT_DELTA(atm.GetTemperature(), T, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), T0); + TS_ASSERT_DELTA(atm.GetTemperature(h), T, epsilon); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(), T/T0, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(h), T/T0, epsilon); + + double P = P0 + 1.0*h; + TS_ASSERT_EQUALS(atm.GetPressureSL(), P0); + TS_ASSERT_DELTA(atm.GetPressure(), P, epsilon); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), P0); + TS_ASSERT_DELTA(atm.GetPressure(h), P, epsilon); + TS_ASSERT_DELTA(atm.GetPressureRatio(), P/P0, epsilon); + + TS_ASSERT_DELTA(atm.GetDensity(), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(0.0), rho0, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(h), P/(R*T), epsilon); + TS_ASSERT_DELTA(atm.GetDensitySL(), rho0, epsilon); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), rho/rho0); + + double a = sqrt(gama*R*T); + TS_ASSERT_DELTA(atm.GetSoundSpeed(), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(0.0), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(h), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedSL(), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedRatio(), a/a0, epsilon); + + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), h); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), h); + + double mu = beta*T*sqrt(T)/(k+T); + double nu = mu/rho; + TS_ASSERT_DELTA(atm.GetAbsoluteViscosity(), mu, epsilon); + TS_ASSERT_DELTA(atm.GetKinematicViscosity(), nu, epsilon); + } + } + + void testSetTemperatureSL() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + constexpr double T0 = 300.0; + constexpr double P0 = FGAtmosphere::StdDaySLpressure; + constexpr double rho0 = P0/(R*T0); + const double a0 = sqrt(gama*R*T0); + + atm.SetTemperatureSL(T0, FGAtmosphere::eRankine); + + for(double h=-1000.0; h<10000; h+= 1000) { + atm.in.altitudeASL = h; + TS_ASSERT(atm.Run(false) == false); + + double T = T0+0.1*h; + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), T0); + TS_ASSERT_DELTA(atm.GetTemperature(), T, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), T0); + TS_ASSERT_DELTA(atm.GetTemperature(h), T, epsilon); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(), T/T0, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(h), T/T0, epsilon); + + double P = P0 + 1.0*h; + TS_ASSERT_EQUALS(atm.GetPressureSL(), P0); + TS_ASSERT_DELTA(atm.GetPressure(), P, epsilon); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), P0); + TS_ASSERT_DELTA(atm.GetPressure(h), P, epsilon); + TS_ASSERT_DELTA(atm.GetPressureRatio(), P/P0, epsilon); + + double rho = P/(R*T); + TS_ASSERT_DELTA(atm.GetDensity(), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(0.0), rho0, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(h), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensitySL(), rho0, epsilon); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), rho/rho0); + + double a = sqrt(gama*R*T); + TS_ASSERT_DELTA(atm.GetSoundSpeed(), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(0.0), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(h), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedSL(), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedRatio(), a/a0, epsilon); + + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), h); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), h); + + double mu = beta*T*sqrt(T)/(k+T); + double nu = mu/rho; + TS_ASSERT_DELTA(atm.GetAbsoluteViscosity(), mu, epsilon); + TS_ASSERT_DELTA(atm.GetKinematicViscosity(), nu, epsilon); + } + } + + void testSetPressureSL() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + constexpr double T0 = FGAtmosphere::StdDaySLtemperature; + constexpr double P0 = 3000.0; + constexpr double rho0 = P0/(R*T0); + const double a0 = sqrt(gama*R*T0); + + atm.SetPressureSL(FGAtmosphere::ePSF, P0); + + for(double h=-1000.0; h<10000; h+= 1000) { + atm.in.altitudeASL = h; + TS_ASSERT(atm.Run(false) == false); + + double T = T0+0.1*h; + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), T0); + TS_ASSERT_DELTA(atm.GetTemperature(), T, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperature(0.0), T0); + TS_ASSERT_DELTA(atm.GetTemperature(h), T, epsilon); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(), T/T0, epsilon); + TS_ASSERT_EQUALS(atm.GetTemperatureRatio(0.0), 1.0); + TS_ASSERT_DELTA(atm.GetTemperatureRatio(h), T/T0, epsilon); + + double P = P0 + 1.0*h; + TS_ASSERT_EQUALS(atm.GetPressureSL(), P0); + TS_ASSERT_DELTA(atm.GetPressure(), P, epsilon); + TS_ASSERT_EQUALS(atm.GetPressure(0.0), P0); + TS_ASSERT_DELTA(atm.GetPressure(h), P, epsilon); + TS_ASSERT_DELTA(atm.GetPressureRatio(), P/P0, epsilon); + + double rho = P/(R*T); + TS_ASSERT_DELTA(atm.GetDensity(), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(0.0), rho0, epsilon); + TS_ASSERT_DELTA(atm.GetDensity(h), rho, epsilon); + TS_ASSERT_DELTA(atm.GetDensitySL(), rho0, epsilon); + TS_ASSERT_EQUALS(atm.GetDensityRatio(), rho/rho0); + + double a = sqrt(gama*R*T); + TS_ASSERT_DELTA(atm.GetSoundSpeed(), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(0.0), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeed(h), a, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedSL(), a0, epsilon); + TS_ASSERT_DELTA(atm.GetSoundSpeedRatio(), a/a0, epsilon); + + TS_ASSERT_EQUALS(atm.GetDensityAltitude(), h); + TS_ASSERT_EQUALS(atm.GetPressureAltitude(), h); + + double mu = beta*T*sqrt(T)/(k+T); + double nu = mu/rho; + TS_ASSERT_DELTA(atm.GetAbsoluteViscosity(), mu, epsilon); + TS_ASSERT_DELTA(atm.GetKinematicViscosity(), nu, epsilon); + } + } + + void testPressureConversion() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + double P0 = 900.0; // mbar + atm.SetPressureSL(FGAtmosphere::eMillibars, P0); + TS_ASSERT_DELTA(atm.GetPressureSL()*psftombar / P0, 1.0, 1e-5); + TS_ASSERT_DELTA(atm.GetPressureSL(FGAtmosphere::eMillibars) / P0, 1.0, 1e-5); + + P0 *= 100.0; // Pa + atm.SetPressureSL(FGAtmosphere::ePascals, P0); + TS_ASSERT_DELTA(atm.GetPressureSL()*psftopa / P0, 1.0, 1e-5); + TS_ASSERT_DELTA(atm.GetPressureSL(FGAtmosphere::ePascals) / P0, 1.0, 1e-5); + + P0 = 25.0; // inHg + atm.SetPressureSL(FGAtmosphere::eInchesHg, P0); + TS_ASSERT_DELTA(atm.GetPressureSL()*psftoinhg / P0, 1.0, 1e-3); + TS_ASSERT_DELTA(atm.GetPressureSL(FGAtmosphere::eInchesHg) / P0, 1.0, 1e-3); + + // Illegal units + TS_ASSERT_THROWS(atm.SetPressureSL(FGAtmosphere::eNoPressUnit, P0), BaseException&); + TS_ASSERT_THROWS(atm.GetPressureSL(FGAtmosphere::eNoPressUnit), BaseException&); + } + + void testTemperatureConversion() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, 0.1, 1.0); + TS_ASSERT(atm.InitModel()); + + double T0 = 250.0; // K + atm.SetTemperatureSL(T0, FGAtmosphere::eKelvin); + TS_ASSERT_DELTA(atm.GetTemperatureSL()*5.0/9.0, T0, epsilon); + + T0 = -30.0; // Celsius + atm.SetTemperatureSL(T0, FGAtmosphere::eCelsius); + TS_ASSERT_DELTA(atm.GetTemperatureSL()*5.0/9.0-273.15, T0, epsilon); + + T0 = 10.0; // Fahrenheit + atm.SetTemperatureSL(T0, FGAtmosphere::eFahrenheit); + TS_ASSERT_DELTA(atm.GetTemperatureSL()-459.67, T0, epsilon); + + // Illegal units + TS_ASSERT_THROWS(atm.SetTemperatureSL(T0, FGAtmosphere::eNoTempUnit), BaseException&); + } + + void testAltitudeParametersValidation() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, -1.0, -100.0); + TS_ASSERT(atm.InitModel()); + + atm.in.altitudeASL = 1000; + TS_ASSERT(atm.Run(false) == false); + + TS_ASSERT_EQUALS(atm.GetTemperature(), 1.8); + TS_ASSERT_DELTA(atm.GetPressure()*psftopa*1e15, 1.0, 1e-5); + } + + void testSeaLevelParametersValidation() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, -1.0, -100.0); + TS_ASSERT(atm.InitModel()); + + atm.SetTemperatureSL(0.0, FGAtmosphere::eKelvin); + TS_ASSERT_EQUALS(atm.GetTemperatureSL(), 1.8); + + atm.SetPressureSL(FGAtmosphere::ePascals, 0.0); + TS_ASSERT_DELTA(atm.GetPressureSL()*psftopa*1e15, 1.0, 1e-5); + } + + void testProbeAtADifferentAltitude() + { + FGFDMExec fdmex; + auto atm = DummyAtmosphere(&fdmex, -1.0, -100.0); + TS_ASSERT(atm.InitModel()); + + TS_ASSERT_EQUALS(atm.GetTemperature(1000.), 1.8); + TS_ASSERT_DELTA(atm.GetPressure(1000.)*psftopa*1e15, 1.0, 1e-5); + } +};