1
0
Fork 0

Sync'ed with JSBSim v1.1.10

- Fixed an error which prevented the equatorial and polar radii from <planet> to be propagated to the initial conditions.
- The planet radii in <planet> can now be specified by the tags <equatorial_radius> and <polar_radius> which are more self explanatory than <semimajor_axis> and <semiminor_axis> (which are still valid).
- Improved the error messages returned by FGTable: the file name and line number where the error occurred are now printed.
- Check the number of <input> tags for flight controls such as <pure_gain> (GitHub issue ). This avoids a crash when some or all <input> elements are missing.
- JSBSim now accepts 2 sign conventions for the cross product inertia (xy, xz, yz) in <mass_balance> (GitHub Pull Request ). The sign convention is specified by the parameter negated_crossproduct_inertia which defaults to true for backward compatibility.
- Turbine engines can now windmill even before they start (GitHub issue  and Pull Request ).
- Fixed a sign error in the computation of aero/h_b-mac-ft (GitHub Pull Request  )
- Fixed a bug where FGTable instances were not untied from the property manager during their destruction. This could lead to segmentation faults when the property manager was later destroyed.
- Exceptions raised by FGTable are now instances of the TableException class.
This commit is contained in:
Bertrand Coconnier 2021-11-20 18:23:34 +01:00
parent 5023e9786a
commit e5fe747662
18 changed files with 209 additions and 98 deletions

View file

@ -731,6 +731,7 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath)
} }
// Reload the planet constants and re-initialize the models. // Reload the planet constants and re-initialize the models.
LoadPlanetConstants(); LoadPlanetConstants();
IC->InitializeIC();
InitializeModels(); InitializeModels();
} }

View file

@ -682,6 +682,9 @@ public:
@return Trim type, if any requested (version 1). */ @return Trim type, if any requested (version 1). */
int TrimRequested(void) const { return trimRequested; } int TrimRequested(void) const { return trimRequested; }
/** Initialize the initial conditions to default values */
void InitializeIC(void);
void bind(FGPropertyManager* pm); void bind(FGPropertyManager* pm);
private: private:
@ -710,7 +713,6 @@ private:
bool Load_v1(Element* document); bool Load_v1(Element* document);
bool Load_v2(Element* document); bool Load_v2(Element* document);
void InitializeIC(void);
void SetEulerAngleRadIC(int idx, double angle); void SetEulerAngleRadIC(int idx, double angle);
void SetBodyVelFpsIC(int idx, double vel); void SetBodyVelFpsIC(int idx, double vel);
void SetNEDVelFpsIC(int idx, double vel); void SetNEDVelFpsIC(int idx, double vel);

View file

