const FGMatrix33& FGBuoyantForces::GetGasMassInertia(void) { const unsigned int size = Cells.size(); if (size == 0) return gasCellJ; gasCellJ = FGMatrix33(); for (unsigned int i=0; i < size; i++) { gasCellJ += Cells[i]->GetInertia(); } return gasCellJ; }
const FGMatrix33& FGBuoyantForces::GetGasMassInertia(void) { const unsigned int size = Cells.size(); if (size == 0) return gasCellJ; gasCellJ = FGMatrix33(); for (unsigned int i=0; i < size; i++) { FGColumnVector3 v = MassBalance->StructuralToBody( Cells[i]->GetXYZ() ); // Body basis is in FT. const double mass = Cells[i]->GetMass(); // FIXME: Verify that this is the correct way to change between the // coordinate frames. gasCellJ += Cells[i]->GetInertia() + FGMatrix33( 0, - mass*v(1)*v(2), - mass*v(1)*v(3), - mass*v(2)*v(1), 0, - mass*v(2)*v(3), - mass*v(3)*v(1), - mass*v(3)*v(2), 0 ); } return gasCellJ; }
const FGMatrix33& FGMassBalance::CalculatePMInertias(void) { size_t size = PointMasses.size(); if (size == 0) return pmJ; pmJ = FGMatrix33(); for (unsigned int i=0; i<size; i++) { pmJ += GetPointmassInertia( lbtoslug * PointMasses[i]->Weight, PointMasses[i]->Location ); pmJ += PointMasses[i]->GetPointMassInertia(); } return pmJ; }
const FGMatrix33& FGPropulsion::CalculateTankInertias(void) { size_t size = Tanks.size(); if (size == 0) return tankJ; tankJ = FGMatrix33(); for (unsigned int i=0; i<size; i++) { tankJ += FDMExec->GetMassBalance()->GetPointmassInertia( lbtoslug * Tanks[i]->GetContents(), Tanks[i]->GetXYZ()); tankJ(1,1) += Tanks[i]->GetIxx(); tankJ(2,2) += Tanks[i]->GetIyy(); tankJ(3,3) += Tanks[i]->GetIzz(); } return tankJ; }
static FGMatrix33 ReadInertiaMatrix(Element* document) { double bixx, biyy, bizz, bixy, bixz, biyz; bixx = biyy = bizz = bixy = bixz = biyz = 0.0; if (document->FindElement("ixx")) bixx = document->FindElementValueAsNumberConvertTo("ixx", "SLUG*FT2"); if (document->FindElement("iyy")) biyy = document->FindElementValueAsNumberConvertTo("iyy", "SLUG*FT2"); if (document->FindElement("izz")) bizz = document->FindElementValueAsNumberConvertTo("izz", "SLUG*FT2"); if (document->FindElement("ixy")) bixy = document->FindElementValueAsNumberConvertTo("ixy", "SLUG*FT2"); if (document->FindElement("ixz")) bixz = document->FindElementValueAsNumberConvertTo("ixz", "SLUG*FT2"); if (document->FindElement("iyz")) biyz = document->FindElementValueAsNumberConvertTo("iyz", "SLUG*FT2"); return FGMatrix33( bixx, -bixy, bixz, -bixy, biyy, -biyz, bixz, -biyz, bizz ); }
void FGLocation::ComputeDerivedUnconditional(void) const { // The radius is just the Euclidean norm of the vector. mRadius = mECLoc.Magnitude(); // The distance of the location to the Z-axis, which is the axis // through the poles. double r02 = mECLoc(eX)*mECLoc(eX) + mECLoc(eY)*mECLoc(eY); double rxy = sqrt(r02); // Compute the sin/cos values of the longitude double sinLon, cosLon; if (rxy == 0.0) { sinLon = 0.0; cosLon = 1.0; } else { sinLon = mECLoc(eY)/rxy; cosLon = mECLoc(eX)/rxy; } // Compute the sin/cos values of the latitude double sinLat, cosLat; if (mRadius == 0.0) { sinLat = 0.0; cosLat = 1.0; } else { sinLat = mECLoc(eZ)/mRadius; cosLat = rxy/mRadius; } // Compute the longitude and latitude itself if ( mECLoc( eX ) == 0.0 && mECLoc( eY ) == 0.0 ) mLon = 0.0; else mLon = atan2( mECLoc( eY ), mECLoc( eX ) ); if ( rxy == 0.0 && mECLoc( eZ ) == 0.0 ) mLat = 0.0; else mLat = atan2( mECLoc(eZ), rxy ); // Compute the transform matrices from and to the earth centered frame. // See Stevens and Lewis, "Aircraft Control and Simulation", Second Edition, // Eqn. 1.4-13, page 40. In Stevens and Lewis notation, this is C_n/e - the // orientation of the navigation (local) frame relative to the ECEF frame, // and a transformation from ECEF to nav (local) frame. mTec2l = FGMatrix33( -cosLon*sinLat, -sinLon*sinLat, cosLat, -sinLon , cosLon , 0.0 , -cosLon*cosLat, -sinLon*cosLat, -sinLat ); // In Stevens and Lewis notation, this is C_e/n - the // orientation of the ECEF frame relative to the nav (local) frame, // and a transformation from nav (local) to ECEF frame. mTl2ec = mTec2l.Transposed(); // Calculate the inertial to ECEF and transpose matrices double cos_epa = cos(epa); double sin_epa = sin(epa); mTi2ec = FGMatrix33( cos_epa, sin_epa, 0.0, -sin_epa, cos_epa, 0.0, 0.0, 0.0, 1.0 ); mTec2i = mTi2ec.Transposed(); // Now calculate the local (or nav, or ned) frame to inertial transform matrix, // and the inverse. mTl2i = mTec2i * mTl2ec; mTi2l = mTl2i.Transposed(); // Calculate the geodetic latitude based on "Transformation from Cartesian // to geodetic coordinates accelerated by Halley's method", Fukushima T. (2006) // Journal of Geodesy, Vol. 79, pp. 689-693 // Unlike I. Sofair's method which uses a closed form solution, Fukushima's // method is an iterative method whose convergence is so fast that only one // iteration suffices. In addition, Fukushima's method has a much better // numerical stability over Sofair's method at the North and South poles and // it also gives the correct result for a spherical Earth. double s0 = fabs(mECLoc(eZ)); double zc = ec * s0; double c0 = ec * rxy; double c02 = c0 * c0; double s02 = s0 * s0; double a02 = c02 + s02; double a0 = sqrt(a02); double a03 = a02 * a0; double s1 = zc*a03 + c*s02*s0; double c1 = rxy*a03 - c*c02*c0; double cs0c0 = c*c0*s0; double b0 = 1.5*cs0c0*((rxy*s0-zc*c0)*a0-cs0c0); s1 = s1*a03-b0*s0; double cc = ec*(c1*a03-b0*c0); mGeodLat = sign(mECLoc(eZ))*atan(s1 / cc); double s12 = s1 * s1; double cc2 = cc * cc; GeodeticAltitude = (rxy*cc + s0*s1 - a*sqrt(ec2*s12 + cc2)) / sqrt(s12 + cc2); // Mark the cached values as valid mCacheValid = true; }
bool FGMassBalance::Load(Element* elem) { string element_name = ""; double bixx, biyy, bizz, bixy, bixz, biyz; string fname="", file=""; string separator = "/"; fname = elem->GetAttributeValue("file"); if (!fname.empty()) { file = FDMExec->GetFullAircraftPath() + separator + fname; document = LoadXMLDocument(file); if (document == 0L) return false; } else { document = elem; } FGModel::Load(document); // Perform base class Load. bixx = biyy = bizz = bixy = bixz = biyz = 0.0; if (document->FindElement("ixx")) bixx = document->FindElementValueAsNumberConvertTo("ixx", "SLUG*FT2"); if (document->FindElement("iyy")) biyy = document->FindElementValueAsNumberConvertTo("iyy", "SLUG*FT2"); if (document->FindElement("izz")) bizz = document->FindElementValueAsNumberConvertTo("izz", "SLUG*FT2"); if (document->FindElement("ixy")) bixy = document->FindElementValueAsNumberConvertTo("ixy", "SLUG*FT2"); if (document->FindElement("ixz")) bixz = document->FindElementValueAsNumberConvertTo("ixz", "SLUG*FT2"); if (document->FindElement("iyz")) biyz = document->FindElementValueAsNumberConvertTo("iyz", "SLUG*FT2"); SetAircraftBaseInertias(FGMatrix33( bixx, -bixy, bixz, -bixy, biyy, -biyz, bixz, -biyz, bizz )); if (document->FindElement("emptywt")) { EmptyWeight = document->FindElementValueAsNumberConvertTo("emptywt", "LBS"); } Element *element = document->FindElement("location"); while (element) { element_name = element->GetAttributeValue("name"); if (element_name == "CG") vbaseXYZcg = element->FindElementTripletConvertTo("IN"); element = document->FindNextElement("location"); } // Find all POINTMASS elements that descend from this METRICS branch of the // config file. element = document->FindElement("pointmass"); while (element) { AddPointMass(element); element = document->FindNextElement("pointmass"); } double ChildFDMWeight = 0.0; for (int fdm=0; fdm<FDMExec->GetFDMCount(); fdm++) { if (FDMExec->GetChildFDM(fdm)->mated) ChildFDMWeight += FDMExec->GetChildFDM(fdm)->exec->GetMassBalance()->GetWeight(); } Weight = EmptyWeight + in.TanksWeight + GetTotalPointMassWeight() + in.GasMass*slugtolb + ChildFDMWeight; Mass = lbtoslug*Weight; PostLoad(document, PropertyManager); Debug(2); return true; }
void FGBallonet::Calculate(double dt) { const double ParentPressure = Parent->GetPressure(); // [lbs/ft^2] const double AirPressure = in.Pressure; // [lbs/ft^2] const double OldTemperature = Temperature; const double OldPressure = Pressure; unsigned int i; //-- Gas temperature -- // The model is based on the ideal gas law. // However, it does look a bit fishy. Please verify. // dT/dt = dU / (Cv n R) dU = 0.0; for (i = 0; i < HeatTransferCoeff.size(); i++) { dU += HeatTransferCoeff[i]->GetValue(); } // dt is already accounted for in dVolumeIdeal. if (Contents > 0) { Temperature += (dU * dt - Pressure * dVolumeIdeal) / (Cv_air * Contents * R); } else { Temperature = Parent->GetTemperature(); } //-- Pressure -- const double IdealPressure = Contents * R * Temperature / MaxVolume; // The pressure is at least that of the parent gas cell. Pressure = max(IdealPressure, ParentPressure); //-- Blower input -- if (BlowerInput) { const double AddedVolume = BlowerInput->GetValue() * dt; if (AddedVolume > 0.0) { Contents += Pressure * AddedVolume / (R * Temperature); } } //-- Pressure relief and manual valving -- // FIXME: Presently the effect of valving is computed using // an ad hoc formula which might not be a good representation // of reality. if ((ValveCoefficient > 0.0) && ((ValveOpen > 0.0) || (Pressure > AirPressure + MaxOverpressure))) { const double DeltaPressure = Pressure - AirPressure; const double VolumeValved = ((Pressure > AirPressure + MaxOverpressure) ? 1.0 : ValveOpen) * ValveCoefficient * DeltaPressure * dt; // FIXME: Too small values of Contents sometimes leads to NaN. // Currently the minimum is restricted to a safe value. Contents = max(1.0, Contents - Pressure * VolumeValved / (R * Temperature)); } //-- Volume -- Volume = Contents * R * Temperature / Pressure; dVolumeIdeal = Contents * R * (Temperature / Pressure - OldTemperature / OldPressure); // Compute the inertia of the ballonet. // Consider the ballonet as a shape of uniform density. // FIXME: If the ballonet isn't ellipsoid or cylindrical the inertia will // be wrong. ballonetJ = FGMatrix33(); const double mass = Contents * M_air; double Ixx, Iyy, Izz; if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Ellipsoid volume. Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius); Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius); Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius); } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Cylindrical volume (might not be valid with an elliptical cross-section). Ixx = (1.0 / 2.0) * mass * Yradius * Zradius; Iyy = (1.0 / 4.0) * mass * Yradius * Zradius + (1.0 / 12.0) * mass * Xwidth * Xwidth; Izz = (1.0 / 4.0) * mass * Yradius * Zradius + (1.0 / 12.0) * mass * Xwidth * Xwidth; } else { // Not supported. Revert to pointmass model. Ixx = Iyy = Izz = 0.0; } // The volume is symmetric, so Ixy = Ixz = Iyz = 0. ballonetJ(1,1) = Ixx; ballonetJ(2,2) = Iyy; ballonetJ(3,3) = Izz; // Transform the moments of inertia to the body frame. ballonetJ += MassBalance->GetPointmassInertia(GetMass(), GetXYZ()); }
FGGasCell::FGGasCell(FGFDMExec* exec, Element* el, unsigned int num, const struct Inputs& input) : FGForce(exec), in(input) { string token; Element* element; FGPropertyManager* PropertyManager = exec->GetPropertyManager(); MassBalance = exec->GetMassBalance(); gasCellJ = FGMatrix33(); gasCellM = FGColumnVector3(); Buoyancy = MaxVolume = MaxOverpressure = Temperature = Pressure = Contents = Volume = dVolumeIdeal = 0.0; Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0; ValveCoefficient = ValveOpen = 0.0; CellNum = num; // NOTE: In the local system X points north, Y points east and Z points down. SetTransformType(FGForce::tLocalBody); type = el->GetAttributeValue("type"); if (type == "HYDROGEN") Type = ttHYDROGEN; else if (type == "HELIUM") Type = ttHELIUM; else if (type == "AIR") Type = ttAIR; else Type = ttUNKNOWN; element = el->FindElement("location"); if (element) { vXYZ = element->FindElementTripletConvertTo("IN"); } else { std::stringstream error; error << "Fatal Error: No location found for this gas cell." << endl; throw std::runtime_error(error.str()); } if ((el->FindElement("x_radius") || el->FindElement("x_width")) && (el->FindElement("y_radius") || el->FindElement("y_width")) && (el->FindElement("z_radius") || el->FindElement("z_width"))) { if (el->FindElement("x_radius")) { Xradius = el->FindElementValueAsNumberConvertTo("x_radius", "FT"); } if (el->FindElement("y_radius")) { Yradius = el->FindElementValueAsNumberConvertTo("y_radius", "FT"); } if (el->FindElement("z_radius")) { Zradius = el->FindElementValueAsNumberConvertTo("z_radius", "FT"); } if (el->FindElement("x_width")) { Xwidth = el->FindElementValueAsNumberConvertTo("x_width", "FT"); } if (el->FindElement("y_width")) { Ywidth = el->FindElementValueAsNumberConvertTo("y_width", "FT"); } if (el->FindElement("z_width")) { Zwidth = el->FindElementValueAsNumberConvertTo("z_width", "FT"); } // The volume is a (potentially) extruded ellipsoid. // However, currently only a few combinations of radius and width are // fully supported. if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Ellipsoid volume. MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0; } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Cylindrical volume. MaxVolume = M_PI * Yradius * Zradius * Xwidth; } else { cerr << "Warning: Unsupported gas cell shape." << endl; MaxVolume = (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 + M_PI * Yradius * Zradius * Xwidth + M_PI * Xradius * Zradius * Ywidth + M_PI * Xradius * Yradius * Zwidth + 2.0 * Xradius * Ywidth * Zwidth + 2.0 * Yradius * Xwidth * Zwidth + 2.0 * Zradius * Xwidth * Ywidth + Xwidth * Ywidth * Zwidth); } } else { std::stringstream error; error << "Fatal Error: Gas cell shape must be given." << endl; throw std::runtime_error(error.str()); } if (el->FindElement("max_overpressure")) { MaxOverpressure = el->FindElementValueAsNumberConvertTo("max_overpressure", "LBS/FT2"); } if (el->FindElement("fullness")) { const double Fullness = el->FindElementValueAsNumber("fullness"); if (0 <= Fullness) { Volume = Fullness * MaxVolume; } else { cerr << "Warning: Invalid initial gas cell fullness value." << endl; } } if (el->FindElement("valve_coefficient")) { ValveCoefficient = el->FindElementValueAsNumberConvertTo("valve_coefficient", "FT4*SEC/SLUG"); ValveCoefficient = max(ValveCoefficient, 0.0); } // Initialize state SetLocation(vXYZ); if (Temperature == 0.0) { Temperature = in.Temperature; } if (Pressure == 0.0) { Pressure = in.Pressure; } if (Volume != 0.0) { // Calculate initial gas content. Contents = Pressure * Volume / (R * Temperature); // Clip to max allowed value. const double IdealPressure = Contents * R * Temperature / MaxVolume; if (IdealPressure > Pressure + MaxOverpressure) { Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature); Pressure = Pressure + MaxOverpressure; } else { Pressure = max(IdealPressure, Pressure); } } else { // Calculate initial gas content. Contents = Pressure * MaxVolume / (R * Temperature); } Volume = Contents * R * Temperature / Pressure; Mass = Contents * M_gas(); // Bind relevant properties string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("buoyant_forces/gas-cell", CellNum); property_name = base_property_name + "/max_volume-ft3"; PropertyManager->Tie( property_name.c_str(), &MaxVolume, false ); PropertyManager->GetNode()->SetWritable( property_name, false ); property_name = base_property_name + "/temp-R"; PropertyManager->Tie( property_name.c_str(), &Temperature, false ); property_name = base_property_name + "/pressure-psf"; PropertyManager->Tie( property_name.c_str(), &Pressure, false ); property_name = base_property_name + "/volume-ft3"; PropertyManager->Tie( property_name.c_str(), &Volume, false ); property_name = base_property_name + "/buoyancy-lbs"; PropertyManager->Tie( property_name.c_str(), &Buoyancy, false ); property_name = base_property_name + "/contents-mol"; PropertyManager->Tie( property_name.c_str(), &Contents, false ); property_name = base_property_name + "/valve_open"; PropertyManager->Tie( property_name.c_str(), &ValveOpen, false ); Debug(0); // Read heat transfer coefficients if (Element* heat = el->FindElement("heat")) { Element* function_element = heat->FindElement("function"); while (function_element) { HeatTransferCoeff.push_back(new FGFunction(PropertyManager, function_element)); function_element = heat->FindNextElement("function"); } } // Load ballonets if there are any if (Element* ballonet_element = el->FindElement("ballonet")) { while (ballonet_element) { Ballonet.push_back(new FGBallonet(exec, ballonet_element, Ballonet.size(), this, in)); ballonet_element = el->FindNextElement("ballonet"); } } }
FGBallonet::FGBallonet(FGFDMExec* exec, Element* el, unsigned int num, FGGasCell* parent, const struct FGGasCell::Inputs& input) : in(input) { string token; Element* element; FGPropertyManager* PropertyManager = exec->GetPropertyManager(); MassBalance = exec->GetMassBalance(); ballonetJ = FGMatrix33(); MaxVolume = MaxOverpressure = Temperature = Pressure = Contents = Volume = dVolumeIdeal = dU = 0.0; Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0; ValveCoefficient = ValveOpen = 0.0; BlowerInput = NULL; CellNum = num; Parent = parent; // NOTE: In the local system X points north, Y points east and Z points down. element = el->FindElement("location"); if (element) { vXYZ = element->FindElementTripletConvertTo("IN"); } else { std::stringstream error; error << "Fatal Error: No location found for this ballonet." << endl; throw std::runtime_error(error.str()); } if ((el->FindElement("x_radius") || el->FindElement("x_width")) && (el->FindElement("y_radius") || el->FindElement("y_width")) && (el->FindElement("z_radius") || el->FindElement("z_width"))) { if (el->FindElement("x_radius")) { Xradius = el->FindElementValueAsNumberConvertTo("x_radius", "FT"); } if (el->FindElement("y_radius")) { Yradius = el->FindElementValueAsNumberConvertTo("y_radius", "FT"); } if (el->FindElement("z_radius")) { Zradius = el->FindElementValueAsNumberConvertTo("z_radius", "FT"); } if (el->FindElement("x_width")) { Xwidth = el->FindElementValueAsNumberConvertTo("x_width", "FT"); } if (el->FindElement("y_width")) { Ywidth = el->FindElementValueAsNumberConvertTo("y_width", "FT"); } if (el->FindElement("z_width")) { Zwidth = el->FindElementValueAsNumberConvertTo("z_width", "FT"); } // The volume is a (potentially) extruded ellipsoid. // FIXME: However, currently only a few combinations of radius and // width are fully supported. if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Ellipsoid volume. MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0; } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Cylindrical volume. MaxVolume = M_PI * Yradius * Zradius * Xwidth; } else { cerr << "Warning: Unsupported ballonet shape." << endl; MaxVolume = (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 + M_PI * Yradius * Zradius * Xwidth + M_PI * Xradius * Zradius * Ywidth + M_PI * Xradius * Yradius * Zwidth + 2.0 * Xradius * Ywidth * Zwidth + 2.0 * Yradius * Xwidth * Zwidth + 2.0 * Zradius * Xwidth * Ywidth + Xwidth * Ywidth * Zwidth); } } else { std::stringstream error; error << "Fatal Error: Ballonet shape must be given." << endl; throw std::runtime_error(error.str()); } if (el->FindElement("max_overpressure")) { MaxOverpressure = el->FindElementValueAsNumberConvertTo("max_overpressure", "LBS/FT2"); } if (el->FindElement("fullness")) { const double Fullness = el->FindElementValueAsNumber("fullness"); if (0 <= Fullness) { Volume = Fullness * MaxVolume; } else { cerr << "Warning: Invalid initial ballonet fullness value." << endl; } } if (el->FindElement("valve_coefficient")) { ValveCoefficient = el->FindElementValueAsNumberConvertTo("valve_coefficient", "FT4*SEC/SLUG"); ValveCoefficient = max(ValveCoefficient, 0.0); } // Initialize state if (Temperature == 0.0) { Temperature = Parent->GetTemperature(); } if (Pressure == 0.0) { Pressure = Parent->GetPressure(); } if (Volume != 0.0) { // Calculate initial air content. Contents = Pressure * Volume / (R * Temperature); // Clip to max allowed value. const double IdealPressure = Contents * R * Temperature / MaxVolume; if (IdealPressure > Pressure + MaxOverpressure) { Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature); Pressure = Pressure + MaxOverpressure; } else { Pressure = max(IdealPressure, Pressure); } } else { // Calculate initial air content. Contents = Pressure * MaxVolume / (R * Temperature); } Volume = Contents * R * Temperature / Pressure; // Bind relevant properties string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("buoyant_forces/gas-cell", Parent->GetIndex()); base_property_name = CreateIndexedPropertyName(base_property_name + "/ballonet", CellNum); property_name = base_property_name + "/max_volume-ft3"; PropertyManager->Tie( property_name, &MaxVolume, false ); PropertyManager->GetNode()->SetWritable( property_name, false ); property_name = base_property_name + "/temp-R"; PropertyManager->Tie( property_name, &Temperature, false ); property_name = base_property_name + "/pressure-psf"; PropertyManager->Tie( property_name, &Pressure, false ); property_name = base_property_name + "/volume-ft3"; PropertyManager->Tie( property_name, &Volume, false ); property_name = base_property_name + "/contents-mol"; PropertyManager->Tie( property_name, &Contents, false ); property_name = base_property_name + "/valve_open"; PropertyManager->Tie( property_name, &ValveOpen, false ); Debug(0); // Read heat transfer coefficients if (Element* heat = el->FindElement("heat")) { Element* function_element = heat->FindElement("function"); while (function_element) { HeatTransferCoeff.push_back(new FGFunction(PropertyManager, function_element)); function_element = heat->FindNextElement("function"); } } // Read blower input function if (Element* blower = el->FindElement("blower_input")) { Element* function_element = blower->FindElement("function"); BlowerInput = new FGFunction(PropertyManager, function_element); } }
void FGGasCell::Calculate(double dt) { const double AirTemperature = in.Temperature; // [Rankine] const double AirPressure = in.Pressure; // [lbs/ft^2] const double AirDensity = in.Density; // [slug/ft^3] const double g = in.gravity; // [lbs/slug] const double OldTemperature = Temperature; const double OldPressure = Pressure; unsigned int i; const size_t no_ballonets = Ballonet.size(); //-- Read ballonet state -- // NOTE: This model might need a more proper integration technique. double BallonetsVolume = 0.0; double BallonetsHeatFlow = 0.0; for (i = 0; i < no_ballonets; i++) { BallonetsVolume += Ballonet[i]->GetVolume(); BallonetsHeatFlow += Ballonet[i]->GetHeatFlow(); } //-- Gas temperature -- if (HeatTransferCoeff.size() > 0) { // The model is based on the ideal gas law. // However, it does look a bit fishy. Please verify. // dT/dt = dU / (Cv n R) double dU = 0.0; for (i = 0; i < HeatTransferCoeff.size(); i++) { dU += HeatTransferCoeff[i]->GetValue(); } // Don't include dt when accounting for adiabatic expansion/contraction. // The rate of adiabatic cooling looks about right: ~5.4 Rankine/1000ft. if (Contents > 0) { Temperature += (dU * dt - Pressure * dVolumeIdeal - BallonetsHeatFlow) / (Cv_gas() * Contents * R); } else { Temperature = AirTemperature; } } else { // No simulation of complex temperature changes. // Note: Making the gas cell behave adiabatically might be a better // option. Temperature = AirTemperature; } //-- Pressure -- const double IdealPressure = Contents * R * Temperature / (MaxVolume - BallonetsVolume); if (IdealPressure > AirPressure + MaxOverpressure) { Pressure = AirPressure + MaxOverpressure; } else { Pressure = max(IdealPressure, AirPressure); } //-- Manual valving -- // FIXME: Presently the effect of manual valving is computed using // an ad hoc formula which might not be a good representation // of reality. if ((ValveCoefficient > 0.0) && (ValveOpen > 0.0)) { // First compute the difference in pressure between the gas in the // cell and the air above it. // FixMe: CellHeight should depend on current volume. const double CellHeight = 2 * Zradius + Zwidth; // [ft] const double GasMass = Contents * M_gas(); // [slug] const double GasVolume = Contents * R * Temperature / Pressure; // [ft^3] const double GasDensity = GasMass / GasVolume; const double DeltaPressure = Pressure + CellHeight * g * (AirDensity - GasDensity) - AirPressure; const double VolumeValved = ValveOpen * ValveCoefficient * DeltaPressure * dt; Contents = max(0.0, Contents - Pressure * VolumeValved / (R * Temperature)); } //-- Update ballonets. -- // Doing that here should give them the opportunity to react to the // new pressure. BallonetsVolume = 0.0; for (i = 0; i < no_ballonets; i++) { Ballonet[i]->Calculate(dt); BallonetsVolume += Ballonet[i]->GetVolume(); } //-- Automatic safety valving. -- if (Contents * R * Temperature / (MaxVolume - BallonetsVolume) > AirPressure + MaxOverpressure) { // Gas is automatically valved. Valving capacity is assumed to be infinite. // FIXME: This could/should be replaced by damage to the gas cell envelope. Contents = (AirPressure + MaxOverpressure) * (MaxVolume - BallonetsVolume) / (R * Temperature); } //-- Volume -- Volume = Contents * R * Temperature / Pressure + BallonetsVolume; dVolumeIdeal = Contents * R * (Temperature / Pressure - OldTemperature / OldPressure); //-- Current buoyancy -- // The buoyancy is computed using the atmospheres local density. Buoyancy = Volume * AirDensity * g; // Note: This is gross buoyancy. The weight of the gas itself and // any ballonets is not deducted here as the effects of the gas mass // is handled by FGMassBalance. vFn.InitMatrix(0.0, 0.0, - Buoyancy); // Compute the inertia of the gas cell. // Consider the gas cell as a shape of uniform density. // FIXME: If the cell isn't ellipsoid or cylindrical the inertia will // be wrong. gasCellJ = FGMatrix33(); const double mass = Contents * M_gas(); double Ixx, Iyy, Izz; if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Ellipsoid volume. Ixx = (1.0 / 5.0) * mass * (Yradius*Yradius + Zradius*Zradius); Iyy = (1.0 / 5.0) * mass * (Xradius*Xradius + Zradius*Zradius); Izz = (1.0 / 5.0) * mass * (Xradius*Xradius + Yradius*Yradius); } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Cylindrical volume (might not be valid with an elliptical cross-section). Ixx = (1.0 / 2.0) * mass * Yradius * Zradius; Iyy = (1.0 / 4.0) * mass * Yradius * Zradius + (1.0 / 12.0) * mass * Xwidth * Xwidth; Izz = (1.0 / 4.0) * mass * Yradius * Zradius + (1.0 / 12.0) * mass * Xwidth * Xwidth; } else { // Not supported. Revert to pointmass model. Ixx = Iyy = Izz = 0.0; } // The volume is symmetric, so Ixy = Ixz = Iyz = 0. gasCellJ(1,1) = Ixx; gasCellJ(2,2) = Iyy; gasCellJ(3,3) = Izz; Mass = mass; // Transform the moments of inertia to the body frame. gasCellJ += MassBalance->GetPointmassInertia(Mass, GetXYZ()); gasCellM.InitMatrix(); gasCellM(eX) += GetXYZ(eX) * Mass*slugtolb; gasCellM(eY) += GetXYZ(eY) * Mass*slugtolb; gasCellM(eZ) += GetXYZ(eZ) * Mass*slugtolb; if (no_ballonets > 0) { // Add the mass, moment and inertia of any ballonets. for (i = 0; i < no_ballonets; i++) { Mass += Ballonet[i]->GetMass(); // Add ballonet moments due to mass (in the structural frame). gasCellM(eX) += Ballonet[i]->GetXYZ(eX) * Ballonet[i]->GetMass()*slugtolb; gasCellM(eY) += Ballonet[i]->GetXYZ(eY) * Ballonet[i]->GetMass()*slugtolb; gasCellM(eZ) += Ballonet[i]->GetXYZ(eZ) * Ballonet[i]->GetMass()*slugtolb; gasCellJ += Ballonet[i]->GetInertia(); } } }