1
0
Fork 0

New features for JSBSim (not to be included in release 2018.3)

The following new features have been added to JSBSim:
* Added the ability to set up the starter and acceleration times of a turbine (parameters <n1spinup>, <n2spinup>, <n1startrate>, <n2startrate>).
* The <integrator> filter can now be reset to 0.0 by setting its <trigger> property to a negative value.
* The integration scheme of the <integrator> filter can now be chosen among "rect" (Euler), "trap" (Trapezoidal), "ab2" 2nd order Adams-BashForth and "ab3" 3rd order Adams-Bashforth
* The following functions can now be used in <function>: floor, ceil and fmod. Their functionalities are the same than the corresponding C/C++ functions.
This commit is contained in:
Bertrand Coconnier 2018-10-28 16:16:20 +01:00
parent 94e1cdc551
commit 04eb045931
8 changed files with 133 additions and 48 deletions

View file

@ -177,6 +177,7 @@ public:
/** Retrieves the element name.
@return the element name, or the empty string if no name has been set.*/
const std::string& GetName(void) const {return name;}
void ChangeName(const std::string& _name) { name = _name; }
/** Gets a line of data belonging to an element.
@param i the index of the data line to return (0 by default).

View file

@ -107,6 +107,9 @@ const std::string FGFunction::not_string = "not";
const std::string FGFunction::ifthen_string = "ifthen";
const std::string FGFunction::switch_string = "switch";
const std::string FGFunction::interpolate1d_string = "interpolate1d";
const std::string FGFunction::floor_string = "floor";
const std::string FGFunction::ceil_string = "ceil";
const std::string FGFunction::fmod_string = "fmod";
FGFunction::FGFunction(FGPropertyManager* PropertyManager, Element* el,
const string& prefix, FGPropertyValue* var)
@ -228,6 +231,12 @@ void FGFunction::Load(FGPropertyManager* PropertyManager, Element* el,
Type = eSwitch;
} else if (operation == interpolate1d_string) {
Type = eInterpolate1D;
} else if (operation == floor_string) {
Type = eFloor;
} else if (operation == ceil_string) {
Type = eCeil;
} else if (operation == fmod_string) {
Type = eFmod;
} else if (operation != description_string) {
cerr << "Bad operation " << operation << " detected in configuration file" << endl;
}
@ -324,7 +333,10 @@ void FGFunction::Load(FGPropertyManager* PropertyManager, Element* el,
operation == not_string ||
operation == ifthen_string ||
operation == switch_string ||
operation == interpolate1d_string)
operation == interpolate1d_string ||
operation == floor_string ||
operation == ceil_string ||
operation == fmod_string)
{
Parameters.push_back(new FGFunction(PropertyManager, element, Prefix, var));
} else if (operation != description_string) {
@ -767,6 +779,18 @@ double FGFunction::GetValue(void) const
else //
{temp = 1;}
break;
case eFloor:
temp = floor(temp);
break;
case eCeil:
temp = ceil(temp);
break;
case eFmod:
if (Parameters[1]->GetValue() != 0.0)
temp = fmod(temp, Parameters[1]->GetValue());
else
temp = HUGE_VAL;
break;
default:
cerr << "Unknown function operation type" << endl;
break;

View file

@ -84,6 +84,9 @@ A function definition consists of an operation, a value, a table, or a property
- avg (takes n args)
- fraction
- mod
- floor (takes 1 arg)
- ceil (takes 1 arg)
- fmod (takes 2 args)
- lt (less than, takes 2 args)
- le (less equal, takes 2 args)
- gt (greater than, takes 2 args)
@ -466,13 +469,36 @@ of the three words refers to one or more instances of a property, value, or tabl
quotient of 3 and leaves a remainder of 0.
@code
<mod>
{property, value, table, or other function element} {property, value, table, or other function element}
{property, value, table, or other function element}
{property, value, table, or other function element}
</mod>
Example: 5 mod 2, evaluates to 1
<mod> <v> 5 </v> <v> 2 </v> </mod>
@endcode
- @b floor returns the largest integral value that is not greater than X.
@code
<floor>
{property, value, table, or other function element}
</floor>
@endcode
Examples: floor(2.3) evaluates to 2.0 while floor(-2.3) evaluates to -3.0
- @b ceil returns the smallest integral value that is not less than X.
@code
<ceil>
{property, value, table, or other function element}
</ceil>
@endcode
Examples: ceil(2.3) evaluates to 3.0 while ceil(-2.3) evaluates to -2.0
- @b fmod returns the floating-point remainder of X/Y (rounded towards zero)
@code
<fmod>
{property, value, table, or other function element}
{property, value, table, or other function element}
</fmod>
@endcode
Example: fmod(18.5, 4.2) evaluates to 1.7
- @b lt returns a 1 if the value of the first immediate child element is less
than the value of the second immediate child element, returns 0
otherwise
@ -799,6 +825,9 @@ private:
static const std::string ifthen_string;
static const std::string switch_string;
static const std::string interpolate1d_string;
static const std::string floor_string;
static const std::string ceil_string;
static const std::string fmod_string;
enum functionType {eTopLevel=0, eProduct, eDifference, eSum, eQuotient, ePow, eSqrt, eToRadians,
eToDegrees, eExp, eAbs, eSign, eSin, eCos, eTan, eASin, eACos, eATan, eATan2,
@ -806,7 +835,7 @@ private:
eLog2, eLn, eLog10, eLT, eLE, eGE, eGT, eEQ, eNE, eAND, eOR, eNOT,
eIfThen, eSwitch, eInterpolate1D, eRotation_alpha_local,
eRotation_beta_local, eRotation_gamma_local, eRotation_bf_to_wf,
eRotation_wf_to_bf} Type;
eRotation_wf_to_bf, eFloor, eCeil, eFmod} Type;
std::string Prefix;
bool cached;
double cachedValue;

View file

@ -534,8 +534,7 @@ bool FGFCS::Load(Element* document)
if ((component_element->GetName() == string("lag_filter")) ||
(component_element->GetName() == string("lead_lag_filter")) ||
(component_element->GetName() == string("washout_filter")) ||
(component_element->GetName() == string("second_order_filter")) ||
(component_element->GetName() == string("integrator")) )
(component_element->GetName() == string("second_order_filter")) )
{
newChannel->Add(new FGFilter(this, component_element));
} else if ((component_element->GetName() == string("pure_gain")) ||
@ -555,6 +554,18 @@ bool FGFCS::Load(Element* document)
newChannel->Add(new FGFCSFunction(this, component_element));
} else if (component_element->GetName() == string("pid")) {
newChannel->Add(new FGPID(this, component_element));
} else if (component_element->GetName() == string("integrator")) {
// <integrator> is equivalent to <pid type="trap">
Element* c1_el = component_element->FindElement("c1");
if (!c1_el) {
cerr << component_element->ReadFrom();
throw("INTEGRATOR component " + component_element->GetAttributeValue("name")
+ " does not provide the parameter <c1>");
}
c1_el->ChangeName("ki");
if (!c1_el->HasAttribute("type"))
c1_el->AddAttribute("type", "trap");
newChannel->Add(new FGPID(this, component_element));
} else if (component_element->GetName() == string("actuator")) {
newChannel->Add(new FGActuator(this, component_element));
} else if (component_element->GetName() == string("sensor")) {

View file

@ -68,7 +68,6 @@ FGFilter::FGFilter(FGFCS* fcs, Element* element) : FGFCSComponent(fcs, element)
else if (Type == "LEAD_LAG_FILTER") FilterType = eLeadLag ;
else if (Type == "SECOND_ORDER_FILTER") FilterType = eOrder2 ;
else if (Type == "WASHOUT_FILTER") FilterType = eWashout ;
else if (Type == "INTEGRATOR") FilterType = eIntegrator ;
else FilterType = eUnknown ;
if (element->FindElement("trigger")) {
@ -170,10 +169,6 @@ void FGFilter::CalculateDynamicFilters(void)
ca = 2.00 / denom;
cb = (2.00 - dt*C[1]) / denom;
break;
case eIntegrator:
if (PropertyNode[1] != 0L) C[1] = PropertyNode[1]->getDoubleValue()*PropertySign[1];
ca = dt*C[1] / 2.00;
break;
case eUnknown:
cerr << "Unknown filter type" << endl;
break;
@ -210,15 +205,6 @@ bool FGFilter::Run(void)
case eWashout:
Output = Input * ca - PreviousInput1 * ca + PreviousOutput1 * cb;
break;
case eIntegrator:
if (Trigger != 0) {
double test = Trigger->getDoubleValue();
if (fabs(test) > 0.000001) {
Input = PreviousInput1 = PreviousInput2 = 0.0;
}
}
Output = Input * ca + PreviousInput1 * ca + PreviousOutput1;
break;
case eUnknown:
break;
}
@ -321,12 +307,6 @@ void FGFilter::Debug(int from)
if (PropertyNode[1] == 0L) cout << " C[1]: " << C[1] << endl;
else cout << " C[1] is the value of property: " << sgn << PropertyNode[1]->GetName() << endl;
break;
case eIntegrator:
if (PropertySign[1] < 0.0) sgn="-";
else sgn = "";
if (PropertyNode[1] == 0L) cout << " C[1]: " << C[1] << endl;
else cout << " C[1] is the value of property: " << sgn << PropertyNode[1]->GetName() << endl;
break;
case eUnknown:
break;
}

View file

@ -199,15 +199,15 @@ For an integrator of the form:
The corresponding filter definition is:
@code
<integrator name="name">
<input> property </input>
<c1> value|property </c1>
[<trigger> property </trigger>]
<integrator name="{string}">
<input> {property} </input>
<c1 type="rect|trap|ab2|ab3"> {[-]property | number} </c1>
[<trigger> {property} </trigger>]
[<clipto>
<min> {[-]property name | value} </min>
<max> {[-]property name | value} </max>
<min> {[-]property | number} </min>
<max> {[-]property | number} </max>
</clipto>]
[<output> property </output>]
[<output> {property} </output>]
</integrator>
@endcode
@ -217,12 +217,32 @@ property value is:
- not 0: (or simply greater than zero), all current and previous inputs will
be set to 0.0
By default, the integration scheme is the trapezoidal scheme.
An integrator is equivalent to a PID with the following parameters:
@code
<pid name="{string}">
<input> {[-]property} </input>
<kp> 0.0 </kp>
<ki type="rect|trap|ab2|ab3"> {number|[-]property} </ki>
<kd> 0.0 </kd>
<trigger> {property} </trigger>
[<clipto>
<min> {[-]property | value} </min>
<max> {[-]property | value} </max>
</clipto>]
[<output> {property} </output>]
</pid>
@endcode
As a consequence, JSBSim internally uses PID controllers to simulate INTEGRATOR
filters.
In all the filter specifications above, an \<output> element is also seen. This
is so that the last component in a "string" can copy its value to the appropriate
output, such as the elevator, or speedbrake, etc.
is so that the last component in a "string" can copy its value to the
appropriate output, such as the elevator, or speedbrake, etc.
@author Jon S. Berndt
@version $Revision: 1.14 $
*/
@ -243,7 +263,7 @@ public:
bool Initialize;
void ResetPastStates(void);
enum {eLag, eLeadLag, eOrder2, eWashout, eIntegrator, eUnknown} FilterType;
enum {eLag, eLeadLag, eOrder2, eWashout, eUnknown} FilterType;
private:
double ca;

