38 #include "FGFDMExec.h" 39 #include "models/FGMassBalance.h" 40 #include "FGGasCell.h" 41 #include "input_output/FGXMLElement.h" 53 IDENT(IdSrc,
"$Id: FGGasCell.cpp,v 1.22 2015/03/28 14:49:02 bcoconni Exp $");
54 IDENT(IdHdr,ID_GASCELL);
60 const double FGGasCell::R = 3.4071;
61 const double FGGasCell::M_air = 0.0019186;
62 const double FGGasCell::M_hydrogen = 0.00013841;
63 const double FGGasCell::M_helium = 0.00027409;
66 const struct Inputs& input)
78 Buoyancy = MaxVolume = MaxOverpressure = Temperature = Pressure =
79 Contents = Volume = dVolumeIdeal = 0.0;
80 Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
81 ValveCoefficient = ValveOpen = 0.0;
85 SetTransformType(FGForce::tLocalBody);
88 if (type ==
"HYDROGEN") Type = ttHYDROGEN;
89 else if (type ==
"HELIUM") Type = ttHELIUM;
90 else if (type ==
"AIR") Type = ttAIR;
91 else Type = ttUNKNOWN;
97 cerr <<
"Fatal Error: No location found for this gas cell." << endl;
127 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
128 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
130 MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
131 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
132 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
134 MaxVolume = M_PI * Yradius * Zradius * Xwidth;
136 cerr <<
"Warning: Unsupported gas cell shape." << endl;
138 (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
139 M_PI * Yradius * Zradius * Xwidth +
140 M_PI * Xradius * Zradius * Ywidth +
141 M_PI * Xradius * Yradius * Zwidth +
142 2.0 * Xradius * Ywidth * Zwidth +
143 2.0 * Yradius * Xwidth * Zwidth +
144 2.0 * Zradius * Xwidth * Ywidth +
145 Xwidth * Ywidth * Zwidth);
148 cerr <<
"Fatal Error: Gas cell shape must be given." << endl;
158 Volume = Fullness * MaxVolume;
160 cerr <<
"Warning: Invalid initial gas cell fullness value." << endl;
167 ValveCoefficient = max(ValveCoefficient, 0.0);
173 if (Temperature == 0.0) {
174 Temperature = in.Temperature;
176 if (Pressure == 0.0) {
177 Pressure = in.Pressure;
181 Contents = Pressure * Volume / (R * Temperature);
184 const double IdealPressure = Contents * R * Temperature / MaxVolume;
185 if (IdealPressure > Pressure + MaxOverpressure) {
186 Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
187 Pressure = Pressure + MaxOverpressure;
189 Pressure = max(IdealPressure, Pressure);
193 Contents = Pressure * MaxVolume / (R * Temperature);
196 Volume = Contents * R * Temperature / Pressure;
197 Mass = Contents * M_gas();
200 string property_name, base_property_name;
202 base_property_name = CreateIndexedPropertyName(
"buoyant_forces/gas-cell", CellNum);
204 property_name = base_property_name +
"/max_volume-ft3";
205 PropertyManager->
Tie( property_name.c_str(), &MaxVolume, false );
206 PropertyManager->GetNode()->
SetWritable( property_name,
false );
207 property_name = base_property_name +
"/temp-R";
208 PropertyManager->
Tie( property_name.c_str(), &Temperature, false );
209 property_name = base_property_name +
"/pressure-psf";
210 PropertyManager->
Tie( property_name.c_str(), &Pressure, false );
211 property_name = base_property_name +
"/volume-ft3";
212 PropertyManager->
Tie( property_name.c_str(), &Volume, false );
213 property_name = base_property_name +
"/buoyancy-lbs";
214 PropertyManager->
Tie( property_name.c_str(), &Buoyancy, false );
215 property_name = base_property_name +
"/contents-mol";
216 PropertyManager->
Tie( property_name.c_str(), &Contents, false );
217 property_name = base_property_name +
"/valve_open";
218 PropertyManager->
Tie( property_name.c_str(), &ValveOpen, false );
225 while (function_element) {
226 HeatTransferCoeff.push_back(
new FGFunction(PropertyManager,
234 while (ballonet_element) {
247 FGGasCell::~FGGasCell()
251 for (i = 0; i < HeatTransferCoeff.size(); i++)
delete HeatTransferCoeff[i];
252 HeatTransferCoeff.clear();
254 for (i = 0; i < Ballonet.size(); i++)
delete Ballonet[i];
264 const double AirTemperature = in.Temperature;
265 const double AirPressure = in.Pressure;
266 const double AirDensity = in.Density;
267 const double g = in.gravity;
269 const double OldTemperature = Temperature;
270 const double OldPressure = Pressure;
272 const size_t no_ballonets = Ballonet.size();
276 double BallonetsVolume = 0.0;
277 double BallonetsHeatFlow = 0.0;
278 for (i = 0; i < no_ballonets; i++) {
279 BallonetsVolume += Ballonet[i]->GetVolume();
280 BallonetsHeatFlow += Ballonet[i]->GetHeatFlow();
285 if (HeatTransferCoeff.size() > 0) {
290 for (i = 0; i < HeatTransferCoeff.size(); i++) {
291 dU += HeatTransferCoeff[i]->GetValue();
297 (dU * dt - Pressure * dVolumeIdeal - BallonetsHeatFlow) /
298 (Cv_gas() * Contents * R);
300 Temperature = AirTemperature;
306 Temperature = AirTemperature;
310 const double IdealPressure =
311 Contents * R * Temperature / (MaxVolume - BallonetsVolume);
312 if (IdealPressure > AirPressure + MaxOverpressure) {
313 Pressure = AirPressure + MaxOverpressure;
315 Pressure = max(IdealPressure, AirPressure);
323 if ((ValveCoefficient > 0.0) && (ValveOpen > 0.0)) {
327 const double CellHeight = 2 * Zradius + Zwidth;
328 const double GasMass = Contents * M_gas();
329 const double GasVolume = Contents * R * Temperature / Pressure;
330 const double GasDensity = GasMass / GasVolume;
331 const double DeltaPressure =
332 Pressure + CellHeight * g * (AirDensity - GasDensity) - AirPressure;
333 const double VolumeValved =
334 ValveOpen * ValveCoefficient * DeltaPressure * dt;
336 max(0.0, Contents - Pressure * VolumeValved / (R * Temperature));
342 BallonetsVolume = 0.0;
343 for (i = 0; i < no_ballonets; i++) {
344 Ballonet[i]->Calculate(dt);
345 BallonetsVolume += Ballonet[i]->GetVolume();
349 if (Contents * R * Temperature / (MaxVolume - BallonetsVolume) >
350 AirPressure + MaxOverpressure) {
354 (AirPressure + MaxOverpressure) *
355 (MaxVolume - BallonetsVolume) / (R * Temperature);
359 Volume = Contents * R * Temperature / Pressure + BallonetsVolume;
361 Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
365 Buoyancy = Volume * AirDensity * g;
370 vFn.InitMatrix(0.0, 0.0, - Buoyancy);
377 const double mass = Contents * M_gas();
378 double Ixx, Iyy, Izz;
379 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
380 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
382 Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
383 Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
384 Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
385 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
386 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
388 Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
390 (1.0 / 4.0) * mass * Yradius * Zradius +
391 (1.0 / 12.0) * mass * Xwidth * Xwidth;
393 (1.0 / 4.0) * mass * Yradius * Zradius +
394 (1.0 / 12.0) * mass * Xwidth * Xwidth;
397 Ixx = Iyy = Izz = 0.0;
407 gasCellM.InitMatrix();
409 GetXYZ(eX) * Mass*slugtolb;
411 GetXYZ(eY) * Mass*slugtolb;
413 GetXYZ(eZ) * Mass*slugtolb;
415 if (no_ballonets > 0) {
417 for (i = 0; i < no_ballonets; i++) {
418 Mass += Ballonet[i]->GetMass();
422 Ballonet[i]->GetXYZ(eX) * Ballonet[i]->GetMass()*slugtolb;
424 Ballonet[i]->GetXYZ(eY) * Ballonet[i]->GetMass()*slugtolb;
426 Ballonet[i]->GetXYZ(eZ) * Ballonet[i]->GetMass()*slugtolb;
428 gasCellJ += Ballonet[i]->GetInertia();
452 void FGGasCell::Debug(
int from)
454 if (debug_lvl <= 0)
return;
458 cout <<
" Gas cell holds " << Contents <<
" mol " <<
460 cout <<
" Cell location (X, Y, Z) (in.): " << vXYZ(eX) <<
", " <<
461 vXYZ(eY) <<
", " << vXYZ(eZ) << endl;
462 cout <<
" Maximum volume: " << MaxVolume <<
" ft3" << endl;
463 cout <<
" Relief valve release pressure: " << MaxOverpressure <<
465 cout <<
" Manual valve coefficient: " << ValveCoefficient <<
466 " ft4*sec/slug" << endl;
467 cout <<
" Initial temperature: " << Temperature <<
" Rankine" <<
469 cout <<
" Initial pressure: " << Pressure <<
" lbs/ft2" << endl;
470 cout <<
" Initial volume: " << Volume <<
" ft3" << endl;
471 cout <<
" Initial mass: " <<
GetMass() <<
" slug mass" << endl;
472 cout <<
" Initial weight: " <<
GetMass()*slugtolb <<
" lbs force" <<
474 cout <<
" Heat transfer: " << endl;
477 if (debug_lvl & 2 ) {
478 if (from == 0) cout <<
"Instantiated: FGGasCell" << endl;
479 if (from == 1) cout <<
"Destroyed: FGGasCell" << endl;
481 if (debug_lvl & 4 ) {
483 if (debug_lvl & 8 ) {
484 cout <<
" " << type <<
" cell holds " << Contents <<
" mol " << endl;
485 cout <<
" Temperature: " << Temperature <<
" Rankine" << endl;
486 cout <<
" Pressure: " << Pressure <<
" lbs/ft2" << endl;
487 cout <<
" Volume: " << Volume <<
" ft3" << endl;
488 cout <<
" Mass: " <<
GetMass() <<
" slug mass" << endl;
489 cout <<
" Weight: " <<
GetMass()*slugtolb <<
" lbs force" << endl;
491 if (debug_lvl & 16) {
493 if (debug_lvl & 64) {
495 cout << IdSrc << endl;
496 cout << IdHdr << endl;
504 const double FGBallonet::R = 3.4071;
505 const double FGBallonet::M_air = 0.0019186;
506 const double FGBallonet::Cv_air = 5.0/2.0;
520 MaxVolume = MaxOverpressure = Temperature = Pressure =
521 Contents = Volume = dVolumeIdeal = dU = 0.0;
522 Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
523 ValveCoefficient = ValveOpen = 0.0;
533 cerr <<
"Fatal Error: No location found for this ballonet." << endl;
563 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
564 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
566 MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
567 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
568 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
570 MaxVolume = M_PI * Yradius * Zradius * Xwidth;
572 cerr <<
"Warning: Unsupported ballonet shape." << endl;
574 (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
575 M_PI * Yradius * Zradius * Xwidth +
576 M_PI * Xradius * Zradius * Ywidth +
577 M_PI * Xradius * Yradius * Zwidth +
578 2.0 * Xradius * Ywidth * Zwidth +
579 2.0 * Yradius * Xwidth * Zwidth +
580 2.0 * Zradius * Xwidth * Ywidth +
581 Xwidth * Ywidth * Zwidth);
584 cerr <<
"Fatal Error: Ballonet shape must be given." << endl;
594 Volume = Fullness * MaxVolume;
596 cerr <<
"Warning: Invalid initial ballonet fullness value." << endl;
603 ValveCoefficient = max(ValveCoefficient, 0.0);
607 if (Temperature == 0.0) {
608 Temperature = Parent->GetTemperature();
610 if (Pressure == 0.0) {
611 Pressure = Parent->GetPressure();
615 Contents = Pressure * Volume / (R * Temperature);
618 const double IdealPressure = Contents * R * Temperature / MaxVolume;
619 if (IdealPressure > Pressure + MaxOverpressure) {
620 Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
621 Pressure = Pressure + MaxOverpressure;
623 Pressure = max(IdealPressure, Pressure);
627 Contents = Pressure * MaxVolume / (R * Temperature);
630 Volume = Contents * R * Temperature / Pressure;
633 string property_name, base_property_name;
634 base_property_name = CreateIndexedPropertyName(
"buoyant_forces/gas-cell", Parent->GetIndex());
635 base_property_name = CreateIndexedPropertyName(base_property_name +
"/ballonet", CellNum);
637 property_name = base_property_name +
"/max_volume-ft3";
638 PropertyManager->
Tie( property_name, &MaxVolume,
false );
639 PropertyManager->GetNode()->
SetWritable( property_name,
false );
641 property_name = base_property_name +
"/temp-R";
642 PropertyManager->
Tie( property_name, &Temperature,
false );
644 property_name = base_property_name +
"/pressure-psf";
645 PropertyManager->
Tie( property_name, &Pressure,
false );
647 property_name = base_property_name +
"/volume-ft3";
648 PropertyManager->
Tie( property_name, &Volume,
false );
650 property_name = base_property_name +
"/contents-mol";
651 PropertyManager->
Tie( property_name, &Contents,
false );
653 property_name = base_property_name +
"/valve_open";
654 PropertyManager->
Tie( property_name, &ValveOpen,
false );
661 while (function_element) {
662 HeatTransferCoeff.push_back(
new FGFunction(PropertyManager,
677 FGBallonet::~FGBallonet()
681 for (i = 0; i < HeatTransferCoeff.size(); i++)
delete HeatTransferCoeff[i];
682 HeatTransferCoeff.clear();
694 const double ParentPressure = Parent->GetPressure();
695 const double AirPressure = in.Pressure;
697 const double OldTemperature = Temperature;
698 const double OldPressure = Pressure;
707 for (i = 0; i < HeatTransferCoeff.size(); i++) {
708 dU += HeatTransferCoeff[i]->GetValue();
713 (dU * dt - Pressure * dVolumeIdeal) / (Cv_air * Contents * R);
715 Temperature = Parent->GetTemperature();
719 const double IdealPressure = Contents * R * Temperature / MaxVolume;
721 Pressure = max(IdealPressure, ParentPressure);
725 const double AddedVolume = BlowerInput->GetValue() * dt;
726 if (AddedVolume > 0.0) {
727 Contents += Pressure * AddedVolume / (R * Temperature);
735 if ((ValveCoefficient > 0.0) &&
736 ((ValveOpen > 0.0) || (Pressure > AirPressure + MaxOverpressure))) {
737 const double DeltaPressure = Pressure - AirPressure;
738 const double VolumeValved =
739 ((Pressure > AirPressure + MaxOverpressure) ? 1.0 : ValveOpen) *
740 ValveCoefficient * DeltaPressure * dt;
744 max(1.0, Contents - Pressure * VolumeValved / (R * Temperature));
748 Volume = Contents * R * Temperature / Pressure;
750 Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
757 const double mass = Contents * M_air;
758 double Ixx, Iyy, Izz;
759 if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
760 (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
762 Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
763 Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
764 Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
765 }
else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
766 (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
768 Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
770 (1.0 / 4.0) * mass * Yradius * Zradius +
771 (1.0 / 12.0) * mass * Xwidth * Xwidth;
773 (1.0 / 4.0) * mass * Yradius * Zradius +
774 (1.0 / 12.0) * mass * Xwidth * Xwidth;
777 Ixx = Iyy = Izz = 0.0;
780 ballonetJ(1,1) = Ixx;
781 ballonetJ(2,2) = Iyy;
782 ballonetJ(3,3) = Izz;
806 void FGBallonet::Debug(
int from)
808 if (debug_lvl <= 0)
return;
812 cout <<
" Ballonet holds " << Contents <<
" mol air" << endl;
813 cout <<
" Location (X, Y, Z) (in.): " << vXYZ(eX) <<
", " <<
814 vXYZ(eY) <<
", " << vXYZ(eZ) << endl;
815 cout <<
" Maximum volume: " << MaxVolume <<
" ft3" << endl;
816 cout <<
" Relief valve release pressure: " << MaxOverpressure <<
818 cout <<
" Relief valve coefficient: " << ValveCoefficient <<
819 " ft4*sec/slug" << endl;
820 cout <<
" Initial temperature: " << Temperature <<
" Rankine" <<
822 cout <<
" Initial pressure: " << Pressure <<
" lbs/ft2" << endl;
823 cout <<
" Initial volume: " << Volume <<
" ft3" << endl;
824 cout <<
" Initial mass: " <<
GetMass() <<
" slug mass" << endl;
825 cout <<
" Initial weight: " <<
GetMass()*slugtolb <<
826 " lbs force" << endl;
827 cout <<
" Heat transfer: " << endl;
830 if (debug_lvl & 2 ) {
831 if (from == 0) cout <<
"Instantiated: FGBallonet" << endl;
832 if (from == 1) cout <<
"Destroyed: FGBallonet" << endl;
834 if (debug_lvl & 4 ) {
836 if (debug_lvl & 8 ) {
837 cout <<
" Ballonet holds " << Contents <<
839 cout <<
" Temperature: " << Temperature <<
" Rankine" << endl;
840 cout <<
" Pressure: " << Pressure <<
" lbs/ft2" << endl;
841 cout <<
" Volume: " << Volume <<
" ft3" << endl;
842 cout <<
" Mass: " <<
GetMass() <<
" slug mass" << endl;
843 cout <<
" Weight: " <<
GetMass()*slugtolb <<
" lbs force" << endl;
845 if (debug_lvl & 16) {
847 if (debug_lvl & 64) {
849 cout << IdSrc << endl;
850 cout << IdHdr << endl;
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
FGGasCell(FGFDMExec *exec, Element *el, unsigned int num, const struct Inputs &input)
Constructor.
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 * FindElement(const std::string &el="")
Searches for a specified element.
double FindElementValueAsNumber(const std::string &el="")
Searches for the named element and returns the data belonging to it as a number.
double GetMass(void) const
Get the current mass of the gas cell (including any ballonets)
void Calculate(double dt)
Runs the gas cell model; called by BuoyantForces.
void Calculate(double dt)
Runs the ballonet model; called by FGGasCell.
FGPropertyManager * GetPropertyManager(void)
Returns a pointer to the property manager object.
void Tie(const std::string &name, bool *pointer, bool useDefault=true)
Tie a property to an external bool variable.
const FGColumnVector3 & GetXYZ(void) const
Get the center of gravity location of the gas cell (including any ballonets)
void SetWritable(const std::string &name, bool state=true)
Set the state of the write attribute for a property.
Represents a mathematical function.
FGMatrix33 GetPointmassInertia(double mass_sl, const FGColumnVector3 &r) const
Computes the inertia contribution of a pointmass.
This class implements a 3 element column vector.
Element * FindNextElement(const std::string &el="")
Searches for the next element as specified.
Handles matrix math operations.
Models a ballonet inside a gas cell.
Encapsulates the JSBSim simulation executive.
Utility class that aids in the conversion of forces between coordinate systems and calculation of mom...
FGColumnVector3 FindElementTripletConvertTo(const std::string &target_units)
Composes a 3-element column vector for the supplied location or orientation.
FGMassBalance * GetMassBalance(void)
Returns the FGAircraft pointer.