Branch data Line data Source code
1 : : /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2 : :
3 : : Header: FGGasCell.h
4 : : Author: Anders Gidenstam
5 : : Date started: 01/21/2006
6 : :
7 : : ----- Copyright (C) 2006 - 2008 Anders Gidenstam (anders(at)gidenstam.org) --
8 : :
9 : : This program is free software; you can redistribute it and/or modify it under
10 : : the terms of the GNU Lesser General Public License as published by the Free Software
11 : : Foundation; either version 2 of the License, or (at your option) any later
12 : : version.
13 : :
14 : : This program is distributed in the hope that it will be useful, but WITHOUT
15 : : ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 : : FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
17 : : details.
18 : :
19 : : You should have received a copy of the GNU Lesser General Public License along with
20 : : this program; if not, write to the Free Software Foundation, Inc., 59 Temple
21 : : Place - Suite 330, Boston, MA 02111-1307, USA.
22 : :
23 : : Further information about the GNU Lesser General Public License can also be found on
24 : : the world wide web at http://www.gnu.org.
25 : :
26 : : FUNCTIONAL DESCRIPTION
27 : : --------------------------------------------------------------------------------
28 : : See header file.
29 : :
30 : : HISTORY
31 : : --------------------------------------------------------------------------------
32 : : 01/21/2006 AG Created
33 : :
34 : : %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 : : INCLUDES
36 : : %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
37 : :
38 : : #include "FGFDMExec.h"
39 : : #include "models/FGAuxiliary.h"
40 : : #include "models/FGAtmosphere.h"
41 : : #include "models/FGInertial.h"
42 : : #include "models/FGMassBalance.h"
43 : : #include "FGGasCell.h"
44 : : #include "input_output/FGXMLElement.h"
45 : : #include <iostream>
46 : : #include <cstdlib>
47 : :
48 : : using std::cerr;
49 : : using std::endl;
50 : : using std::cout;
51 : : using std::string;
52 : : using std::max;
53 : :
54 : : namespace JSBSim {
55 : :
56 : : static const char *IdSrc = "$Id: FGGasCell.cpp,v 1.12 2009/10/24 22:59:30 jberndt Exp $";
57 : : static const char *IdHdr = ID_GASCELL;
58 : :
59 : : /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
60 : : CLASS IMPLEMENTATION
61 : : %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
62 : : /* Constants. */
63 : : const double FGGasCell::R = 3.4071; // [lbs ft/(mol Rankine)]
64 : : const double FGGasCell::M_air = 0.0019186; // [slug/mol]
65 : : const double FGGasCell::M_hydrogen = 0.00013841; // [slug/mol]
66 : : const double FGGasCell::M_helium = 0.00027409; // [slug/mol]
67 : :
68 : 0 : FGGasCell::FGGasCell(FGFDMExec* exec, Element* el, int num) : FGForce(exec)
69 : : {
70 : 0 : string token;
71 : : Element* element;
72 : :
73 : 0 : Auxiliary = exec->GetAuxiliary();
74 : 0 : Atmosphere = exec->GetAtmosphere();
75 : 0 : PropertyManager = exec->GetPropertyManager();
76 : 0 : Inertial = exec->GetInertial();
77 : 0 : MassBalance = exec->GetMassBalance();
78 : :
79 : 0 : gasCellJ = FGMatrix33();
80 : 0 : gasCellM = FGColumnVector3();
81 : :
82 : : Buoyancy = MaxVolume = MaxOverpressure = Temperature = Pressure =
83 : 0 : Contents = Volume = dVolumeIdeal = 0.0;
84 : 0 : Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
85 : 0 : ValveCoefficient = ValveOpen = 0.0;
86 : 0 : CellNum = num;
87 : :
88 : : // NOTE: In the local system X points north, Y points east and Z points down.
89 : 0 : SetTransformType(FGForce::tLocalBody);
90 : :
91 : 0 : type = el->GetAttributeValue("type");
92 [ # # ][ # # ]: 0 : if (type == "HYDROGEN") Type = ttHYDROGEN;
93 [ # # ][ # # ]: 0 : else if (type == "HELIUM") Type = ttHELIUM;
94 [ # # ][ # # ]: 0 : else if (type == "AIR") Type = ttAIR;
95 : 0 : else Type = ttUNKNOWN;
96 : :
97 : 0 : element = el->FindElement("location");
98 [ # # # # ]: 0 : if (element) {
99 : 0 : vXYZ = element->FindElementTripletConvertTo("IN");
100 : : } else {
101 : 0 : cerr << "Fatal Error: No location found for this gas cell." << endl;
102 : 0 : exit(-1);
103 : : }
104 [ # # ][ # # ]: 0 : if ((el->FindElement("x_radius") || el->FindElement("x_width")) &&
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
105 : : (el->FindElement("y_radius") || el->FindElement("y_width")) &&
106 : : (el->FindElement("z_radius") || el->FindElement("z_width"))) {
107 : :
108 [ # # ][ # # ]: 0 : if (el->FindElement("x_radius")) {
109 : 0 : Xradius = el->FindElementValueAsNumberConvertTo("x_radius", "FT");
110 : : }
111 [ # # ][ # # ]: 0 : if (el->FindElement("y_radius")) {
112 : 0 : Yradius = el->FindElementValueAsNumberConvertTo("y_radius", "FT");
113 : : }
114 [ # # ][ # # ]: 0 : if (el->FindElement("z_radius")) {
115 : 0 : Zradius = el->FindElementValueAsNumberConvertTo("z_radius", "FT");
116 : : }
117 : :
118 [ # # ][ # # ]: 0 : if (el->FindElement("x_width")) {
119 : 0 : Xwidth = el->FindElementValueAsNumberConvertTo("x_width", "FT");
120 : : }
121 [ # # ][ # # ]: 0 : if (el->FindElement("y_width")) {
122 : 0 : Ywidth = el->FindElementValueAsNumberConvertTo("y_width", "FT");
123 : : }
124 [ # # ][ # # ]: 0 : if (el->FindElement("z_width")) {
125 : 0 : Zwidth = el->FindElementValueAsNumberConvertTo("z_width", "FT");
126 : : }
127 : :
128 : : // The volume is a (potentially) extruded ellipsoid.
129 : : // However, currently only a few combinations of radius and width are
130 : : // fully supported.
131 [ # # ][ # # ]: 0 : if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
132 : : (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
133 : : // Ellipsoid volume.
134 : 0 : MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
135 [ # # ][ # # ]: 0 : } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
136 : : (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
137 : : // Cylindrical volume.
138 : 0 : MaxVolume = M_PI * Yradius * Zradius * Xwidth;
139 : : } else {
140 : 0 : cerr << "Warning: Unsupported gas cell shape." << endl;
141 : : MaxVolume =
142 : : (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
143 : : M_PI * Yradius * Zradius * Xwidth +
144 : : M_PI * Xradius * Zradius * Ywidth +
145 : : M_PI * Xradius * Yradius * Zwidth +
146 : : 2.0 * Xradius * Ywidth * Zwidth +
147 : : 2.0 * Yradius * Xwidth * Zwidth +
148 : : 2.0 * Zradius * Xwidth * Ywidth +
149 : 0 : Xwidth * Ywidth * Zwidth);
150 : : }
151 : : } else {
152 : 0 : cerr << "Fatal Error: Gas cell shape must be given." << endl;
153 : 0 : exit(-1);
154 : : }
155 [ # # ][ # # ]: 0 : if (el->FindElement("max_overpressure")) {
156 : : MaxOverpressure = el->FindElementValueAsNumberConvertTo("max_overpressure",
157 : 0 : "LBS/FT2");
158 : : }
159 [ # # ][ # # ]: 0 : if (el->FindElement("fullness")) {
160 : 0 : const double Fullness = el->FindElementValueAsNumber("fullness");
161 [ # # # # ]: 0 : if (0 <= Fullness) {
162 : 0 : Volume = Fullness * MaxVolume;
163 : : } else {
164 : 0 : cerr << "Warning: Invalid initial gas cell fullness value." << endl;
165 : : }
166 : : }
167 [ # # ][ # # ]: 0 : if (el->FindElement("valve_coefficient")) {
168 : : ValveCoefficient =
169 : : el->FindElementValueAsNumberConvertTo("valve_coefficient",
170 : 0 : "FT4*SEC/SLUG");
171 : 0 : ValveCoefficient = max(ValveCoefficient, 0.0);
172 : : }
173 : :
174 : : // Initialize state
175 : 0 : SetLocation(vXYZ);
176 : :
177 [ # # ][ # # ]: 0 : if (Temperature == 0.0) {
178 : 0 : Temperature = Atmosphere->GetTemperature();
179 : : }
180 [ # # ][ # # ]: 0 : if (Pressure == 0.0) {
181 : 0 : Pressure = Atmosphere->GetPressure();
182 : : }
183 [ # # ][ # # ]: 0 : if (Volume != 0.0) {
184 : : // Calculate initial gas content.
185 : 0 : Contents = Pressure * Volume / (R * Temperature);
186 : :
187 : : // Clip to max allowed value.
188 : 0 : const double IdealPressure = Contents * R * Temperature / MaxVolume;
189 [ # # ][ # # ]: 0 : if (IdealPressure > Pressure + MaxOverpressure) {
190 : 0 : Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
191 : 0 : Pressure = Pressure + MaxOverpressure;
192 : : } else {
193 : 0 : Pressure = max(IdealPressure, Pressure);
194 : : }
195 : : } else {
196 : : // Calculate initial gas content.
197 : 0 : Contents = Pressure * MaxVolume / (R * Temperature);
198 : : }
199 : :
200 : 0 : Volume = Contents * R * Temperature / Pressure;
201 : 0 : Mass = Contents * M_gas();
202 : :
203 : : // Bind relevant properties
204 : 0 : string property_name, base_property_name;
205 : :
206 : 0 : base_property_name = CreateIndexedPropertyName("buoyant_forces/gas-cell", CellNum);
207 : :
208 : 0 : property_name = base_property_name + "/max_volume-ft3";
209 : 0 : PropertyManager->Tie( property_name.c_str(), &MaxVolume );
210 : 0 : PropertyManager->SetWritable( property_name, false );
211 : 0 : property_name = base_property_name + "/temp-R";
212 : 0 : PropertyManager->Tie( property_name.c_str(), &Temperature );
213 : 0 : property_name = base_property_name + "/pressure-psf";
214 : 0 : PropertyManager->Tie( property_name.c_str(), &Pressure );
215 : 0 : property_name = base_property_name + "/volume-ft3";
216 : 0 : PropertyManager->Tie( property_name.c_str(), &Volume );
217 : 0 : property_name = base_property_name + "/buoyancy-lbs";
218 : 0 : PropertyManager->Tie( property_name.c_str(), &Buoyancy );
219 : 0 : property_name = base_property_name + "/contents-mol";
220 : 0 : PropertyManager->Tie( property_name.c_str(), &Contents );
221 : 0 : property_name = base_property_name + "/valve_open";
222 : 0 : PropertyManager->Tie( property_name.c_str(), &ValveOpen );
223 : :
224 : 0 : Debug(0);
225 : :
226 : : // Read heat transfer coefficients
227 [ # # ][ # # ]: 0 : if (Element* heat = el->FindElement("heat")) {
228 : 0 : Element* function_element = heat->FindElement("function");
229 [ # # ][ # # ]: 0 : while (function_element) {
230 : : HeatTransferCoeff.push_back(new FGFunction(PropertyManager,
231 : 0 : function_element));
232 : 0 : function_element = heat->FindNextElement("function");
233 : : }
234 : : }
235 : :
236 : : // Load ballonets if there are any
237 [ # # ][ # # ]: 0 : if (Element* ballonet_element = el->FindElement("ballonet")) {
238 [ # # ][ # # ]: 0 : while (ballonet_element) {
239 : : Ballonet.push_back(new FGBallonet(exec,
240 : : ballonet_element,
241 : : Ballonet.size(),
242 : 0 : this));
243 : 0 : ballonet_element = el->FindNextElement("ballonet");
244 : : }
245 : 0 : }
246 : :
247 : 0 : }
248 : :
249 : : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
250 : :
251 : 0 : FGGasCell::~FGGasCell()
252 : : {
253 : : unsigned int i;
254 : :
255 [ # # ][ # # ]: 0 : for (i = 0; i < HeatTransferCoeff.size(); i++) delete HeatTransferCoeff[i];
[ # # ][ # # ]
256 : 0 : HeatTransferCoeff.clear();
257 : :
258 [ # # ][ # # ]: 0 : for (i = 0; i < Ballonet.size(); i++) delete Ballonet[i];
[ # # ][ # # ]
259 : 0 : Ballonet.clear();
260 : :
261 : 0 : Debug(1);
262 : 0 : }
263 : :
264 : : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
265 : :
266 : 0 : void FGGasCell::Calculate(double dt)
267 : : {
268 : 0 : const double AirTemperature = Atmosphere->GetTemperature(); // [Rankine]
269 : 0 : const double AirPressure = Atmosphere->GetPressure(); // [lbs/ft²]
270 : 0 : const double AirDensity = Atmosphere->GetDensity(); // [slug/ft³]
271 : 0 : const double g = Inertial->gravity(); // [lbs/slug]
272 : :
273 : 0 : const double OldTemperature = Temperature;
274 : 0 : const double OldPressure = Pressure;
275 : : unsigned int i;
276 : 0 : const unsigned int no_ballonets = Ballonet.size();
277 : :
278 : : //-- Read ballonet state --
279 : : // NOTE: This model might need a more proper integration technique.
280 : 0 : double BallonetsVolume = 0.0;
281 : 0 : double BallonetsHeatFlow = 0.0;
282 [ # # ]: 0 : for (i = 0; i < no_ballonets; i++) {
283 : 0 : BallonetsVolume += Ballonet[i]->GetVolume();
284 : 0 : BallonetsHeatFlow += Ballonet[i]->GetHeatFlow();
285 : : }
286 : :
287 : : //-- Gas temperature --
288 : :
289 [ # # ]: 0 : if (HeatTransferCoeff.size() > 0) {
290 : : // The model is based on the ideal gas law.
291 : : // However, it does look a bit fishy. Please verify.
292 : : // dT/dt = dU / (Cv n R)
293 : 0 : double dU = 0.0;
294 [ # # ]: 0 : for (i = 0; i < HeatTransferCoeff.size(); i++) {
295 : 0 : dU += HeatTransferCoeff[i]->GetValue();
296 : : }
297 : : // Don't include dt when accounting for adiabatic expansion/contraction.
298 : : // The rate of adiabatic cooling looks about right: ~5.4 Rankine/1000ft.
299 [ # # ]: 0 : if (Contents > 0) {
300 : : Temperature +=
301 : : (dU * dt - Pressure * dVolumeIdeal - BallonetsHeatFlow) /
302 : 0 : (Cv_gas() * Contents * R);
303 : : } else {
304 : 0 : Temperature = AirTemperature;
305 : : }
306 : : } else {
307 : : // No simulation of complex temperature changes.
308 : : // Note: Making the gas cell behave adiabatically might be a better
309 : : // option.
310 : 0 : Temperature = AirTemperature;
311 : : }
312 : :
313 : : //-- Pressure --
314 : : const double IdealPressure =
315 : 0 : Contents * R * Temperature / (MaxVolume - BallonetsVolume);
316 [ # # ]: 0 : if (IdealPressure > AirPressure + MaxOverpressure) {
317 : 0 : Pressure = AirPressure + MaxOverpressure;
318 : : } else {
319 : 0 : Pressure = max(IdealPressure, AirPressure);
320 : : }
321 : :
322 : : //-- Manual valving --
323 : :
324 : : // FIXME: Presently the effect of manual valving is computed using
325 : : // an ad hoc formula which might not be a good representation
326 : : // of reality.
327 [ # # ][ # # ]: 0 : if ((ValveCoefficient > 0.0) && (ValveOpen > 0.0)) {
328 : : // First compute the difference in pressure between the gas in the
329 : : // cell and the air above it.
330 : : // FixMe: CellHeight should depend on current volume.
331 : 0 : const double CellHeight = 2 * Zradius + Zwidth; // [ft]
332 : 0 : const double GasMass = Contents * M_gas(); // [slug]
333 : 0 : const double GasVolume = Contents * R * Temperature / Pressure; // [ft³]
334 : 0 : const double GasDensity = GasMass / GasVolume;
335 : : const double DeltaPressure =
336 : 0 : Pressure + CellHeight * g * (AirDensity - GasDensity) - AirPressure;
337 : : const double VolumeValved =
338 : 0 : ValveOpen * ValveCoefficient * DeltaPressure * dt;
339 : : Contents =
340 : 0 : max(0.0, Contents - Pressure * VolumeValved / (R * Temperature));
341 : : }
342 : :
343 : : //-- Update ballonets. --
344 : : // Doing that here should give them the opportunity to react to the
345 : : // new pressure.
346 : 0 : BallonetsVolume = 0.0;
347 [ # # ]: 0 : for (i = 0; i < no_ballonets; i++) {
348 : 0 : Ballonet[i]->Calculate(dt);
349 : 0 : BallonetsVolume += Ballonet[i]->GetVolume();
350 : : }
351 : :
352 : : //-- Automatic safety valving. --
353 [ # # ]: 0 : if (Contents * R * Temperature / (MaxVolume - BallonetsVolume) >
354 : : AirPressure + MaxOverpressure) {
355 : : // Gas is automatically valved. Valving capacity is assumed to be infinite.
356 : : // FIXME: This could/should be replaced by damage to the gas cell envelope.
357 : : Contents =
358 : : (AirPressure + MaxOverpressure) *
359 : 0 : (MaxVolume - BallonetsVolume) / (R * Temperature);
360 : : }
361 : :
362 : : //-- Volume --
363 : 0 : Volume = Contents * R * Temperature / Pressure + BallonetsVolume;
364 : : dVolumeIdeal =
365 : 0 : Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
366 : :
367 : : //-- Current buoyancy --
368 : : // The buoyancy is computed using the atmospheres local density.
369 : 0 : Buoyancy = Volume * AirDensity * g;
370 : :
371 : : // Note: This is gross buoyancy. The weight of the gas itself and
372 : : // any ballonets is not deducted here as the effects of the gas mass
373 : : // is handled by FGMassBalance.
374 : 0 : vFn.InitMatrix(0.0, 0.0, - Buoyancy);
375 : :
376 : : // Compute the inertia of the gas cell.
377 : : // Consider the gas cell as a shape of uniform density.
378 : : // FIXME: If the cell isn't ellipsoid or cylindrical the inertia will
379 : : // be wrong.
380 : 0 : gasCellJ = FGMatrix33();
381 : 0 : const double mass = Contents * M_gas();
382 : : double Ixx, Iyy, Izz;
383 [ # # ][ # # ]: 0 : if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
[ # # ][ # # ]
[ # # ][ # # ]
384 : : (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
385 : : // Ellipsoid volume.
386 : 0 : Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
387 : 0 : Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
388 : 0 : Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
389 [ # # ][ # # ]: 0 : } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
[ # # ][ # # ]
[ # # ][ # # ]
390 : : (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
391 : : // Cylindrical volume (might not be valid with an elliptical cross-section).
392 : 0 : Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
393 : : Iyy =
394 : : (1.0 / 4.0) * mass * Yradius * Zradius +
395 : 0 : (1.0 / 12.0) * mass * Xwidth * Xwidth;
396 : : Izz =
397 : : (1.0 / 4.0) * mass * Yradius * Zradius +
398 : 0 : (1.0 / 12.0) * mass * Xwidth * Xwidth;
399 : : } else {
400 : : // Not supported. Revert to pointmass model.
401 : 0 : Ixx = Iyy = Izz = 0.0;
402 : : }
403 : : // The volume is symmetric, so Ixy = Ixz = Iyz = 0.
404 : 0 : gasCellJ(1,1) = Ixx;
405 : 0 : gasCellJ(2,2) = Iyy;
406 : 0 : gasCellJ(3,3) = Izz;
407 : 0 : Mass = mass;
408 : 0 : gasCellM.InitMatrix();
409 : : gasCellM(eX) +=
410 : 0 : GetXYZ(eX) * Mass*slugtolb;
411 : : gasCellM(eY) +=
412 : 0 : GetXYZ(eY) * Mass*slugtolb;
413 : : gasCellM(eZ) +=
414 : 0 : GetXYZ(eZ) * Mass*slugtolb;
415 : :
416 [ # # ]: 0 : if (no_ballonets > 0) {
417 : : // Add the mass, moment and inertia of any ballonets.
418 : 0 : const FGColumnVector3 p = MassBalance->StructuralToBody( GetXYZ() );
419 : :
420 [ # # ]: 0 : for (i = 0; i < no_ballonets; i++) {
421 : 0 : Mass += Ballonet[i]->GetMass();
422 : :
423 : : // Add ballonet moments.
424 : : gasCellM(eX) +=
425 : 0 : Ballonet[i]->GetXYZ(eX) * Ballonet[i]->GetMass()*slugtolb;
426 : : gasCellM(eY) +=
427 : 0 : Ballonet[i]->GetXYZ(eY) * Ballonet[i]->GetMass()*slugtolb;
428 : : gasCellM(eZ) +=
429 : 0 : Ballonet[i]->GetXYZ(eZ) * Ballonet[i]->GetMass()*slugtolb;
430 : :
431 : : // Moments of inertia must be converted to the gas cell frame here.
432 : : FGColumnVector3 v =
433 : 0 : MassBalance->StructuralToBody( Ballonet[i]->GetXYZ() ) - p;
434 : : // Body basis is in FT.
435 : 0 : const double mass = Ballonet[i]->GetMass();
436 : : gasCellJ += Ballonet[i]->GetInertia() +
437 : : FGMatrix33( 0, - mass*v(1)*v(2), - mass*v(1)*v(3),
438 : : - mass*v(2)*v(1), 0, - mass*v(2)*v(3),
439 : 0 : - mass*v(3)*v(1), - mass*v(3)*v(2), 0 );
440 : 0 : }
441 : : }
442 : 0 : }
443 : :
444 : : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
445 : : // The bitmasked value choices are as follows:
446 : : // unset: In this case (the default) JSBSim would only print
447 : : // out the normally expected messages, essentially echoing
448 : : // the config files as they are read. If the environment
449 : : // variable is not set, debug_lvl is set to 1 internally
450 : : // 0: This requests JSBSim not to output any messages
451 : : // whatsoever.
452 : : // 1: This value explicity requests the normal JSBSim
453 : : // startup messages
454 : : // 2: This value asks for a message to be printed out when
455 : : // a class is instantiated
456 : : // 4: When this value is set, a message is displayed when a
457 : : // FGModel object executes its Run() method
458 : : // 8: When this value is set, various runtime state variables
459 : : // are printed out periodically
460 : : // 16: When set various parameters are sanity checked and
461 : : // a message is printed out when they go out of bounds
462 : :
463 : 0 : void FGGasCell::Debug(int from)
464 : : {
465 [ # # ]: 0 : if (debug_lvl <= 0) return;
466 : :
467 [ # # ]: 0 : if (debug_lvl & 1) { // Standard console startup message output
468 [ # # ]: 0 : if (from == 0) { // Constructor
469 : : cout << " Gas cell holds " << Contents << " mol " <<
470 : 0 : type << endl;
471 : : cout << " Cell location (X, Y, Z) (in.): " << vXYZ(eX) << ", " <<
472 : 0 : vXYZ(eY) << ", " << vXYZ(eZ) << endl;
473 : 0 : cout << " Maximum volume: " << MaxVolume << " ft3" << endl;
474 : : cout << " Relief valve release pressure: " << MaxOverpressure <<
475 : 0 : " lbs/ft2" << endl;
476 : : cout << " Manual valve coefficient: " << ValveCoefficient <<
477 : 0 : " ft4*sec/slug" << endl;
478 : : cout << " Initial temperature: " << Temperature << " Rankine" <<
479 : 0 : endl;
480 : 0 : cout << " Initial pressure: " << Pressure << " lbs/ft2" << endl;
481 : 0 : cout << " Initial volume: " << Volume << " ft3" << endl;
482 : 0 : cout << " Initial mass: " << GetMass() << " slug mass" << endl;
483 : : cout << " Initial weight: " << GetMass()*lbtoslug << " lbs force" <<
484 : 0 : endl;
485 : 0 : cout << " Heat transfer: " << endl;
486 : : }
487 : : }
488 [ # # ]: 0 : if (debug_lvl & 2 ) { // Instantiation/Destruction notification
489 [ # # ]: 0 : if (from == 0) cout << "Instantiated: FGGasCell" << endl;
490 [ # # ]: 0 : if (from == 1) cout << "Destroyed: FGGasCell" << endl;
491 : : }
492 : 0 : if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
493 : : }
494 [ # # ]: 0 : if (debug_lvl & 8 ) { // Runtime state variables
495 : 0 : cout << " " << type << " cell holds " << Contents << " mol " << endl;
496 : 0 : cout << " Temperature: " << Temperature << " Rankine" << endl;
497 : 0 : cout << " Pressure: " << Pressure << " lbs/ft2" << endl;
498 : 0 : cout << " Volume: " << Volume << " ft3" << endl;
499 : 0 : cout << " Mass: " << GetMass() << " slug mass" << endl;
500 : 0 : cout << " Weight: " << GetMass()*lbtoslug << " lbs force" << endl;
501 : : }
502 : 0 : if (debug_lvl & 16) { // Sanity checking
503 : : }
504 [ # # ]: 0 : if (debug_lvl & 64) {
505 [ # # ]: 0 : if (from == 0) { // Constructor
506 : 0 : cout << IdSrc << endl;
507 : 0 : cout << IdHdr << endl;
508 : : }
509 : : }
510 : : }
511 : :
512 : : /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
513 : : CLASS IMPLEMENTATION
514 : : %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
515 : : const double FGBallonet::R = 3.4071; // [lbs ft/(mol Rankine)]
516 : : const double FGBallonet::M_air = 0.0019186; // [slug/mol]
517 : : const double FGBallonet::Cv_air = 5.0/2.0; // [??]
518 : :
519 : 0 : FGBallonet::FGBallonet(FGFDMExec* exec, Element* el, int num, FGGasCell* parent)
520 : : {
521 : 0 : string token;
522 : : Element* element;
523 : :
524 : 0 : Auxiliary = exec->GetAuxiliary();
525 : 0 : Atmosphere = exec->GetAtmosphere();
526 : 0 : PropertyManager = exec->GetPropertyManager();
527 : 0 : Inertial = exec->GetInertial();
528 : :
529 : 0 : ballonetJ = FGMatrix33();
530 : :
531 : : MaxVolume = MaxOverpressure = Temperature = Pressure =
532 : 0 : Contents = Volume = dVolumeIdeal = dU = 0.0;
533 : 0 : Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0;
534 : 0 : ValveCoefficient = ValveOpen = 0.0;
535 : 0 : BlowerInput = NULL;
536 : 0 : CellNum = num;
537 : 0 : Parent = parent;
538 : :
539 : : // NOTE: In the local system X points north, Y points east and Z points down.
540 : 0 : element = el->FindElement("location");
541 [ # # # # ]: 0 : if (element) {
542 : 0 : vXYZ = element->FindElementTripletConvertTo("IN");
543 : : } else {
544 : 0 : cerr << "Fatal Error: No location found for this ballonet." << endl;
545 : 0 : exit(-1);
546 : : }
547 [ # # ][ # # ]: 0 : if ((el->FindElement("x_radius") || el->FindElement("x_width")) &&
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
548 : : (el->FindElement("y_radius") || el->FindElement("y_width")) &&
549 : : (el->FindElement("z_radius") || el->FindElement("z_width"))) {
550 : :
551 [ # # ][ # # ]: 0 : if (el->FindElement("x_radius")) {
552 : 0 : Xradius = el->FindElementValueAsNumberConvertTo("x_radius", "FT");
553 : : }
554 [ # # ][ # # ]: 0 : if (el->FindElement("y_radius")) {
555 : 0 : Yradius = el->FindElementValueAsNumberConvertTo("y_radius", "FT");
556 : : }
557 [ # # ][ # # ]: 0 : if (el->FindElement("z_radius")) {
558 : 0 : Zradius = el->FindElementValueAsNumberConvertTo("z_radius", "FT");
559 : : }
560 : :
561 [ # # ][ # # ]: 0 : if (el->FindElement("x_width")) {
562 : 0 : Xwidth = el->FindElementValueAsNumberConvertTo("x_width", "FT");
563 : : }
564 [ # # ][ # # ]: 0 : if (el->FindElement("y_width")) {
565 : 0 : Ywidth = el->FindElementValueAsNumberConvertTo("y_width", "FT");
566 : : }
567 [ # # ][ # # ]: 0 : if (el->FindElement("z_width")) {
568 : 0 : Zwidth = el->FindElementValueAsNumberConvertTo("z_width", "FT");
569 : : }
570 : :
571 : : // The volume is a (potentially) extruded ellipsoid.
572 : : // FIXME: However, currently only a few combinations of radius and
573 : : // width are fully supported.
574 [ # # ][ # # ]: 0 : if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
575 : : (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
576 : : // Ellipsoid volume.
577 : 0 : MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0;
578 [ # # ][ # # ]: 0 : } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
579 : : (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
580 : : // Cylindrical volume.
581 : 0 : MaxVolume = M_PI * Yradius * Zradius * Xwidth;
582 : : } else {
583 : 0 : cerr << "Warning: Unsupported ballonet shape." << endl;
584 : : MaxVolume =
585 : : (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 +
586 : : M_PI * Yradius * Zradius * Xwidth +
587 : : M_PI * Xradius * Zradius * Ywidth +
588 : : M_PI * Xradius * Yradius * Zwidth +
589 : : 2.0 * Xradius * Ywidth * Zwidth +
590 : : 2.0 * Yradius * Xwidth * Zwidth +
591 : : 2.0 * Zradius * Xwidth * Ywidth +
592 : 0 : Xwidth * Ywidth * Zwidth);
593 : : }
594 : : } else {
595 : 0 : cerr << "Fatal Error: Ballonet shape must be given." << endl;
596 : 0 : exit(-1);
597 : : }
598 [ # # ][ # # ]: 0 : if (el->FindElement("max_overpressure")) {
599 : : MaxOverpressure = el->FindElementValueAsNumberConvertTo("max_overpressure",
600 : 0 : "LBS/FT2");
601 : : }
602 [ # # ][ # # ]: 0 : if (el->FindElement("fullness")) {
603 : 0 : const double Fullness = el->FindElementValueAsNumber("fullness");
604 [ # # # # ]: 0 : if (0 <= Fullness) {
605 : 0 : Volume = Fullness * MaxVolume;
606 : : } else {
607 : 0 : cerr << "Warning: Invalid initial ballonet fullness value." << endl;
608 : : }
609 : : }
610 [ # # ][ # # ]: 0 : if (el->FindElement("valve_coefficient")) {
611 : : ValveCoefficient =
612 : : el->FindElementValueAsNumberConvertTo("valve_coefficient",
613 : 0 : "FT4*SEC/SLUG");
614 : 0 : ValveCoefficient = max(ValveCoefficient, 0.0);
615 : : }
616 : :
617 : : // Initialize state
618 [ # # ][ # # ]: 0 : if (Temperature == 0.0) {
619 : 0 : Temperature = Parent->GetTemperature();
620 : : }
621 [ # # ][ # # ]: 0 : if (Pressure == 0.0) {
622 : 0 : Pressure = Parent->GetPressure();
623 : : }
624 [ # # ][ # # ]: 0 : if (Volume != 0.0) {
625 : : // Calculate initial air content.
626 : 0 : Contents = Pressure * Volume / (R * Temperature);
627 : :
628 : : // Clip to max allowed value.
629 : 0 : const double IdealPressure = Contents * R * Temperature / MaxVolume;
630 [ # # ][ # # ]: 0 : if (IdealPressure > Pressure + MaxOverpressure) {
631 : 0 : Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature);
632 : 0 : Pressure = Pressure + MaxOverpressure;
633 : : } else {
634 : 0 : Pressure = max(IdealPressure, Pressure);
635 : : }
636 : : } else {
637 : : // Calculate initial air content.
638 : 0 : Contents = Pressure * MaxVolume / (R * Temperature);
639 : : }
640 : :
641 : 0 : Volume = Contents * R * Temperature / Pressure;
642 : :
643 : : // Bind relevant properties
644 : 0 : string property_name, base_property_name;
645 : 0 : base_property_name = CreateIndexedPropertyName("buoyant_forces/gas-cell", Parent->GetIndex());
646 : 0 : base_property_name = CreateIndexedPropertyName(base_property_name + "/ballonet", CellNum);
647 : :
648 : 0 : property_name = base_property_name + "/max_volume-ft3";
649 : 0 : PropertyManager->Tie( property_name, &MaxVolume );
650 : 0 : PropertyManager->SetWritable( property_name, false );
651 : :
652 : 0 : property_name = base_property_name + "/temp-R";
653 : 0 : PropertyManager->Tie( property_name, &Temperature );
654 : :
655 : 0 : property_name = base_property_name + "/pressure-psf";
656 : 0 : PropertyManager->Tie( property_name, &Pressure );
657 : :
658 : 0 : property_name = base_property_name + "/volume-ft3";
659 : 0 : PropertyManager->Tie( property_name, &Volume );
660 : :
661 : 0 : property_name = base_property_name + "/contents-mol";
662 : 0 : PropertyManager->Tie( property_name, &Contents );
663 : :
664 : 0 : property_name = base_property_name + "/valve_open";
665 : 0 : PropertyManager->Tie( property_name, &ValveOpen );
666 : :
667 : 0 : Debug(0);
668 : :
669 : : // Read heat transfer coefficients
670 [ # # ][ # # ]: 0 : if (Element* heat = el->FindElement("heat")) {
671 : 0 : Element* function_element = heat->FindElement("function");
672 [ # # ][ # # ]: 0 : while (function_element) {
673 : : HeatTransferCoeff.push_back(new FGFunction(PropertyManager,
674 : 0 : function_element));
675 : 0 : function_element = heat->FindNextElement("function");
676 : : }
677 : : }
678 : : // Read blower input function
679 [ # # ][ # # ]: 0 : if (Element* blower = el->FindElement("blower_input")) {
680 : 0 : Element* function_element = blower->FindElement("function");
681 : : BlowerInput = new FGFunction(PropertyManager,
682 : 0 : function_element);
683 : 0 : }
684 : 0 : }
685 : :
686 : : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
687 : :
688 : 0 : FGBallonet::~FGBallonet()
689 : : {
690 : : unsigned int i;
691 : :
692 [ # # ][ # # ]: 0 : for (i = 0; i < HeatTransferCoeff.size(); i++) delete HeatTransferCoeff[i];
[ # # ][ # # ]
693 : 0 : HeatTransferCoeff.clear();
694 : :
695 [ # # ][ # # ]: 0 : delete BlowerInput;
696 : 0 : BlowerInput = NULL;
697 : :
698 : 0 : Debug(1);
699 : 0 : }
700 : :
701 : : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
702 : :
703 : 0 : void FGBallonet::Calculate(double dt)
704 : : {
705 : 0 : const double ParentPressure = Parent->GetPressure(); // [lbs/ft²]
706 : 0 : const double AirPressure = Atmosphere->GetPressure(); // [lbs/ft²]
707 : :
708 : 0 : const double OldTemperature = Temperature;
709 : 0 : const double OldPressure = Pressure;
710 : : unsigned int i;
711 : :
712 : : //-- Gas temperature --
713 : :
714 : : // The model is based on the ideal gas law.
715 : : // However, it does look a bit fishy. Please verify.
716 : : // dT/dt = dU / (Cv n R)
717 : 0 : dU = 0.0;
718 [ # # ]: 0 : for (i = 0; i < HeatTransferCoeff.size(); i++) {
719 : 0 : dU += HeatTransferCoeff[i]->GetValue();
720 : : }
721 : : // dt is already accounted for in dVolumeIdeal.
722 [ # # ]: 0 : if (Contents > 0) {
723 : : Temperature +=
724 : 0 : (dU * dt - Pressure * dVolumeIdeal) / (Cv_air * Contents * R);
725 : : } else {
726 : 0 : Temperature = Parent->GetTemperature();
727 : : }
728 : :
729 : : //-- Pressure --
730 : 0 : const double IdealPressure = Contents * R * Temperature / MaxVolume;
731 : : // The pressure is at least that of the parent gas cell.
732 : 0 : Pressure = max(IdealPressure, ParentPressure);
733 : :
734 : : //-- Blower input --
735 [ # # ]: 0 : if (BlowerInput) {
736 : 0 : const double AddedVolume = BlowerInput->GetValue() * dt;
737 [ # # ]: 0 : if (AddedVolume > 0.0) {
738 : 0 : Contents += Pressure * AddedVolume / (R * Temperature);
739 : : }
740 : : }
741 : :
742 : : //-- Pressure relief and manual valving --
743 : : // FIXME: Presently the effect of valving is computed using
744 : : // an ad hoc formula which might not be a good representation
745 : : // of reality.
746 [ # # ][ # # ]: 0 : if ((ValveCoefficient > 0.0) &&
[ # # ]
747 : : ((ValveOpen > 0.0) || (Pressure > AirPressure + MaxOverpressure))) {
748 : 0 : const double DeltaPressure = Pressure - AirPressure;
749 : : const double VolumeValved =
750 : : ((Pressure > AirPressure + MaxOverpressure) ? 1.0 : ValveOpen) *
751 [ # # ]: 0 : ValveCoefficient * DeltaPressure * dt;
752 : : // FIXME: Too small values of Contents sometimes leads to NaN.
753 : : // Currently the minimum is restricted to a safe value.
754 : : Contents =
755 : 0 : max(1.0, Contents - Pressure * VolumeValved / (R * Temperature));
756 : : }
757 : :
758 : : //-- Volume --
759 : 0 : Volume = Contents * R * Temperature / Pressure;
760 : : dVolumeIdeal =
761 : 0 : Contents * R * (Temperature / Pressure - OldTemperature / OldPressure);
762 : :
763 : : // Compute the inertia of the ballonet.
764 : : // Consider the ballonet as a shape of uniform density.
765 : : // FIXME: If the ballonet isn't ellipsoid or cylindrical the inertia will
766 : : // be wrong.
767 : 0 : ballonetJ = FGMatrix33();
768 : 0 : const double mass = Contents * M_air;
769 : : double Ixx, Iyy, Izz;
770 [ # # ][ # # ]: 0 : if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
[ # # ][ # # ]
[ # # ][ # # ]
771 : : (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
772 : : // Ellipsoid volume.
773 : 0 : Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius);
774 : 0 : Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius);
775 : 0 : Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius);
776 [ # # ][ # # ]: 0 : } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) &&
[ # # ][ # # ]
[ # # ][ # # ]
777 : : (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) {
778 : : // Cylindrical volume (might not be valid with an elliptical cross-section).
779 : 0 : Ixx = (1.0 / 2.0) * mass * Yradius * Zradius;
780 : : Iyy =
781 : : (1.0 / 4.0) * mass * Yradius * Zradius +
782 : 0 : (1.0 / 12.0) * mass * Xwidth * Xwidth;
783 : : Izz =
784 : : (1.0 / 4.0) * mass * Yradius * Zradius +
785 : 0 : (1.0 / 12.0) * mass * Xwidth * Xwidth;
786 : : } else {
787 : : // Not supported. Revert to pointmass model.
788 : 0 : Ixx = Iyy = Izz = 0.0;
789 : : }
790 : : // The volume is symmetric, so Ixy = Ixz = Iyz = 0.
791 : 0 : ballonetJ(1,1) = Ixx;
792 : 0 : ballonetJ(2,2) = Iyy;
793 : 0 : ballonetJ(3,3) = Izz;
794 : 0 : }
795 : :
796 : : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
797 : : // The bitmasked value choices are as follows:
798 : : // unset: In this case (the default) JSBSim would only print
799 : : // out the normally expected messages, essentially echoing
800 : : // the config files as they are read. If the environment
801 : : // variable is not set, debug_lvl is set to 1 internally
802 : : // 0: This requests JSBSim not to output any messages
803 : : // whatsoever.
804 : : // 1: This value explicity requests the normal JSBSim
805 : : // startup messages
806 : : // 2: This value asks for a message to be printed out when
807 : : // a class is instantiated
808 : : // 4: When this value is set, a message is displayed when a
809 : : // FGModel object executes its Run() method
810 : : // 8: When this value is set, various runtime state variables
811 : : // are printed out periodically
812 : : // 16: When set various parameters are sanity checked and
813 : : // a message is printed out when they go out of bounds
814 : :
815 : 0 : void FGBallonet::Debug(int from)
816 : : {
817 [ # # ]: 0 : if (debug_lvl <= 0) return;
818 : :
819 [ # # ]: 0 : if (debug_lvl & 1) { // Standard console startup message output
820 [ # # ]: 0 : if (from == 0) { // Constructor
821 : 0 : cout << " Ballonet holds " << Contents << " mol air" << endl;
822 : : cout << " Location (X, Y, Z) (in.): " << vXYZ(eX) << ", " <<
823 : 0 : vXYZ(eY) << ", " << vXYZ(eZ) << endl;
824 : 0 : cout << " Maximum volume: " << MaxVolume << " ft3" << endl;
825 : : cout << " Relief valve release pressure: " << MaxOverpressure <<
826 : 0 : " lbs/ft2" << endl;
827 : : cout << " Relief valve coefficient: " << ValveCoefficient <<
828 : 0 : " ft4*sec/slug" << endl;
829 : : cout << " Initial temperature: " << Temperature << " Rankine" <<
830 : 0 : endl;
831 : 0 : cout << " Initial pressure: " << Pressure << " lbs/ft2" << endl;
832 : 0 : cout << " Initial volume: " << Volume << " ft3" << endl;
833 : 0 : cout << " Initial mass: " << GetMass() << " slug mass" << endl;
834 : : cout << " Initial weight: " << GetMass()*lbtoslug <<
835 : 0 : " lbs force" << endl;
836 : 0 : cout << " Heat transfer: " << endl;
837 : : }
838 : : }
839 [ # # ]: 0 : if (debug_lvl & 2 ) { // Instantiation/Destruction notification
840 [ # # ]: 0 : if (from == 0) cout << "Instantiated: FGBallonet" << endl;
841 [ # # ]: 0 : if (from == 1) cout << "Destroyed: FGBallonet" << endl;
842 : : }
843 : 0 : if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
844 : : }
845 [ # # ]: 0 : if (debug_lvl & 8 ) { // Runtime state variables
846 : : cout << " Ballonet holds " << Contents <<
847 : 0 : " mol air" << endl;
848 : 0 : cout << " Temperature: " << Temperature << " Rankine" << endl;
849 : 0 : cout << " Pressure: " << Pressure << " lbs/ft2" << endl;
850 : 0 : cout << " Volume: " << Volume << " ft3" << endl;
851 : 0 : cout << " Mass: " << GetMass() << " slug mass" << endl;
852 : 0 : cout << " Weight: " << GetMass()*lbtoslug << " lbs force" << endl;
853 : : }
854 : 0 : if (debug_lvl & 16) { // Sanity checking
855 : : }
856 [ # # ]: 0 : if (debug_lvl & 64) {
857 [ # # ]: 0 : if (from == 0) { // Constructor
858 : 0 : cout << IdSrc << endl;
859 : 0 : cout << IdHdr << endl;
860 : : }
861 : : }
862 : : }
863 [ + + ][ + - ]: 12 : }
|