View file

@ -70,7 +70,7 @@ FGTurbine::FGTurbine(FGFDMExec* exec, Element *el, int engine_number, struct Inp
Augmented = AugMethod = Injected = 0;
BypassRatio = BleedDemand = 0.0;
IdleThrustLookup = MilThrustLookup = MaxThrustLookup = InjectionLookup = 0;
N1_spinup = 1.0; N2_spinup = 3.0;
N1_spinup = 1.0; N2_spinup = 3.0; IgnitionN1 = 5.21; IgnitionN2 = 25.18; N1_start_rate = 1.4; N2_start_rate = 2.0;
InjectionTime = 30.0;
InjectionTimer = InjWaterNorm = 0.0;
EPR = 1.0;
@ -281,8 +281,8 @@ double FGTurbine::SpinUp(void)
{
Running = false;
FuelFlow_pph = 0.0;
N2 = Seek(&N2, 25.18, N2_spinup, N2/2.0);
N1 = Seek(&N1, 5.21, N1_spinup, N1/2.0);
N2 = Seek(&N2, IgnitionN2, N2_spinup, N2/2.0);
N1 = Seek(&N1, IgnitionN1, N1_spinup, N1/2.0);
EGT_degC = Seek(&EGT_degC, in.TAT_c, 11.7, 7.3);
OilPressure_psi = N2 * 0.62;
OilTemp_degK = Seek(&OilTemp_degK, in.TAT_c + 273.0, 0.2, 0.2);
@ -299,8 +299,8 @@ double FGTurbine::Start(void)
if ((N2 > 15.0) && !Starved) { // minimum 15% N2 needed for start
Cranking = true; // provided for sound effects signal
if (N2 < IdleN2) {
N2 = Seek(&N2, IdleN2, 2.0, N2/2.0);
N1 = Seek(&N1, IdleN1, 1.4, N1/2.0);
N2 = Seek(&N2, IdleN2, N2_start_rate, N2/2.0);
N1 = Seek(&N1, IdleN1, N1_start_rate, N1/2.0);
EGT_degC = Seek(&EGT_degC, in.TAT_c + 363.1, 21.3, 7.3);
FuelFlow_pph = IdleFF * N2 / IdleN2;
OilPressure_psi = N2 * 0.62;
@ -448,6 +448,10 @@ bool FGTurbine::Load(FGFDMExec* exec, Element *el)
TSFC = el->FindElementValueAsNumber("tsfc");
if (el->FindElement("atsfc"))
ATSFC = el->FindElementValueAsNumber("atsfc");
if (el->FindElement("ignitionn1"))
IgnitionN1 = el->FindElementValueAsNumber("ignitionn1");
if (el->FindElement("ignitionn2"))
IgnitionN1 = el->FindElementValueAsNumber("ignitionn2");
if (el->FindElement("idlen1"))
IdleN1 = el->FindElementValueAsNumber("idlen1");
if (el->FindElement("idlen2"))
@ -460,6 +464,10 @@ bool FGTurbine::Load(FGFDMExec* exec, Element *el)
N1_spinup = el->FindElementValueAsNumber("n1spinup");
if (el->FindElement("n2spinup"))
N2_spinup = el->FindElementValueAsNumber("n2spinup");
if (el->FindElement("n1startrate"))
N1_start_rate = el->FindElementValueAsNumber("n1startrate");
if (el->FindElement("n2startrate"))
N2_start_rate = el->FindElementValueAsNumber("n2startrate");
if (el->FindElement("augmented"))
Augmented = (int)el->FindElementValueAsNumber("augmented");
if (el->FindElement("augmethod"))

View file

@ -66,8 +66,8 @@ CLASS DOCUMENTATION
<P>
- STARTING (on ground):
-# Set the control FGEngine::Starter to true. The engine will spin up to
a maximum of about %25 N2 (%5.2 N1). This simulates the action of a
pneumatic starter.
a maximum of about %25 N2 (%5.2 N1). This value may be changed using the <startnX> parameter.
This simulates the action of a pneumatic starter.
-# After reaching %15 N2 set the control FGEngine::Cutoff to false. If fuel
is available the engine will now accelerate to idle. The starter will
automatically be set to false after the start cycle.
@ -78,7 +78,7 @@ CLASS DOCUMENTATION
-# Place the control FGEngine::Cutoff to false.
<P>
Ignition is assumed to be on anytime the Cutoff control is set to false,
therefore a seperate ignition system is not modeled.
therefore a separate ignition system is not modeled.
<h3>Configuration File Format:</h3>
@code
@ -89,10 +89,14 @@ CLASS DOCUMENTATION
<bleed> {number} </bleed>
<tsfc> {number} </tsfc>
<atsfc> {number} </atsfc>
<ignitionn1> {number} </ignitionn1>
<ignitionn2> {number} </ignitionn2>
<idlen1> {number} </idlen1>
<idlen2> {number} </idlen2>
<n1spinup> {number} </n1spinup>
<n2spinup> {number} </n2spinup>
<n1startrate> {number} </n1startrate>
<n2startrate> {number} </n2startrate>
<maxn1> {number} </maxn1>
<maxn2> {number} </maxn2>
<augmented> {0 | 1} </augmented>
@ -111,10 +115,14 @@ CLASS DOCUMENTATION
bleed - Thrust reduction factor due to losses (0.0 to 1.0).
tsfc - Thrust-specific fuel consumption at cruise, lbm/hr/lbf
atsfc - Afterburning TSFC, lbm/hr/lbf
ignitionn1 - Fan rotor rpm (% of max) while starting
ignitionn2 - Core rotor rpm (% of max) while starting
idlen1 - Fan rotor rpm (% of max) at idle
idlen2 - Core rotor rpm (% of max) at idle
n1spinup - Fan rotor rpm starter acceleration (default 1.0)
n2spinup - Core rotor rpm starter acceleration (default 3.0)
n1spinup - Fan rotor rpm starter acceleration to ignitionn1 value (default 1.0)
n2spinup - Core rotor rpm starter acceleration to ignitionn2 value (default 3.0)
n1startrate - Fan rotor rpm time taken to accelerate from ignitionn1 to idlen1 value (default 1.4)
n2startrate - Core rotor rpm time taken to accelerate to ignitionn2 idlen2 value (default 2.0)
maxn1 - Fan rotor rpm (% of max) at full throttle
maxn2 - Core rotor rpm (% of max) at full throttle
augmented
@ -238,6 +246,8 @@ private:
double ATSFC; ///< Augmented TSFC (lbm/hr/lbf)
double IdleN1; ///< Idle N1
double IdleN2; ///< Idle N2
double IgnitionN1; ///< Ignition N1
double IgnitionN2; ///< Ignition N2
double N1; ///< N1
double N2; ///< N2
double N2norm; ///< N2 normalized (0=idle, 1=max)
@ -248,8 +258,10 @@ private:
double N2_factor; ///< factor to tie N2 and throttle
double ThrottlePos; ///< FCS-supplied throttle position - modified for local use!
double AugmentCmd; ///< modulated afterburner command (0.0 to 1.0)
double N1_spinup; ///< N1 spin up rate from starter (per second)
double N2_spinup; ///< N2 spin up rate from starter (per second)
double N1_spinup; ///< N1 spin up rate from pneumatic starter (per second)
double N2_spinup; ///< N2 spin up rate from pneumatic starter (per second)
double N1_start_rate; ///< N1 spin up rate from ignition (per second)
double N2_start_rate; ///< N2 spin up rate from ignition (per second)
bool Stalled; ///< true if engine is compressor-stalled
bool Seized; ///< true if inner spool is seized
bool Overtemp; ///< true if EGT exceeds limits