39#include "models/FGMassBalance.h"
41#include "input_output/FGXMLElement.h"
42#include "input_output/FGLog.h"
54const double FGGasCell::R = 3.4071;
55const double FGGasCell::M_air = 0.0019186;
56const double FGGasCell::M_hydrogen = 0.00013841;
57const double FGGasCell::M_helium = 0.00027409;
60 const struct Inputs& input)
69 Buoyancy = MaxVolume = MaxOverpressure = Temperature = Pressure =
70 Contents = Volume = dVolumeIdeal = 0.0;
71 Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
72 ValveCoefficient = ValveOpen = 0.0;
76 SetTransformType(FGForce::tLocalBody);
79 if (type ==
"HYDROGEN") Type = ttHYDROGEN;
80 else if (type ==
"HELIUM") Type = ttHELIUM;
81 else if (type ==
"AIR") Type = ttAIR;
82 else Type = ttUNKNOWN;
89 err <<
"\nFatal Error: No location found for this gas cell.\n";
119 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
120 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
122 MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
123 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
124 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
126 MaxVolume = M_PI * Yradius * Zradius * Xwidth;
129 log <<
"Unsupported gas cell shape.\n";
131 (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
132 M_PI * Yradius * Zradius * Xwidth +
133 M_PI * Xradius * Zradius * Ywidth +
134 M_PI * Xradius * Yradius * Zwidth +
135 2.0 * Xradius * Ywidth * Zwidth +
136 2.0 * Yradius * Xwidth * Zwidth +
137 2.0 * Zradius * Xwidth * Ywidth +
138 Xwidth * Ywidth * Zwidth);
142 err <<
"\nGas cell shape must be given.\n";
152 Volume = Fullness * MaxVolume;
155 log <<
"Invalid initial gas cell fullness value.\n";
162 ValveCoefficient = max(ValveCoefficient, 0.0);
168 if (Temperature == 0.0) {
169 Temperature = in.Temperature;
171 if (Pressure == 0.0) {
172 Pressure = in.Pressure;
176 Contents = Pressure * Volume / (R * Temperature);
179 const double IdealPressure = Contents * R * Temperature / MaxVolume;
180 if (IdealPressure > Pressure + MaxOverpressure) {
181 Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
182 Pressure = Pressure + MaxOverpressure;
184 Pressure = max(IdealPressure, Pressure);
188 Contents = Pressure * MaxVolume / (R * Temperature);
191 Volume = Contents * R * Temperature / Pressure;
192 Mass = Contents * M_gas();
195 string property_name, base_property_name;
197 base_property_name = CreateIndexedPropertyName(
"buoyant_forces/gas-cell", CellNum);
199 property_name = base_property_name +
"/max_volume-ft3";
200 PropertyManager->Tie( property_name.c_str(), &MaxVolume);
201 PropertyManager->GetNode(property_name)->setAttribute( SGPropertyNode::WRITE,
false );
202 property_name = base_property_name +
"/temp-R";
203 PropertyManager->Tie( property_name.c_str(), &Temperature);
204 property_name = base_property_name +
"/pressure-psf";
205 PropertyManager->Tie( property_name.c_str(), &Pressure);
206 property_name = base_property_name +
"/volume-ft3";
207 PropertyManager->Tie( property_name.c_str(), &Volume);
208 property_name = base_property_name +
"/buoyancy-lbs";
209 PropertyManager->Tie( property_name.c_str(), &Buoyancy);
210 property_name = base_property_name +
"/contents-mol";
211 PropertyManager->Tie( property_name.c_str(), &Contents);
212 property_name = base_property_name +
"/valve_open";
213 PropertyManager->Tie( property_name.c_str(), &ValveOpen);
220 while (function_element) {
221 HeatTransferCoeff.push_back(
new FGFunction(exec, function_element));
228 while (ballonet_element) {
241FGGasCell::~FGGasCell()
245 for (i = 0; i < HeatTransferCoeff.size(); i++)
delete HeatTransferCoeff[i];
246 HeatTransferCoeff.clear();
248 for (i = 0; i < Ballonet.size(); i++)
delete Ballonet[i];
258 const double AirTemperature = in.Temperature;
259 const double AirPressure = in.Pressure;
260 const double AirDensity = in.Density;
261 const double g = in.gravity;
263 const double OldTemperature = Temperature;
264 const double OldPressure = Pressure;
266 const size_t no_ballonets = Ballonet.size();
270 double BallonetsVolume = 0.0;
271 double BallonetsHeatFlow = 0.0;
272 for (i = 0; i < no_ballonets; i++) {
273 BallonetsVolume += Ballonet[i]->GetVolume();
274 BallonetsHeatFlow += Ballonet[i]->GetHeatFlow();
279 if (!HeatTransferCoeff.empty()) {
284 for (i = 0; i < HeatTransferCoeff.size(); i++) {
285 dU += HeatTransferCoeff[i]->GetValue();
291 (dU * dt - Pressure * dVolumeIdeal - BallonetsHeatFlow) /
292 (Cv_gas() * Contents * R);
294 Temperature = AirTemperature;
300 Temperature = AirTemperature;
304 const double IdealPressure =
305 Contents * R * Temperature / (MaxVolume - BallonetsVolume);
306 if (IdealPressure > AirPressure + MaxOverpressure) {
307 Pressure = AirPressure + MaxOverpressure;
309 Pressure = max(IdealPressure, AirPressure);
317 if ((ValveCoefficient > 0.0) && (ValveOpen > 0.0)) {
321 const double GasVolume = Contents * R * Temperature / Pressure;
322 const double CellHeight = (2 * Zradius + Zwidth) *
323 min(1.0, GasVolume / MaxVolume);
324 const double GasDensity = M_gas() * Pressure / (R * Temperature);
325 const double DeltaPressure = max(0.0,
326 Pressure + CellHeight * g * (AirDensity - GasDensity) - AirPressure);
327 const double VolumeValved =
328 ValveOpen * ValveCoefficient * DeltaPressure * dt;
330 max(0.0, Contents - Pressure * VolumeValved / (R * Temperature));
336 BallonetsVolume = 0.0;
337 for (i = 0; i < no_ballonets; i++) {
338 Ballonet[i]->Calculate(dt);
339 BallonetsVolume += Ballonet[i]->GetVolume();
343 if (Contents * R * Temperature / (MaxVolume - BallonetsVolume) >
344 AirPressure + MaxOverpressure) {
348 (AirPressure + MaxOverpressure) *
349 (MaxVolume - BallonetsVolume) / (R * Temperature);
353 Volume = Contents * R * Temperature / Pressure + BallonetsVolume;
355 Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
359 Buoyancy = Volume * AirDensity * g;
364 vFn = {0.0, 0.0, - Buoyancy};
371 const double mass = Contents * M_gas();
372 double Ixx, Iyy, Izz;
373 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
374 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
376 Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
377 Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
378 Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
379 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
380 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
382 Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
384 (1.0 / 4.0) * mass * Yradius * Zradius +
385 (1.0 / 12.0) * mass * Xwidth * Xwidth;
387 (1.0 / 4.0) * mass * Yradius * Zradius +
388 (1.0 / 12.0) * mass * Xwidth * Xwidth;
391 Ixx = Iyy = Izz = 0.0;
399 gasCellJ += MassBalance->GetPointmassInertia(Mass,
GetXYZ());
401 gasCellM.InitMatrix();
409 if (no_ballonets > 0) {
411 for (i = 0; i < no_ballonets; i++) {
412 Mass += Ballonet[i]->GetMass();
416 Ballonet[i]->GetXYZ(eX) * Ballonet[i]->GetMass()*
slugtolb;
418 Ballonet[i]->GetXYZ(eY) * Ballonet[i]->GetMass()*
slugtolb;
420 Ballonet[i]->GetXYZ(eZ) * Ballonet[i]->GetMass()*
slugtolb;
422 gasCellJ += Ballonet[i]->GetInertia();
446void FGGasCell::Debug(
int from)
448 if (debug_lvl <= 0)
return;
453 log <<
" Gas cell holds " << std::fixed << Contents <<
" mol " << type <<
"\n";
454 log <<
" Cell location (X, Y, Z) (in.): " << vXYZ(eX) <<
", "
455 << vXYZ(eY) <<
", " << vXYZ(eZ) <<
"\n";
456 log <<
" Maximum volume: " << MaxVolume <<
" ft3\n";
457 log <<
" Relief valve release pressure: " << MaxOverpressure
459 log <<
" Manual valve coefficient: " << ValveCoefficient
460 <<
" ft4*sec/slug\n";
461 log <<
" Initial temperature: " << Temperature <<
" Rankine\n";
462 log <<
" Initial pressure: " << Pressure <<
" lbs/ft2\n";
463 log <<
" Initial volume: " << Volume <<
" ft3\n";
464 log <<
" Initial mass: " <<
GetMass() <<
" slug mass\n";
466 log <<
" Heat transfer: \n";
469 if (debug_lvl & 2 ) {
470 FGLogging log(LogLevel::DEBUG);
471 if (from == 0) log <<
"Instantiated: FGGasCell\n";
472 if (from == 1) log <<
"Destroyed: FGGasCell\n";
474 if (debug_lvl & 4 ) {
476 if (debug_lvl & 8 ) {
477 FGLogging log(LogLevel::DEBUG);
478 log <<
" " << type <<
" cell holds " << std::fixed << Contents <<
" mol\n";
479 log <<
" Temperature: " << Temperature <<
" Rankine\n";
480 log <<
" Pressure: " << Pressure <<
" lbs/ft2\n";
481 log <<
" Volume: " << Volume <<
" ft3\n";
482 log <<
" Mass: " <<
GetMass() <<
" slug mass\n";
485 if (debug_lvl & 16) {
487 if (debug_lvl & 64) {
496const double FGBallonet::R = 3.4071;
497const double FGBallonet::M_air = 0.0019186;
498const double FGBallonet::Cv_air = 5.0/2.0;
500FGBallonet::FGBallonet(FGFDMExec* exec, Element* el,
unsigned int num,
501 FGGasCell* parent,
const struct FGGasCell::Inputs& input)
507 auto PropertyManager = exec->GetPropertyManager();
508 MassBalance = exec->GetMassBalance();
510 MaxVolume = MaxOverpressure = Temperature = Pressure =
511 Contents = Volume = dVolumeIdeal = dU = 0.0;
512 Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
513 ValveCoefficient = ValveOpen = 0.0;
519 element = el->FindElement(
"location");
521 vXYZ = element->FindElementTripletConvertTo(
"IN");
523 XMLLogException err(el);
524 err <<
"\nFatal Error: No location found for this ballonet.\n";
527 if ((el->FindElement(
"x_radius") || el->FindElement(
"x_width")) &&
528 (el->FindElement(
"y_radius") || el->FindElement(
"y_width")) &&
529 (el->FindElement(
"z_radius") || el->FindElement(
"z_width"))) {
531 if (el->FindElement(
"x_radius")) {
532 Xradius = el->FindElementValueAsNumberConvertTo(
"x_radius",
"FT");
534 if (el->FindElement(
"y_radius")) {
535 Yradius = el->FindElementValueAsNumberConvertTo(
"y_radius",
"FT");
537 if (el->FindElement(
"z_radius")) {
538 Zradius = el->FindElementValueAsNumberConvertTo(
"z_radius",
"FT");
541 if (el->FindElement(
"x_width")) {
542 Xwidth = el->FindElementValueAsNumberConvertTo(
"x_width",
"FT");
544 if (el->FindElement(
"y_width")) {
545 Ywidth = el->FindElementValueAsNumberConvertTo(
"y_width",
"FT");
547 if (el->FindElement(
"z_width")) {
548 Zwidth = el->FindElementValueAsNumberConvertTo(
"z_width",
"FT");
554 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
555 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
557 MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
558 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
559 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
561 MaxVolume = M_PI * Yradius * Zradius * Xwidth;
563 FGXMLLogging log(el, LogLevel::WARN);
564 log <<
"Unsupported ballonet shape.\n";
566 (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
567 M_PI * Yradius * Zradius * Xwidth +
568 M_PI * Xradius * Zradius * Ywidth +
569 M_PI * Xradius * Yradius * Zwidth +
570 2.0 * Xradius * Ywidth * Zwidth +
571 2.0 * Yradius * Xwidth * Zwidth +
572 2.0 * Zradius * Xwidth * Ywidth +
573 Xwidth * Ywidth * Zwidth);
576 XMLLogException err(el);
577 err <<
"\nFatal Error: Ballonet shape must be given.\n";
580 if (el->FindElement(
"max_overpressure")) {
581 MaxOverpressure = el->FindElementValueAsNumberConvertTo(
"max_overpressure",
584 if (el->FindElement(
"fullness")) {
585 const double Fullness = el->FindElementValueAsNumber(
"fullness");
587 Volume = Fullness * MaxVolume;
589 FGXMLLogging log(el, LogLevel::WARN);
590 log <<
"Invalid initial ballonet fullness value.\n";
593 if (el->FindElement(
"valve_coefficient")) {
595 el->FindElementValueAsNumberConvertTo(
"valve_coefficient",
597 ValveCoefficient = max(ValveCoefficient, 0.0);
601 if (Temperature == 0.0) {
602 Temperature = Parent->GetTemperature();
604 if (Pressure == 0.0) {
605 Pressure = Parent->GetPressure();
609 Contents = Pressure * Volume / (R * Temperature);
612 const double IdealPressure = Contents * R * Temperature / MaxVolume;
613 if (IdealPressure > Pressure + MaxOverpressure) {
614 Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
615 Pressure = Pressure + MaxOverpressure;
617 Pressure = max(IdealPressure, Pressure);
621 Contents = Pressure * MaxVolume / (R * Temperature);
624 Volume = Contents * R * Temperature / Pressure;
627 string property_name, base_property_name;
628 base_property_name = CreateIndexedPropertyName(
"buoyant_forces/gas-cell", Parent->GetIndex());
629 base_property_name = CreateIndexedPropertyName(base_property_name +
"/ballonet", CellNum);
631 property_name = base_property_name +
"/max_volume-ft3";
632 PropertyManager->Tie( property_name, &MaxVolume);
633 PropertyManager->GetNode(property_name)->setAttribute( SGPropertyNode::WRITE,
false );
635 property_name = base_property_name +
"/temp-R";
636 PropertyManager->Tie( property_name, &Temperature);
638 property_name = base_property_name +
"/pressure-psf";
639 PropertyManager->Tie( property_name, &Pressure);
641 property_name = base_property_name +
"/volume-ft3";
642 PropertyManager->Tie( property_name, &Volume);
644 property_name = base_property_name +
"/contents-mol";
645 PropertyManager->Tie( property_name, &Contents);
647 property_name = base_property_name +
"/valve_open";
648 PropertyManager->Tie( property_name, &ValveOpen);
653 if (Element* heat = el->FindElement(
"heat")) {
654 Element* function_element = heat->FindElement(
"function");
655 while (function_element) {
656 HeatTransferCoeff.push_back(
new FGFunction(exec, function_element));
657 function_element = heat->FindNextElement(
"function");
661 if (Element* blower = el->FindElement(
"blower_input")) {
662 Element* function_element = blower->FindElement(
"function");
663 BlowerInput =
new FGFunction(exec, function_element);
669FGBallonet::~FGBallonet()
673 for (i = 0; i < HeatTransferCoeff.size(); i++)
delete HeatTransferCoeff[i];
674 HeatTransferCoeff.clear();
686 const double ParentPressure = Parent->
GetPressure();
687 const double AirPressure = in.Pressure;
689 const double OldTemperature = Temperature;
690 const double OldPressure = Pressure;
699 for (i = 0; i < HeatTransferCoeff.size(); i++) {
700 dU += HeatTransferCoeff[i]->GetValue();
705 (dU * dt - Pressure * dVolumeIdeal) / (Cv_air * Contents * R);
711 const double IdealPressure = Contents * R * Temperature / MaxVolume;
713 Pressure = max(IdealPressure, ParentPressure);
717 const double AddedVolume = BlowerInput->
GetValue() * dt;
718 if (AddedVolume > 0.0) {
719 Contents += Pressure * AddedVolume / (R * Temperature);
727 if ((ValveCoefficient > 0.0) &&
728 ((ValveOpen > 0.0) || (Pressure > AirPressure + MaxOverpressure))) {
729 const double DeltaPressure = Pressure - AirPressure;
730 const double VolumeValved =
731 ((Pressure > AirPressure + MaxOverpressure) ? 1.0 : ValveOpen) *
732 ValveCoefficient * DeltaPressure * dt;
736 max(1.0, Contents - Pressure * VolumeValved / (R * Temperature));
740 Volume = Contents * R * Temperature / Pressure;
742 Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
749 const double mass = Contents * M_air;
750 double Ixx, Iyy, Izz;
751 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
752 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
754 Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
755 Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
756 Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
757 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
758 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
760 Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
762 (1.0 / 4.0) * mass * Yradius * Zradius +
763 (1.0 / 12.0) * mass * Xwidth * Xwidth;
765 (1.0 / 4.0) * mass * Yradius * Zradius +
766 (1.0 / 12.0) * mass * Xwidth * Xwidth;
769 Ixx = Iyy = Izz = 0.0;
772 ballonetJ(1,1) = Ixx;
773 ballonetJ(2,2) = Iyy;
774 ballonetJ(3,3) = Izz;
776 ballonetJ += MassBalance->GetPointmassInertia(
GetMass(),
GetXYZ());
798void FGBallonet::Debug(
int from)
800 if (debug_lvl <= 0)
return;
805 log <<
" Ballonet holds " << std::fixed << Contents <<
" mol air\n";
806 log <<
" Location (X, Y, Z) (in.): " << vXYZ(eX) <<
", "
807 << vXYZ(eY) <<
", " << vXYZ(eZ) <<
"\n";
808 log <<
" Maximum volume: " << MaxVolume <<
" ft3\n";
809 log <<
" Relief valve release pressure: " << MaxOverpressure
811 log <<
" Relief valve coefficient: " << ValveCoefficient
812 <<
" ft4*sec/slug\n";
813 log <<
" Initial temperature: " << Temperature <<
" Rankine\n";
814 log <<
" Initial pressure: " << Pressure <<
" lbs/ft2\n";
815 log <<
" Initial volume: " << Volume <<
" ft3\n";
816 log <<
" Initial mass: " <<
GetMass() <<
" slug mass\n";
819 log <<
" Heat transfer: \n";
822 if (debug_lvl & 2 ) {
823 FGLogging log(LogLevel::DEBUG);
824 if (from == 0) log <<
"Instantiated: FGBallonet\n";
825 if (from == 1) log <<
"Destroyed: FGBallonet\n";
827 if (debug_lvl & 4 ) {
829 if (debug_lvl & 8 ) {
830 FGLogging log(LogLevel::DEBUG);
831 log <<
" Ballonet holds " << std::fixed << Contents <<
" mol air\n";
832 log <<
" Temperature: " << Temperature <<
" Rankine\n";
833 log <<
" Pressure: " << Pressure <<
" lbs/ft2\n";
834 log <<
" Volume: " << Volume <<
" ft3\n";
835 log <<
" Mass: " <<
GetMass() <<
" slug mass\n";
838 if (debug_lvl & 16) {
840 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.