@ -36,6 +36,8 @@ JSB 1/9/00 Created
INCLUDES INCLUDES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
#include <assert.h>
#include "FGTable.h" #include "FGTable.h"
#include "input_output/FGXMLElement.h" #include "input_output/FGXMLElement.h"
@ -107,9 +109,23 @@ FGTable::FGTable(const FGTable& t) : PropertyManager(t.PropertyManager)
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
unsigned int FindNumColumns(const string& test_line)
{
// determine number of data columns in table (first column is row lookup - don't count)
size_t position=0;
unsigned int nCols=0;
while ((position = test_line.find_first_not_of(" \t", position)) != string::npos) {
nCols++;
position = test_line.find_first_of(" \t", position);
}
return nCols;
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
FGTable::FGTable(FGPropertyManager* propMan, Element* el, FGTable::FGTable(FGPropertyManager* propMan, Element* el,
const std::string& prefix) const std::string& Prefix)
: PropertyManager(propMan), Prefix(prefix) : PropertyManager(propMan)
{ {
unsigned int i; unsigned int i;
@ -142,7 +158,7 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
std::cerr << el->ReadFrom() std::cerr << el->ReadFrom()
<<" An unknown table type attribute is listed: " << call_type <<" An unknown table type attribute is listed: " << call_type
<< endl; << endl;
throw("Execution cannot continue."); throw TableException("Unknown table type.");
} }
// Determine and store the lookup properties for this table unless this table // Determine and store the lookup properties for this table unless this table
@ -157,10 +173,11 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
// The 'internal' attribute of the table element cannot be specified // The 'internal' attribute of the table element cannot be specified
// at the same time that independentVars are specified. // at the same time that independentVars are specified.
if (internal) { if (internal) {
cerr << endl << fgred << " This table specifies both 'internal' call type" << endl; cerr << el->ReadFrom()
cerr << " and specific lookup properties via the 'independentVar' element." << endl; << fgred << " This table specifies both 'internal' call type" << endl
cerr << " These are mutually exclusive specifications. The 'internal'" << endl; << " and specific lookup properties via the 'independentVar' element." << endl
cerr << " attribute will be ignored." << fgdef << endl << endl; << " These are mutually exclusive specifications. The 'internal'" << endl
<< " attribute will be ignored." << fgdef << endl << endl;
internal = false; internal = false;
} }
@ -182,7 +199,7 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
} else if (lookup_axis == string("table")) { } else if (lookup_axis == string("table")) {
lookupProperty[eTable] = node; lookupProperty[eTable] = node;
} else if (!lookup_axis.empty()) { } else if (!lookup_axis.empty()) {
throw("Lookup table axis specification not understood: " + lookup_axis); throw TableException("Lookup table axis specification not understood: " + lookup_axis);
} else { // assumed single dimension table; row lookup } else { // assumed single dimension table; row lookup
lookupProperty[eRow] = node; lookupProperty[eRow] = node;
} }
@ -202,7 +219,8 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
if (FindNumColumns(test_line) == 2) dimension = 1; // 1D table if (FindNumColumns(test_line) == 2) dimension = 1; // 1D table
else if (FindNumColumns(test_line) > 2) dimension = 2; // 2D table else if (FindNumColumns(test_line) > 2) dimension = 2; // 2D table
else { else {
cerr << "Invalid number of columns in table" << endl; std::cerr << tableData->ReadFrom()
<< "Invalid number of columns in table" << endl;
} }
} }
@ -211,7 +229,10 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
if (brkpt_string.empty()) { if (brkpt_string.empty()) {
// no independentVars found, and table is not marked as internal, nor is it // no independentVars found, and table is not marked as internal, nor is it
// a 3D table // a 3D table
throw("No independent variable found for table."); std::cerr << el->ReadFrom()
<< "No independentVars found, and table is not marked as internal,"
<< " nor is it a 3D table." << endl;
throw TableException("No independent variable found for table.");
} }
} }
// end lookup property code // end lookup property code
@ -243,9 +264,15 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
if (nRows >= 2) { if (nRows >= 2) {
nCols = FindNumColumns(tableData->GetDataLine(0)); nCols = FindNumColumns(tableData->GetDataLine(0));
if (nCols < 2) throw(string("Not enough columns in table data.")); if (nCols < 2) {
std::cerr << tableData->ReadFrom()
<< "Not enough columns in table data" << endl;
throw TableException("Not enough columns in table data.");
}
} else { } else {
throw(string("Not enough rows in the table data.")); std::cerr << tableData->ReadFrom()
<< "Not enough rows in table data" << endl;
throw TableException("Not enough rows in the table data.");
} }
Type = tt2D; Type = tt2D;
@ -296,14 +323,14 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
if (dimension > 2) { if (dimension > 2) {
for (b=2; b<=nTables; ++b) { for (b=2; b<=nTables; ++b) {
if (Data[b][1] <= Data[b-1][1]) { if (Data[b][1] <= Data[b-1][1]) {
stringstream errormsg; std::cerr << el->ReadFrom()
errormsg << fgred << highint << endl << fgred << highint
<< " FGTable: breakpoint lookup is not monotonically increasing" << endl << " FGTable: breakpoint lookup is not monotonically increasing" << endl
<< " in breakpoint " << b; << " in breakpoint " << b;
if (nameel != 0) errormsg << " of table in " << nameel->GetAttributeValue("name"); if (nameel != 0) std::cerr << " of table in " << nameel->GetAttributeValue("name");
errormsg << ":" << reset << endl std::cerr << ":" << reset << endl
<< " " << Data[b][1] << "<=" << Data[b-1][1] << endl; << " " << Data[b][1] << "<=" << Data[b-1][1] << endl;
throw(errormsg.str()); throw TableException("Breakpoint lookup is not monotonically increasing");
} }
} }
} }
@ -312,14 +339,14 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
if (dimension > 1) { if (dimension > 1) {
for (c=2; c<=nCols; ++c) { for (c=2; c<=nCols; ++c) {
if (Data[0][c] <= Data[0][c-1]) { if (Data[0][c] <= Data[0][c-1]) {
stringstream errormsg; std::cerr << el->ReadFrom()
errormsg << fgred << highint << endl << fgred << highint
<< " FGTable: column lookup is not monotonically increasing" << endl << " FGTable: column lookup is not monotonically increasing" << endl
<< " in column " << c; << " in column " << c;
if (nameel != 0) errormsg << " of table in " << nameel->GetAttributeValue("name"); if (nameel != 0) std::cerr << " of table in " << nameel->GetAttributeValue("name");
errormsg << ":" << reset << endl std::cerr << ":" << reset << endl
<< " " << Data[0][c] << "<=" << Data[0][c-1] << endl; << " " << Data[0][c] << "<=" << Data[0][c-1] << endl;
throw(errormsg.str()); throw TableException("FGTable: column lookup is not monotonically increasing");
} }
} }
} }
@ -328,19 +355,19 @@ FGTable::FGTable(FGPropertyManager* propMan, Element* el,
if (dimension < 3) { // in 3D tables, check only rows of subtables if (dimension < 3) { // in 3D tables, check only rows of subtables
for (r=2; r<=nRows; ++r) { for (r=2; r<=nRows; ++r) {
if (Data[r][0]<=Data[r-1][0]) { if (Data[r][0]<=Data[r-1][0]) {
stringstream errormsg; std::cerr << el->ReadFrom()
errormsg << fgred << highint << endl << fgred << highint
<< " FGTable: row lookup is not monotonically increasing" << endl << " FGTable: row lookup is not monotonically increasing" << endl
<< " in row " << r; << " in row " << r;
if (nameel != 0) errormsg << " of table in " << nameel->GetAttributeValue("name"); if (nameel != 0) std::cerr << " of table in " << nameel->GetAttributeValue("name");
errormsg << ":" << reset << endl std::cerr << ":" << reset << endl
<< " " << Data[r][0] << "<=" << Data[r-1][0] << endl; << " " << Data[r][0] << "<=" << Data[r-1][0] << endl;
throw(errormsg.str()); throw TableException("FGTable: row lookup is not monotonically increasing");
} }
} }
} }
bind(el); bind(el, Prefix);
if (debug_lvl & 1) Print(); if (debug_lvl & 1) Print();
} }
@ -363,6 +390,13 @@ double** FGTable::Allocate(void)
FGTable::~FGTable() FGTable::~FGTable()
{ {
// Untie the bound property so that it makes no further reference to this
// instance of FGTable after the destruction is completed.
if (!Name.empty() && !internal) {
string tmp = mkPropertyName(nullptr, "");
PropertyManager->Untie(tmp);
}
if (nTables > 0) { if (nTables > 0) {
for (unsigned int i=0; i<nTables; i++) delete Tables[i]; for (unsigned int i=0; i<nTables; i++) delete Tables[i];
Tables.clear(); Tables.clear();
@ -375,20 +409,6 @@ FGTable::~FGTable()
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
unsigned int FGTable::FindNumColumns(const string& test_line)
{
// determine number of data columns in table (first column is row lookup - don't count)
size_t position=0;
unsigned int nCols=0;
while ((position = test_line.find_first_not_of(" \t", position)) != string::npos) {
nCols++;
position = test_line.find_first_of(" \t", position);
}
return nCols;
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
double FGTable::GetValue(void) const double FGTable::GetValue(void) const
{ {
double temp = 0; double temp = 0;
@ -396,13 +416,19 @@ double FGTable::GetValue(void) const
switch (Type) { switch (Type) {
case tt1D: case tt1D:
assert(lookupProperty[eRow]);
temp = lookupProperty[eRow]->getDoubleValue(); temp = lookupProperty[eRow]->getDoubleValue();
temp2 = GetValue(temp); temp2 = GetValue(temp);
return temp2; return temp2;
case tt2D: case tt2D:
assert(lookupProperty[eRow]);
assert(lookupProperty[eColumn]);
return GetValue(lookupProperty[eRow]->getDoubleValue(), return GetValue(lookupProperty[eRow]->getDoubleValue(),
lookupProperty[eColumn]->getDoubleValue()); lookupProperty[eColumn]->getDoubleValue());
case tt3D: case tt3D:
assert(lookupProperty[eRow]);
assert(lookupProperty[eColumn]);
assert(lookupProperty[eTable]);
return GetValue(lookupProperty[eRow]->getDoubleValue(), return GetValue(lookupProperty[eRow]->getDoubleValue(),
lookupProperty[eColumn]->getDoubleValue(), lookupProperty[eColumn]->getDoubleValue(),
lookupProperty[eTable]->getDoubleValue()); lookupProperty[eTable]->getDoubleValue());
@ -432,7 +458,7 @@ double FGTable::GetValue(double key) const
} }
// the key is somewhere in the middle, search for the right breakpoint // the key is somewhere in the middle, search for the right breakpoint
// The search is particularly efficient if // The search is particularly efficient if
// the correct breakpoint has not changed since last frame or // the correct breakpoint has not changed since last frame or
// has only changed very little // has only changed very little
@ -508,7 +534,7 @@ double FGTable::GetValue(double rowKey, double colKey, double tableKey) const
} }
// the key is somewhere in the middle, search for the right breakpoint // the key is somewhere in the middle, search for the right breakpoint
// The search is particularly efficient if // The search is particularly efficient if
// the correct breakpoint has not changed since last frame or // the correct breakpoint has not changed since last frame or
// has only changed very little // has only changed very little
@ -625,28 +651,33 @@ void FGTable::Print(void)
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
void FGTable::bind(Element* el) string FGTable::mkPropertyName(Element* el, const std::string& Prefix)
{
if (!Prefix.empty()) {
if (is_number(Prefix)) {
if (Name.find("#") != string::npos) { // if "#" is found
Name = replace(Name, "#", Prefix);
} else {
cerr << el->ReadFrom()
<< "Malformed table name with number: " << Prefix
<< " and property name: " << Name
<< " but no \"#\" sign for substitution." << endl;
}
} else {
Name = Prefix + "/" + Name;
}
}
return PropertyManager->mkPropertyName(Name, false);
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
void FGTable::bind(Element* el, const string& Prefix)
{ {
typedef double (FGTable::*PMF)(void) const; typedef double (FGTable::*PMF)(void) const;
if ( !Name.empty() && !internal) { if ( !Name.empty() && !internal) {
string tmp; string tmp = mkPropertyName(el, Prefix);
if (Prefix.empty())
tmp = PropertyManager->mkPropertyName(Name, false); // Allow upper
else {
if (is_number(Prefix)) {
if (Name.find("#") != string::npos) { // if "#" is found
Name = replace(Name,"#",Prefix);
tmp = PropertyManager->mkPropertyName(Name, false); // Allow upper
} else {
cerr << el->ReadFrom()
<< "Malformed table name with number: " << Prefix
<< " and property name: " << Name
<< " but no \"#\" sign for substitution." << endl;
}
} else {
tmp = PropertyManager->mkPropertyName(Prefix + "/" + Name, false);
}
}
if (PropertyManager->HasNode(tmp)) { if (PropertyManager->HasNode(tmp)) {
FGPropertyNode* _property = PropertyManager->GetNode(tmp); FGPropertyNode* _property = PropertyManager->GetNode(tmp);

View file

@ -226,6 +226,19 @@ combustion_efficiency = Lookup_Combustion_Efficiency->GetValue(equivalence_ratio
@author Jon S. Berndt @author Jon S. Berndt
*/ */
/** Exception convenience class.
*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
DECLARATION: TableException
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
class TableException : public std::runtime_error
{
public:
TableException(const std::string& msg) : std::runtime_error{msg} { }
};
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
CLASS DECLARATION CLASS DECLARATION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
@ -302,11 +315,10 @@ private:
mutable int lastRowIndex, lastColumnIndex, lastTableIndex; mutable int lastRowIndex, lastColumnIndex, lastTableIndex;
double** Allocate(void); double** Allocate(void);
FGPropertyManager* const PropertyManager; FGPropertyManager* const PropertyManager;
std::string Prefix;
std::string Name; std::string Name;
void bind(Element*); void bind(Element* el, const std::string& Prefix);
unsigned int FindNumColumns(const std::string&); std::string mkPropertyName(Element* el, const std::string& Prefix);
void Debug(int from); void Debug(int from);
}; };
} }

View file

@ -223,7 +223,7 @@ bool FGAuxiliary::Run(bool Holding)
hoverbcg = in.DistanceAGL / in.Wingspan; hoverbcg = in.DistanceAGL / in.Wingspan;
FGColumnVector3 vMac = in.Tb2l * in.RPBody; FGColumnVector3 vMac = in.Tb2l * in.RPBody;
hoverbmac = (in.DistanceAGL + vMac(3)) / in.Wingspan; hoverbmac = (in.DistanceAGL - vMac(3)) / in.Wingspan;
return false; return false;
} }

View file

@ -94,8 +94,12 @@ bool FGInertial::Load(Element* el)
if (el->FindElement("semimajor_axis")) if (el->FindElement("semimajor_axis"))
a = el->FindElementValueAsNumberConvertTo("semimajor_axis", "FT"); a = el->FindElementValueAsNumberConvertTo("semimajor_axis", "FT");
else if (el->FindElement("equatorial_radius"))
a = el->FindElementValueAsNumberConvertTo("equatorial_radius", "FT");
if (el->FindElement("semiminor_axis")) if (el->FindElement("semiminor_axis"))
b = el->FindElementValueAsNumberConvertTo("semiminor_axis", "FT"); b = el->FindElementValueAsNumberConvertTo("semiminor_axis", "FT");
else if (el->FindElement("polar_radius"))
b = el->FindElementValueAsNumberConvertTo("polar_radius", "FT");
if (el->FindElement("rotation_rate")) { if (el->FindElement("rotation_rate")) {
double RotationRate = el->FindElementValueAsNumberConvertTo("rotation_rate", "RAD/SEC"); double RotationRate = el->FindElementValueAsNumberConvertTo("rotation_rate", "RAD/SEC");
vOmegaPlanet = {0., 0., RotationRate}; vOmegaPlanet = {0., 0., RotationRate};
@ -229,7 +233,7 @@ void FGInertial::SetGravityType(int gt)
// Messages to warn the user about possible inconsistencies. // Messages to warn the user about possible inconsistencies.
switch (gt) switch (gt)
{ {
case eGravType::gtStandard: case eGravType::gtStandard:
if (a != b) if (a != b)
cout << "Warning: Standard gravity model has been set for a non-spherical planet" << endl; cout << "Warning: Standard gravity model has been set for a non-spherical planet" << endl;
break; break;

View file

@ -117,9 +117,14 @@ static FGMatrix33 ReadInertiaMatrix(Element* document)
// Transform the inertia products from the structural frame to the body frame // Transform the inertia products from the structural frame to the body frame
// and create the inertia matrix. // and create the inertia matrix.
return FGMatrix33( bixx, -bixy, bixz, if (document->GetAttributeValue("negated_crossproduct_inertia") == string("false"))
-bixy, biyy, -biyz, return FGMatrix33( bixx, bixy, -bixz,
bixz, -biyz, bizz ); bixy, biyy, biyz,
-bixz, biyz, bizz );
else
return FGMatrix33( bixx, -bixy, bixz,
-bixy, biyy, -biyz,
bixz, -biyz, bizz );
} }
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View file

@ -65,12 +65,22 @@ CLASS DOCUMENTATION
The inertia tensor must be specified in the structural frame (x axis The inertia tensor must be specified in the structural frame (x axis
positive aft, y axis positive out of the right wing and z axis upward). The positive aft, y axis positive out of the right wing and z axis upward). The
sign of the inertia cross products are not modified by JSBSim so in most sign of the inertia cross products are optional by JSBSim.
cases, negative values should be provided for <ixy>, <ixz> and <iyz>. if negated_crossproduct_inertia == "true", then define:
ixy = -integral( x * y * dm ),
ixz = -integral( x * z * dm ),
iyz = -integral( y * z * dm ).
else if negated_crossproduct_inertia == "false", then define:
ixy = integral( x * y * dm ),
ixz = integral( x * z * dm ),
iyz = integral( y * z * dm ).
default is negated_crossproduct_inertia = "true".
We strongly recommend defining negated_crossproduct_inertia = "false",
which is consistent with the specifications in the field of flight dynamics.
<h3>Configuration File Format for \<mass_balance> Section:</h3> <h3>Configuration File Format for \<mass_balance> Section:</h3>
@code{.xml} @code{.xml}
<mass_balance> <mass_balance negated_crossproduct_inertia="true|false">
<ixx unit="{SLUG*FT2 | KG*M2}"> {number} </ixx> <ixx unit="{SLUG*FT2 | KG*M2}"> {number} </ixx>
<iyy unit="{SLUG*FT2 | KG*M2}"> {number} </iyy> <iyy unit="{SLUG*FT2 | KG*M2}"> {number} </iyy>
<izz unit="{SLUG*FT2 | KG*M2}"> {number} </izz> <izz unit="{SLUG*FT2 | KG*M2}"> {number} </izz>
@ -98,6 +108,11 @@ CLASS DOCUMENTATION
... other point masses ...] ... other point masses ...]
</mass_balance> </mass_balance>
@endcode @endcode
@see Stevens and Lewis, "Flight Control & Simulation"
@see Bernard Etkin, " Dynamics Of Atmosferic Flight"
@see https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
@see https://www.mathworks.com/help/physmod/sm/ug/specify-custom-inertia.html
*/ */
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View file

@ -67,6 +67,8 @@ FGActuator::FGActuator(FGFCS* fcs, Element* element)
initialized = 0; initialized = 0;
saturated = false; saturated = false;
CheckInputNodes(1, 1, element);
if ( element->FindElement("deadband_width") ) { if ( element->FindElement("deadband_width") ) {
deadband_width = element->FindElementValueAsNumber("deadband_width"); deadband_width = element->FindElementValueAsNumber("deadband_width");
} }
@ -74,7 +76,7 @@ FGActuator::FGActuator(FGFCS* fcs, Element* element)
hysteresis_width = element->FindElementValueAsNumber("hysteresis_width"); hysteresis_width = element->FindElementValueAsNumber("hysteresis_width");
} }
// There can be a single rate limit specified, or increasing and // There can be a single rate limit specified, or increasing and
// decreasing rate limits specified, and rate limits can be numeric, or // decreasing rate limits specified, and rate limits can be numeric, or
// a property. // a property.
Element* ratelim_el = element->FindElement("rate_limit"); Element* ratelim_el = element->FindElement("rate_limit");
@ -133,7 +135,7 @@ void FGActuator::ResetPastStates(void)
FGFCSComponent::ResetPastStates(); FGFCSComponent::ResetPastStates();
PreviousOutput = PreviousHystOutput = PreviousRateLimOutput PreviousOutput = PreviousHystOutput = PreviousRateLimOutput
= PreviousLagInput = PreviousLagOutput = Output = 0.0; = PreviousLagInput = PreviousLagOutput = Output = 0.0;
} }
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@ -166,7 +168,7 @@ bool FGActuator::Run(void )
} }
PreviousOutput = Output; // previous value needed for "stuck" malfunction PreviousOutput = Output; // previous value needed for "stuck" malfunction
initialized = 1; initialized = 1;
Clip(); Clip();
@ -223,7 +225,7 @@ void FGActuator::Hysteresis(void)
// "Output" is - for the purposes of this Hysteresis method - really the input // "Output" is - for the purposes of this Hysteresis method - really the input
// to the method. // to the method.
double input = Output; double input = Output;
if ( initialized ) { if ( initialized ) {
if (input > PreviousHystOutput) if (input > PreviousHystOutput)
Output = max(PreviousHystOutput, input-0.5*hysteresis_width); Output = max(PreviousHystOutput, input-0.5*hysteresis_width);

View file

@ -56,6 +56,8 @@ FGDeadBand::FGDeadBand(FGFCS* fcs, Element* element)
Width = nullptr; Width = nullptr;
gain = 1.0; gain = 1.0;
CheckInputNodes(1, 1, element);
Element* width_element = element->FindElement("width"); Element* width_element = element->FindElement("width");
if (width_element) if (width_element)
Width = new FGParameterValue(width_element, PropertyManager); Width = new FGParameterValue(width_element, PropertyManager);

View file

@ -116,7 +116,7 @@ FGFCSComponent::FGFCSComponent(FGFCS* _fcs, Element* element) : fcs(_fcs)
PropertyManager )); PropertyManager ));
init_element = element->FindNextElement("init"); init_element = element->FindNextElement("init");
} }
Element *input_element = element->FindElement("input"); Element *input_element = element->FindElement("input");
while (input_element) { while (input_element) {
InputNodes.push_back(new FGPropertyValue(input_element->GetDataLine(), InputNodes.push_back(new FGPropertyValue(input_element->GetDataLine(),
@ -212,6 +212,30 @@ void FGFCSComponent::ResetPastStates(void)
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
void FGFCSComponent::CheckInputNodes(size_t MinNodes, size_t MaxNodes, Element* el)
{
size_t num = InputNodes.size();
if (num < MinNodes) {
cerr << el->ReadFrom()
<< " Not enough <input> nodes are provided" << endl
<< " Expecting " << MinNodes << " while " << num
<< " are provided." << endl;
throw("Some inputs are missing.");
}
if (num > MaxNodes) {
cerr << el->ReadFrom()
<< " Too many <input> nodes are provided" << endl
<< " Expecting " << MaxNodes << " while " << num
<< " are provided." << endl
<< " The last " << num-MaxNodes << " input nodes will be ignored."
<< endl;
}
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
void FGFCSComponent::SetOutput(void) void FGFCSComponent::SetOutput(void)
{ {
for (auto node: OutputNodes) for (auto node: OutputNodes)
@ -329,7 +353,7 @@ void FGFCSComponent::Debug(int from)
if (clip) { if (clip) {
cout << " Minimum limit: " << ClipMin->GetName() << endl; cout << " Minimum limit: " << ClipMin->GetName() << endl;
cout << " Maximum limit: " << ClipMax->GetName() << endl; cout << " Maximum limit: " << ClipMax->GetName() << endl;
} }
if (delay > 0) cout <<" Frame delay: " << delay if (delay > 0) cout <<" Frame delay: " << delay
<< " frames (" << delay*dt << " sec)" << endl; << " frames (" << delay*dt << " sec)" << endl;
} }

View file

@ -117,6 +117,7 @@ protected:
void Delay(void); void Delay(void);
void Clip(void); void Clip(void);
void CheckInputNodes(size_t MinNodes, size_t MaxNodes, Element* el);
virtual void bind(Element* el); virtual void bind(Element* el);
virtual void Debug(int from); virtual void Debug(int from);
}; };

View file

@ -52,6 +52,9 @@ FGFilter::FGFilter(FGFCS* fcs, Element* element)
: FGFCSComponent(fcs, element), DynamicFilter(false), Initialize(true) : FGFCSComponent(fcs, element), DynamicFilter(false), Initialize(true)
{ {
C[1] = C[2] = C[3] = C[4] = C[5] = C[6] = nullptr; C[1] = C[2] = C[3] = C[4] = C[5] = C[6] = nullptr;
CheckInputNodes(1, 1, element);
for (int i=1; i<7; i++) for (int i=1; i<7; i++)
ReadFilterCoefficients(element, i); ReadFilterCoefficients(element, i);
@ -88,11 +91,11 @@ void FGFilter::ResetPastStates(void)
void FGFilter::ReadFilterCoefficients(Element* element, int index) void FGFilter::ReadFilterCoefficients(Element* element, int index)
{ {
// index is known to be 1-7. // index is known to be 1-7.
// A stringstream would be overkill, but also trying to avoid sprintf // A stringstream would be overkill, but also trying to avoid sprintf
string coefficient = "c0"; string coefficient = "c0";
coefficient[1] += index; coefficient[1] += index;
if ( element->FindElement(coefficient) ) { if ( element->FindElement(coefficient) ) {
C[index] = new FGParameterValue(element->FindElement(coefficient), C[index] = new FGParameterValue(element->FindElement(coefficient),
PropertyManager); PropertyManager);
@ -151,9 +154,9 @@ bool FGFilter::Run(void)
} else { } else {
Input = InputNodes[0]->getDoubleValue(); Input = InputNodes[0]->getDoubleValue();
if (DynamicFilter) CalculateDynamicFilters(); if (DynamicFilter) CalculateDynamicFilters();
switch (FilterType) { switch (FilterType) {
case eLag: case eLag:
Output = (Input + PreviousInput1) * ca + PreviousOutput1 * cb; Output = (Input + PreviousInput1) * ca + PreviousOutput1 * cb;

View file

@ -57,6 +57,8 @@ FGGain::FGGain(FGFCS* fcs, Element* element) : FGFCSComponent(fcs, element)
InMax = 1.0; InMax = 1.0;
OutMin = OutMax = 0.0; OutMin = OutMax = 0.0;
CheckInputNodes(1, 1, element);
if (Type == "PURE_GAIN") { if (Type == "PURE_GAIN") {
if ( !element->FindElement("gain") ) { if ( !element->FindElement("gain") ) {
cerr << element->ReadFrom() cerr << element->ReadFrom()

View file

@ -56,6 +56,8 @@ FGKinemat::FGKinemat(FGFCS* fcs, Element* element)
double tmpDetent; double tmpDetent;
double tmpTime; double tmpTime;
CheckInputNodes(1, 1, element);
Detents.clear(); Detents.clear();
TransitionTimes.clear(); TransitionTimes.clear();

View file

@ -51,6 +51,8 @@ CLASS IMPLEMENTATION
FGLinearActuator::FGLinearActuator(FGFCS* fcs, Element* element) FGLinearActuator::FGLinearActuator(FGFCS* fcs, Element* element)
: FGFCSComponent(fcs, element) : FGFCSComponent(fcs, element)
{ {
CheckInputNodes(1, 1, element);
ptrSet = nullptr; ptrSet = nullptr;
if (element->FindElement("set")) { if (element->FindElement("set")) {
string property_string = element->FindElementValue("set"); string property_string = element->FindElementValue("set");

View file

@ -57,6 +57,8 @@ FGPID::FGPID(FGFCS* fcs, Element* element) : FGFCSComponent(fcs, element)
IsStandard = false; IsStandard = false;
IntType = eNone; // No integrator initially defined. IntType = eNone; // No integrator initially defined.
CheckInputNodes(1, 1, element);
string pid_type = element->GetAttributeValue("type"); string pid_type = element->GetAttributeValue("type");
if (pid_type == "standard") IsStandard = true; if (pid_type == "standard") IsStandard = true;
@ -190,7 +192,7 @@ bool FGPID::Run(void )
} }
if (test < 0.0) I_out_total = 0.0; // Reset integrator to 0.0 if (test < 0.0) I_out_total = 0.0; // Reset integrator to 0.0
I_out_total += Ki->GetValue() * dt * I_out_delta; I_out_total += Ki->GetValue() * dt * I_out_delta;
if (IsStandard) if (IsStandard)

View file

@ -190,8 +190,9 @@ double FGTurbine::Off(void)
FuelFlow_pph = Seek(&FuelFlow_pph, 0, 1000.0, 10000.0); FuelFlow_pph = Seek(&FuelFlow_pph, 0, 1000.0, 10000.0);
// some engines have inlets that close when they are off. So, if a flag is true disable windmilling // some engines have inlets that close when they are off. So, if a flag is true disable windmilling
if (disableWindmill == false) { if (disableWindmill == false) {
N1 = Seek(&N1, in.qbar/10.0, N1/2.0, N1/N1_spindown); // Need a small non-zero increment for acceleration otherwise acceleration will be 0 if N1 = 0
N2 = Seek(&N2, in.qbar/15.0, N2/2.0, N2/N2_spindown); N1 = Seek(&N1, in.qbar/10.0, N1/2.0 + 0.1, N1/N1_spindown);
N2 = Seek(&N2, in.qbar/15.0, N2/2.0 + 0.1, N2/N2_spindown);
} else { } else {
N1 = Seek(&N1, 0, N1/2.0, N1/N1_spindown); N1 = Seek(&N1, 0, N1/2.0, N1/N1_spindown);
N2 = Seek(&N2, 0, N2/2.0, N2/N2_spindown); N2 = Seek(&N2, 0, N2/2.0, N2/N2_spindown);