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;
}
Beispiel #3
0
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;
}
Beispiel #4
0
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;
}
Beispiel #5
0
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 );
}
Beispiel #6
0
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;
}
Beispiel #7
0
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;
}
Beispiel #8
0
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());
}
Beispiel #9
0
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");
    }
  }

}
Beispiel #10
0
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);
  }
}
Beispiel #11
0
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();
    }
  }
}