38 #include "FGFDMExec.h"
39 #include "models/FGMassBalance.h"
40 #include "FGGasCell.h"
41 #include "input_output/FGXMLElement.h"
55 const double FGGasCell::R = 3.4071;
56 const double FGGasCell::M_air = 0.0019186;
57 const double FGGasCell::M_hydrogen = 0.00013841;
58 const double FGGasCell::M_helium = 0.00027409;
61 const struct Inputs& input)
70 Buoyancy = MaxVolume = MaxOverpressure = Temperature = Pressure =
71 Contents = Volume = dVolumeIdeal = 0.0;
72 Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
73 ValveCoefficient = ValveOpen = 0.0;
77 SetTransformType(FGForce::tLocalBody);
80 if (type ==
"HYDROGEN") Type = ttHYDROGEN;
81 else if (type ==
"HELIUM") Type = ttHELIUM;
82 else if (type ==
"AIR") Type = ttAIR;
83 else Type = ttUNKNOWN;
89 const string s(
"Fatal Error: No location found for this gas cell.");
90 cerr << el->
ReadFrom() << endl << s << endl;
120 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
121 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
123 MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
124 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
125 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
127 MaxVolume = M_PI * Yradius * Zradius * Xwidth;
129 cerr <<
"Warning: Unsupported gas cell shape." << endl;
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);
141 const string s(
"Fatal Error: Gas cell shape must be given.");
142 cerr << el->
ReadFrom() << endl << s << endl;
152 Volume = Fullness * MaxVolume;
154 cerr <<
"Warning: Invalid initial gas cell fullness value." << endl;
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()->
SetWritable( property_name,
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) {
240 FGGasCell::~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.size() > 0) {
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(0.0, 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;
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();
445 void FGGasCell::Debug(
int from)
447 if (debug_lvl <= 0)
return;
451 cout <<
" Gas cell holds " << Contents <<
" mol " <<
453 cout <<
" Cell location (X, Y, Z) (in.): " << vXYZ(eX) <<
", " <<
454 vXYZ(eY) <<
", " << vXYZ(eZ) << endl;
455 cout <<
" Maximum volume: " << MaxVolume <<
" ft3" << endl;
456 cout <<
" Relief valve release pressure: " << MaxOverpressure <<
458 cout <<
" Manual valve coefficient: " << ValveCoefficient <<
459 " ft4*sec/slug" << endl;
460 cout <<
" Initial temperature: " << Temperature <<
" Rankine" <<
462 cout <<
" Initial pressure: " << Pressure <<
" lbs/ft2" << endl;
463 cout <<
" Initial volume: " << Volume <<
" ft3" << endl;
464 cout <<
" Initial mass: " <<
GetMass() <<
" slug mass" << endl;
467 cout <<
" Heat transfer: " << endl;
470 if (debug_lvl & 2 ) {
471 if (from == 0) cout <<
"Instantiated: FGGasCell" << endl;
472 if (from == 1) cout <<
"Destroyed: FGGasCell" << endl;
474 if (debug_lvl & 4 ) {
476 if (debug_lvl & 8 ) {
477 cout <<
" " << type <<
" cell holds " << Contents <<
" mol " << endl;
478 cout <<
" Temperature: " << Temperature <<
" Rankine" << endl;
479 cout <<
" Pressure: " << Pressure <<
" lbs/ft2" << endl;
480 cout <<
" Volume: " << Volume <<
" ft3" << endl;
481 cout <<
" Mass: " <<
GetMass() <<
" slug mass" << endl;
484 if (debug_lvl & 16) {
486 if (debug_lvl & 64) {
495 const double FGBallonet::R = 3.4071;
496 const double FGBallonet::M_air = 0.0019186;
497 const double FGBallonet::Cv_air = 5.0/2.0;
499 FGBallonet::FGBallonet(FGFDMExec* exec, Element* el,
unsigned int num,
500 FGGasCell* parent,
const struct FGGasCell::Inputs& input)
506 FGPropertyManager* 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 const string s(
"Fatal Error: No location found for this ballonet.");
523 cerr << el->ReadFrom() << endl << s << endl;
524 throw BaseException(s);
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 cerr <<
"Warning: Unsupported ballonet shape." << endl;
564 (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
565 M_PI * Yradius * Zradius * Xwidth +
566 M_PI * Xradius * Zradius * Ywidth +
567 M_PI * Xradius * Yradius * Zwidth +
568 2.0 * Xradius * Ywidth * Zwidth +
569 2.0 * Yradius * Xwidth * Zwidth +
570 2.0 * Zradius * Xwidth * Ywidth +
571 Xwidth * Ywidth * Zwidth);
574 const string s(
"Fatal Error: Ballonet shape must be given.");
575 cerr << el->ReadFrom() << endl << s << endl;
576 throw BaseException(s);
578 if (el->FindElement(
"max_overpressure")) {
579 MaxOverpressure = el->FindElementValueAsNumberConvertTo(
"max_overpressure",
582 if (el->FindElement(
"fullness")) {
583 const double Fullness = el->FindElementValueAsNumber(
"fullness");
585 Volume = Fullness * MaxVolume;
587 cerr <<
"Warning: Invalid initial ballonet fullness value." << endl;
590 if (el->FindElement(
"valve_coefficient")) {
592 el->FindElementValueAsNumberConvertTo(
"valve_coefficient",
594 ValveCoefficient = max(ValveCoefficient, 0.0);
598 if (Temperature == 0.0) {
599 Temperature = Parent->GetTemperature();
601 if (Pressure == 0.0) {
602 Pressure = Parent->GetPressure();
606 Contents = Pressure * Volume / (R * Temperature);
609 const double IdealPressure = Contents * R * Temperature / MaxVolume;
610 if (IdealPressure > Pressure + MaxOverpressure) {
611 Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
612 Pressure = Pressure + MaxOverpressure;
614 Pressure = max(IdealPressure, Pressure);
618 Contents = Pressure * MaxVolume / (R * Temperature);
621 Volume = Contents * R * Temperature / Pressure;
624 string property_name, base_property_name;
625 base_property_name = CreateIndexedPropertyName(
"buoyant_forces/gas-cell", Parent->GetIndex());
626 base_property_name = CreateIndexedPropertyName(base_property_name +
"/ballonet", CellNum);
628 property_name = base_property_name +
"/max_volume-ft3";
629 PropertyManager->Tie( property_name, &MaxVolume);
630 PropertyManager->GetNode()->SetWritable( property_name,
false );
632 property_name = base_property_name +
"/temp-R";
633 PropertyManager->Tie( property_name, &Temperature);
635 property_name = base_property_name +
"/pressure-psf";
636 PropertyManager->Tie( property_name, &Pressure);
638 property_name = base_property_name +
"/volume-ft3";
639 PropertyManager->Tie( property_name, &Volume);
641 property_name = base_property_name +
"/contents-mol";
642 PropertyManager->Tie( property_name, &Contents);
644 property_name = base_property_name +
"/valve_open";
645 PropertyManager->Tie( property_name, &ValveOpen);
650 if (Element* heat = el->FindElement(
"heat")) {
651 Element* function_element = heat->FindElement(
"function");
652 while (function_element) {
653 HeatTransferCoeff.push_back(
new FGFunction(exec, function_element));
654 function_element = heat->FindNextElement(
"function");
658 if (Element* blower = el->FindElement(
"blower_input")) {
659 Element* function_element = blower->FindElement(
"function");
660 BlowerInput =
new FGFunction(exec, function_element);
666 FGBallonet::~FGBallonet()
670 for (i = 0; i < HeatTransferCoeff.size(); i++)
delete HeatTransferCoeff[i];
671 HeatTransferCoeff.clear();
683 const double ParentPressure = Parent->
GetPressure();
684 const double AirPressure = in.Pressure;
686 const double OldTemperature = Temperature;
687 const double OldPressure = Pressure;
696 for (i = 0; i < HeatTransferCoeff.size(); i++) {
697 dU += HeatTransferCoeff[i]->GetValue();
702 (dU * dt - Pressure * dVolumeIdeal) / (Cv_air * Contents * R);
708 const double IdealPressure = Contents * R * Temperature / MaxVolume;
710 Pressure = max(IdealPressure, ParentPressure);
714 const double AddedVolume = BlowerInput->
GetValue() * dt;
715 if (AddedVolume > 0.0) {
716 Contents += Pressure * AddedVolume / (R * Temperature);
724 if ((ValveCoefficient > 0.0) &&
725 ((ValveOpen > 0.0) || (Pressure > AirPressure + MaxOverpressure))) {
726 const double DeltaPressure = Pressure - AirPressure;
727 const double VolumeValved =
728 ((Pressure > AirPressure + MaxOverpressure) ? 1.0 : ValveOpen) *
729 ValveCoefficient * DeltaPressure * dt;
733 max(1.0, Contents - Pressure * VolumeValved / (R * Temperature));
737 Volume = Contents * R * Temperature / Pressure;
739 Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
746 const double mass = Contents * M_air;
747 double Ixx, Iyy, Izz;
748 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
749 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
751 Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
752 Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
753 Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
754 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
755 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
757 Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
759 (1.0 / 4.0) * mass * Yradius * Zradius +
760 (1.0 / 12.0) * mass * Xwidth * Xwidth;
762 (1.0 / 4.0) * mass * Yradius * Zradius +
763 (1.0 / 12.0) * mass * Xwidth * Xwidth;
766 Ixx = Iyy = Izz = 0.0;
769 ballonetJ(1,1) = Ixx;
770 ballonetJ(2,2) = Iyy;
771 ballonetJ(3,3) = Izz;
795 void FGBallonet::Debug(
int from)
797 if (debug_lvl <= 0)
return;
801 cout <<
" Ballonet holds " << Contents <<
" mol air" << endl;
802 cout <<
" Location (X, Y, Z) (in.): " << vXYZ(eX) <<
", " <<
803 vXYZ(eY) <<
", " << vXYZ(eZ) << endl;
804 cout <<
" Maximum volume: " << MaxVolume <<
" ft3" << endl;
805 cout <<
" Relief valve release pressure: " << MaxOverpressure <<
807 cout <<
" Relief valve coefficient: " << ValveCoefficient <<
808 " ft4*sec/slug" << endl;
809 cout <<
" Initial temperature: " << Temperature <<
" Rankine" <<
811 cout <<
" Initial pressure: " << Pressure <<
" lbs/ft2" << endl;
812 cout <<
" Initial volume: " << Volume <<
" ft3" << endl;
813 cout <<
" Initial mass: " <<
GetMass() <<
" slug mass" << endl;
815 " lbs force" << endl;
816 cout <<
" Heat transfer: " << endl;
819 if (debug_lvl & 2 ) {
820 if (from == 0) cout <<
"Instantiated: FGBallonet" << endl;
821 if (from == 1) cout <<
"Destroyed: FGBallonet" << endl;
823 if (debug_lvl & 4 ) {
825 if (debug_lvl & 8 ) {
826 cout <<
" Ballonet holds " << Contents <<
828 cout <<
" Temperature: " << Temperature <<
" Rankine" << endl;
829 cout <<
" Pressure: " << Pressure <<
" lbs/ft2" << endl;
830 cout <<
" Volume: " << Volume <<
" ft3" << endl;
831 cout <<
" Mass: " <<
GetMass() <<
" slug mass" << endl;
834 if (debug_lvl & 16) {
836 if (debug_lvl & 64) {