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;
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;
198 property_name = base_property_name +
"/max_volume-ft3";
199 PropertyManager->
Tie( property_name.c_str(), &MaxVolume);
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) {
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};
369 gasCellJ.InitMatrix();
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();
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;
522 const string s(
"Fatal Error: No location found for this ballonet.");
523 cerr << el->
ReadFrom() << endl << s << endl;
530 if (el->FindElement(
"x_radius")) {
531 Xradius = el->FindElementValueAsNumberConvertTo(
"x_radius",
"FT");
534 Yradius = el->FindElementValueAsNumberConvertTo(
"y_radius",
"FT");
537 Zradius = el->FindElementValueAsNumberConvertTo(
"z_radius",
"FT");
541 Xwidth = el->FindElementValueAsNumberConvertTo(
"x_width",
"FT");
544 Ywidth = el->FindElementValueAsNumberConvertTo(
"y_width",
"FT");
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;
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);
652 while (function_element) {
653 HeatTransferCoeff.push_back(
new FGFunction(exec, function_element));
660 BlowerInput =
new FGFunction(exec, function_element);
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);
704 Temperature = Parent->GetTemperature();
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);
745 ballonetJ.InitMatrix();
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;
773 ballonetJ += MassBalance->GetPointmassInertia(
GetMass(),
GetXYZ());