39#include "models/FGMassBalance.h"
41#include "input_output/FGXMLElement.h"
42#include "input_output/FGLog.h"
53const double FGGasCell::R = 3.4071;
54const double FGGasCell::M_air = 0.0019186;
55const double FGGasCell::M_hydrogen = 0.00013841;
56const double FGGasCell::M_helium = 0.00027409;
59 const struct Inputs& input)
68 Buoyancy = MaxVolume = MaxOverpressure = Temperature = Pressure =
69 Contents = Volume = dVolumeIdeal = 0.0;
70 Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
71 ValveCoefficient = ValveOpen = 0.0;
75 SetTransformType(FGForce::tLocalBody);
78 if (type ==
"HYDROGEN") Type = ttHYDROGEN;
79 else if (type ==
"HELIUM") Type = ttHELIUM;
80 else if (type ==
"AIR") Type = ttAIR;
81 else Type = ttUNKNOWN;
88 err <<
"\nFatal Error: No location found for this gas cell.\n";
118 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
119 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
121 MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
122 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
123 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
125 MaxVolume = M_PI * Yradius * Zradius * Xwidth;
128 log <<
"Unsupported gas cell shape.\n";
130 (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
131 M_PI * Yradius * Zradius * Xwidth +
132 M_PI * Xradius * Zradius * Ywidth +
133 M_PI * Xradius * Yradius * Zwidth +
134 2.0 * Xradius * Ywidth * Zwidth +
135 2.0 * Yradius * Xwidth * Zwidth +
136 2.0 * Zradius * Xwidth * Ywidth +
137 Xwidth * Ywidth * Zwidth);
141 err <<
"\nGas cell shape must be given.\n";
151 Volume = Fullness * MaxVolume;
154 log <<
"Invalid initial gas cell fullness value.\n";
161 ValveCoefficient = max(ValveCoefficient, 0.0);
167 if (Temperature == 0.0) {
168 Temperature = in.Temperature;
170 if (Pressure == 0.0) {
171 Pressure = in.Pressure;
175 Contents = Pressure * Volume / (R * Temperature);
178 const double IdealPressure = Contents * R * Temperature / MaxVolume;
179 if (IdealPressure > Pressure + MaxOverpressure) {
180 Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
181 Pressure = Pressure + MaxOverpressure;
183 Pressure = max(IdealPressure, Pressure);
187 Contents = Pressure * MaxVolume / (R * Temperature);
190 Volume = Contents * R * Temperature / Pressure;
191 Mass = Contents * M_gas();
194 string property_name, base_property_name;
196 base_property_name = CreateIndexedPropertyName(
"buoyant_forces/gas-cell", CellNum);
198 property_name = base_property_name +
"/max_volume-ft3";
199 PropertyManager->Tie( property_name.c_str(), &MaxVolume);
200 PropertyManager->GetNode(property_name)->setAttribute( SGPropertyNode::WRITE,
false );
201 property_name = base_property_name +
"/temp-R";
202 PropertyManager->Tie( property_name.c_str(), &Temperature);
203 property_name = base_property_name +
"/pressure-psf";
204 PropertyManager->Tie( property_name.c_str(), &Pressure);
205 property_name = base_property_name +
"/volume-ft3";
206 PropertyManager->Tie( property_name.c_str(), &Volume);
207 property_name = base_property_name +
"/buoyancy-lbs";
208 PropertyManager->Tie( property_name.c_str(), &Buoyancy);
209 property_name = base_property_name +
"/contents-mol";
210 PropertyManager->Tie( property_name.c_str(), &Contents);
211 property_name = base_property_name +
"/valve_open";
212 PropertyManager->Tie( property_name.c_str(), &ValveOpen);
219 while (function_element) {
220 HeatTransferCoeff.push_back(
new FGFunction(exec, function_element));
227 while (ballonet_element) {
240FGGasCell::~FGGasCell()
244 for (i = 0; i < HeatTransferCoeff.size(); i++)
delete HeatTransferCoeff[i];
245 HeatTransferCoeff.clear();
247 for (i = 0; i < Ballonet.size(); i++)
delete Ballonet[i];
257 const double AirTemperature = in.Temperature;
258 const double AirPressure = in.Pressure;
259 const double AirDensity = in.Density;
260 const double g = in.gravity;
262 const double OldTemperature = Temperature;
263 const double OldPressure = Pressure;
265 const size_t no_ballonets = Ballonet.size();
269 double BallonetsVolume = 0.0;
270 double BallonetsHeatFlow = 0.0;
271 for (i = 0; i < no_ballonets; i++) {
272 BallonetsVolume += Ballonet[i]->GetVolume();
273 BallonetsHeatFlow += Ballonet[i]->GetHeatFlow();
278 if (!HeatTransferCoeff.empty()) {
283 for (i = 0; i < HeatTransferCoeff.size(); i++) {
284 dU += HeatTransferCoeff[i]->GetValue();
290 (dU * dt - Pressure * dVolumeIdeal - BallonetsHeatFlow) /
291 (Cv_gas() * Contents * R);
293 Temperature = AirTemperature;
299 Temperature = AirTemperature;
303 const double IdealPressure =
304 Contents * R * Temperature / (MaxVolume - BallonetsVolume);
305 if (IdealPressure > AirPressure + MaxOverpressure) {
306 Pressure = AirPressure + MaxOverpressure;
308 Pressure = max(IdealPressure, AirPressure);
316 if ((ValveCoefficient > 0.0) && (ValveOpen > 0.0)) {
320 const double CellHeight = 2 * Zradius + Zwidth;
321 const double GasMass = Contents * M_gas();
322 const double GasVolume = Contents * R * Temperature / Pressure;
323 const double GasDensity = GasMass / GasVolume;
324 const double DeltaPressure =
325 Pressure + CellHeight * g * (AirDensity - GasDensity) - AirPressure;
326 const double VolumeValved =
327 ValveOpen * ValveCoefficient * DeltaPressure * dt;
329 max(1E-8, Contents - Pressure * VolumeValved / (R * Temperature));
335 BallonetsVolume = 0.0;
336 for (i = 0; i < no_ballonets; i++) {
337 Ballonet[i]->Calculate(dt);
338 BallonetsVolume += Ballonet[i]->GetVolume();
342 if (Contents * R * Temperature / (MaxVolume - BallonetsVolume) >
343 AirPressure + MaxOverpressure) {
347 (AirPressure + MaxOverpressure) *
348 (MaxVolume - BallonetsVolume) / (R * Temperature);
352 Volume = Contents * R * Temperature / Pressure + BallonetsVolume;
354 Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
358 Buoyancy = Volume * AirDensity * g;
363 vFn = {0.0, 0.0, - Buoyancy};
370 const double mass = Contents * M_gas();
371 double Ixx, Iyy, Izz;
372 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
373 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
375 Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
376 Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
377 Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
378 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
379 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
381 Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
383 (1.0 / 4.0) * mass * Yradius * Zradius +
384 (1.0 / 12.0) * mass * Xwidth * Xwidth;
386 (1.0 / 4.0) * mass * Yradius * Zradius +
387 (1.0 / 12.0) * mass * Xwidth * Xwidth;
390 Ixx = Iyy = Izz = 0.0;
398 gasCellJ += MassBalance->GetPointmassInertia(Mass,
GetXYZ());
400 gasCellM.InitMatrix();
408 if (no_ballonets > 0) {
410 for (i = 0; i < no_ballonets; i++) {
411 Mass += Ballonet[i]->GetMass();
415 Ballonet[i]->GetXYZ(eX) * Ballonet[i]->GetMass()*
slugtolb;
417 Ballonet[i]->GetXYZ(eY) * Ballonet[i]->GetMass()*
slugtolb;
419 Ballonet[i]->GetXYZ(eZ) * Ballonet[i]->GetMass()*
slugtolb;
421 gasCellJ += Ballonet[i]->GetInertia();
445void FGGasCell::Debug(
int from)
447 if (debug_lvl <= 0)
return;
452 log <<
" Gas cell holds " << std::fixed << Contents <<
" mol " << type <<
"\n";
453 log <<
" Cell location (X, Y, Z) (in.): " << vXYZ(eX) <<
", "
454 << vXYZ(eY) <<
", " << vXYZ(eZ) <<
"\n";
455 log <<
" Maximum volume: " << MaxVolume <<
" ft3\n";
456 log <<
" Relief valve release pressure: " << MaxOverpressure
458 log <<
" Manual valve coefficient: " << ValveCoefficient
459 <<
" ft4*sec/slug\n";
460 log <<
" Initial temperature: " << Temperature <<
" Rankine\n";
461 log <<
" Initial pressure: " << Pressure <<
" lbs/ft2\n";
462 log <<
" Initial volume: " << Volume <<
" ft3\n";
463 log <<
" Initial mass: " <<
GetMass() <<
" slug mass\n";
465 log <<
" Heat transfer: \n";
468 if (debug_lvl & 2 ) {
469 FGLogging log(LogLevel::DEBUG);
470 if (from == 0) log <<
"Instantiated: FGGasCell\n";
471 if (from == 1) log <<
"Destroyed: FGGasCell\n";
473 if (debug_lvl & 4 ) {
475 if (debug_lvl & 8 ) {
476 FGLogging log(LogLevel::DEBUG);
477 log <<
" " << type <<
" cell holds " << std::fixed << Contents <<
" mol\n";
478 log <<
" Temperature: " << Temperature <<
" Rankine\n";
479 log <<
" Pressure: " << Pressure <<
" lbs/ft2\n";
480 log <<
" Volume: " << Volume <<
" ft3\n";
481 log <<
" Mass: " <<
GetMass() <<
" slug mass\n";
484 if (debug_lvl & 16) {
486 if (debug_lvl & 64) {
495const double FGBallonet::R = 3.4071;
496const double FGBallonet::M_air = 0.0019186;
497const double FGBallonet::Cv_air = 5.0/2.0;
499FGBallonet::FGBallonet(FGFDMExec* exec, Element* el,
unsigned int num,
500 FGGasCell* parent,
const struct FGGasCell::Inputs& input)
506 auto PropertyManager = exec->GetPropertyManager();
507 MassBalance = exec->GetMassBalance();
509 MaxVolume = MaxOverpressure = Temperature = Pressure =
510 Contents = Volume = dVolumeIdeal = dU = 0.0;
511 Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
512 ValveCoefficient = ValveOpen = 0.0;
518 element = el->FindElement(
"location");
520 vXYZ = element->FindElementTripletConvertTo(
"IN");
522 XMLLogException err(el);
523 err <<
"\nFatal Error: No location found for this ballonet.\n";
526 if ((el->FindElement(
"x_radius") || el->FindElement(
"x_width")) &&
527 (el->FindElement(
"y_radius") || el->FindElement(
"y_width")) &&
528 (el->FindElement(
"z_radius") || el->FindElement(
"z_width"))) {
530 if (el->FindElement(
"x_radius")) {
531 Xradius = el->FindElementValueAsNumberConvertTo(
"x_radius",
"FT");
533 if (el->FindElement(
"y_radius")) {
534 Yradius = el->FindElementValueAsNumberConvertTo(
"y_radius",
"FT");
536 if (el->FindElement(
"z_radius")) {
537 Zradius = el->FindElementValueAsNumberConvertTo(
"z_radius",
"FT");
540 if (el->FindElement(
"x_width")) {
541 Xwidth = el->FindElementValueAsNumberConvertTo(
"x_width",
"FT");
543 if (el->FindElement(
"y_width")) {
544 Ywidth = el->FindElementValueAsNumberConvertTo(
"y_width",
"FT");
546 if (el->FindElement(
"z_width")) {
547 Zwidth = el->FindElementValueAsNumberConvertTo(
"z_width",
"FT");
553 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
554 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
556 MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
557 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
558 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
560 MaxVolume = M_PI * Yradius * Zradius * Xwidth;
562 FGXMLLogging log(el, LogLevel::WARN);
563 log <<
"Unsupported ballonet shape.\n";
565 (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
566 M_PI * Yradius * Zradius * Xwidth +
567 M_PI * Xradius * Zradius * Ywidth +
568 M_PI * Xradius * Yradius * Zwidth +
569 2.0 * Xradius * Ywidth * Zwidth +
570 2.0 * Yradius * Xwidth * Zwidth +
571 2.0 * Zradius * Xwidth * Ywidth +
572 Xwidth * Ywidth * Zwidth);
575 XMLLogException err(el);
576 err <<
"\nFatal Error: Ballonet shape must be given.\n";
579 if (el->FindElement(
"max_overpressure")) {
580 MaxOverpressure = el->FindElementValueAsNumberConvertTo(
"max_overpressure",
583 if (el->FindElement(
"fullness")) {
584 const double Fullness = el->FindElementValueAsNumber(
"fullness");
586 Volume = Fullness * MaxVolume;
588 FGXMLLogging log(el, LogLevel::WARN);
589 log <<
"Invalid initial ballonet fullness value.\n";
592 if (el->FindElement(
"valve_coefficient")) {
594 el->FindElementValueAsNumberConvertTo(
"valve_coefficient",
596 ValveCoefficient = max(ValveCoefficient, 0.0);
600 if (Temperature == 0.0) {
601 Temperature = Parent->GetTemperature();
603 if (Pressure == 0.0) {
604 Pressure = Parent->GetPressure();
608 Contents = Pressure * Volume / (R * Temperature);
611 const double IdealPressure = Contents * R * Temperature / MaxVolume;
612 if (IdealPressure > Pressure + MaxOverpressure) {
613 Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
614 Pressure = Pressure + MaxOverpressure;
616 Pressure = max(IdealPressure, Pressure);
620 Contents = Pressure * MaxVolume / (R * Temperature);
623 Volume = Contents * R * Temperature / Pressure;
626 string property_name, base_property_name;
627 base_property_name = CreateIndexedPropertyName(
"buoyant_forces/gas-cell", Parent->GetIndex());
628 base_property_name = CreateIndexedPropertyName(base_property_name +
"/ballonet", CellNum);
630 property_name = base_property_name +
"/max_volume-ft3";
631 PropertyManager->Tie( property_name, &MaxVolume);
632 PropertyManager->GetNode(property_name)->setAttribute( SGPropertyNode::WRITE,
false );
634 property_name = base_property_name +
"/temp-R";
635 PropertyManager->Tie( property_name, &Temperature);
637 property_name = base_property_name +
"/pressure-psf";
638 PropertyManager->Tie( property_name, &Pressure);
640 property_name = base_property_name +
"/volume-ft3";
641 PropertyManager->Tie( property_name, &Volume);
643 property_name = base_property_name +
"/contents-mol";
644 PropertyManager->Tie( property_name, &Contents);
646 property_name = base_property_name +
"/valve_open";
647 PropertyManager->Tie( property_name, &ValveOpen);
652 if (Element* heat = el->FindElement(
"heat")) {
653 Element* function_element = heat->FindElement(
"function");
654 while (function_element) {
655 HeatTransferCoeff.push_back(
new FGFunction(exec, function_element));
656 function_element = heat->FindNextElement(
"function");
660 if (Element* blower = el->FindElement(
"blower_input")) {
661 Element* function_element = blower->FindElement(
"function");
662 BlowerInput =
new FGFunction(exec, function_element);
668FGBallonet::~FGBallonet()
672 for (i = 0; i < HeatTransferCoeff.size(); i++)
delete HeatTransferCoeff[i];
673 HeatTransferCoeff.clear();
685 const double ParentPressure = Parent->
GetPressure();
686 const double AirPressure = in.Pressure;
688 const double OldTemperature = Temperature;
689 const double OldPressure = Pressure;
698 for (i = 0; i < HeatTransferCoeff.size(); i++) {
699 dU += HeatTransferCoeff[i]->GetValue();
704 (dU * dt - Pressure * dVolumeIdeal) / (Cv_air * Contents * R);
710 const double IdealPressure = Contents * R * Temperature / MaxVolume;
712 Pressure = max(IdealPressure, ParentPressure);
716 const double AddedVolume = BlowerInput->
GetValue() * dt;
717 if (AddedVolume > 0.0) {
718 Contents += Pressure * AddedVolume / (R * Temperature);
726 if ((ValveCoefficient > 0.0) &&
727 ((ValveOpen > 0.0) || (Pressure > AirPressure + MaxOverpressure))) {
728 const double DeltaPressure = Pressure - AirPressure;
729 const double VolumeValved =
730 ((Pressure > AirPressure + MaxOverpressure) ? 1.0 : ValveOpen) *
731 ValveCoefficient * DeltaPressure * dt;
735 max(1.0, Contents - Pressure * VolumeValved / (R * Temperature));
739 Volume = Contents * R * Temperature / Pressure;
741 Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
748 const double mass = Contents * M_air;
749 double Ixx, Iyy, Izz;
750 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
751 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
753 Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
754 Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
755 Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
756 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
757 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
759 Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
761 (1.0 / 4.0) * mass * Yradius * Zradius +
762 (1.0 / 12.0) * mass * Xwidth * Xwidth;
764 (1.0 / 4.0) * mass * Yradius * Zradius +
765 (1.0 / 12.0) * mass * Xwidth * Xwidth;
768 Ixx = Iyy = Izz = 0.0;
771 ballonetJ(1,1) = Ixx;
772 ballonetJ(2,2) = Iyy;
773 ballonetJ(3,3) = Izz;
775 ballonetJ += MassBalance->GetPointmassInertia(
GetMass(),
GetXYZ());
797void FGBallonet::Debug(
int from)
799 if (debug_lvl <= 0)
return;
804 log <<
" Ballonet holds " << std::fixed << Contents <<
" mol air\n";
805 log <<
" Location (X, Y, Z) (in.): " << vXYZ(eX) <<
", "
806 << vXYZ(eY) <<
", " << vXYZ(eZ) <<
"\n";
807 log <<
" Maximum volume: " << MaxVolume <<
" ft3\n";
808 log <<
" Relief valve release pressure: " << MaxOverpressure
810 log <<
" Relief valve coefficient: " << ValveCoefficient
811 <<
" ft4*sec/slug\n";
812 log <<
" Initial temperature: " << Temperature <<
" Rankine\n";
813 log <<
" Initial pressure: " << Pressure <<
" lbs/ft2\n";
814 log <<
" Initial volume: " << Volume <<
" ft3\n";
815 log <<
" Initial mass: " <<
GetMass() <<
" slug mass\n";
818 log <<
" Heat transfer: \n";
821 if (debug_lvl & 2 ) {
822 FGLogging log(LogLevel::DEBUG);
823 if (from == 0) log <<
"Instantiated: FGBallonet\n";
824 if (from == 1) log <<
"Destroyed: FGBallonet\n";
826 if (debug_lvl & 4 ) {
828 if (debug_lvl & 8 ) {
829 FGLogging log(LogLevel::DEBUG);
830 log <<
" Ballonet holds " << std::fixed << Contents <<
" mol air\n";
831 log <<
" Temperature: " << Temperature <<
" Rankine\n";
832 log <<
" Pressure: " << Pressure <<
" lbs/ft2\n";
833 log <<
" Volume: " << Volume <<
" ft3\n";
834 log <<
" Mass: " <<
GetMass() <<
" slug mass\n";
837 if (debug_lvl & 16) {
839 if (debug_lvl & 64) {
Element * FindElement(const std::string &el="")
Searches for a specified element.
FGColumnVector3 FindElementTripletConvertTo(const std::string &target_units)
Composes a 3-element column vector for the supplied location or orientation.
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
double FindElementValueAsNumberConvertTo(const std::string &el, const std::string &target_units)
Searches for the named element and converts and returns the data belonging to it.
Element * FindNextElement(const std::string &el="")
Searches for the next element as specified.
double FindElementValueAsNumber(const std::string &el="")
Searches for the named element and returns the data belonging to it as a number.
Models a ballonet inside a gas cell.
double GetMass(void) const
Get the current mass of the ballonets.
const FGColumnVector3 & GetXYZ(void) const
Get the center of gravity location of the ballonet.
void Calculate(double dt)
Runs the ballonet model; called by FGGasCell.
Encapsulates the JSBSim simulation executive.
std::shared_ptr< FGMassBalance > GetMassBalance(void) const
Returns the FGAircraft pointer.
std::shared_ptr< FGPropertyManager > GetPropertyManager(void) const
Returns a pointer to the property manager object.
Utility class that aids in the conversion of forces between coordinate systems and calculation of mom...
Represents a mathematical function.
double GetValue(void) const override
Retrieves the value of the function object.
FGGasCell(FGFDMExec *exec, Element *el, unsigned int num, const struct Inputs &input)
Constructor.
double GetTemperature(void) const
Get the current gas temperature inside the gas cell.
double GetMass(void) const
Get the current mass of the gas cell (including any ballonets)
const FGColumnVector3 & GetXYZ(void) const
Get the center of gravity location of the gas cell (including any ballonets)
void Calculate(double dt)
Runs the gas cell model; called by BuoyantForces.
double GetPressure(void) const
Get the current gas pressure inside the gas cell.
static constexpr double slugtolb
Note that definition of lbtoslug by the inverse of slugtolb and not to a different constant you can a...
void InitMatrix(void)
Initialize the matrix.
Main namespace for the JSBSim Flight Dynamics Model.