JSBSim Flight Dynamics Model  1.2.0 (05 Nov 2023)
An Open Source Flight Dynamics and Control Software Library in C++
FGFunction.cpp
1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2 
3 Module: FGFunction.cpp
4 Author: Jon Berndt
5 Date started: 8/25/2004
6 Purpose: Stores various parameter types for functions
7 
8  ------------- Copyright (C) 2004 Jon S. Berndt (jon@jsbsim.org) -------------
9 
10  This program is free software; you can redistribute it and/or modify it under
11  the terms of the GNU Lesser General Public License as published by the Free
12  Software Foundation; either version 2 of the License, or (at your option) any
13  later version.
14 
15  This program is distributed in the hope that it will be useful, but WITHOUT
16  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
18  details.
19 
20  You should have received a copy of the GNU Lesser General Public License along
21  with this program; if not, write to the Free Software Foundation, Inc., 59
22  Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 
24  Further information about the GNU Lesser General Public License can also be
25  found on the world wide web at http://www.gnu.org.
26 
27 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
28 INCLUDES
29 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
30 
31 #include <iomanip>
32 #include <memory>
33 
34 #include "simgear/misc/strutils.hxx"
35 #include "FGFDMExec.h"
36 #include "FGFunction.h"
37 #include "FGTable.h"
38 #include "FGRealValue.h"
39 #include "input_output/FGXMLElement.h"
40 #include "math/FGFunctionValue.h"
41 
42 
43 using namespace std;
44 
45 namespace JSBSim {
46 
47 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
48 CLASS IMPLEMENTATION
49 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
50 
51 const double invlog2val = 1.0/log10(2.0);
52 constexpr unsigned int MaxArgs = 9999;
53 
54 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
55 
56 class WrongNumberOfArguments : public runtime_error
57 {
58 public:
59  WrongNumberOfArguments(const string &msg, const vector<FGParameter_ptr> &p,
60  Element* el)
61  : runtime_error(msg), Parameters(p), element(el) {}
62  size_t NumberOfArguments(void) const { return Parameters.size(); }
63  FGParameter* FirstParameter(void) const { return *(Parameters.cbegin()); }
64  const Element* GetElement(void) const { return element; }
65 
66 private:
67  const vector<FGParameter_ptr> Parameters;
68  const Element* element;
69 };
70 
71 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
72 
73 template<typename func_t, unsigned int Nmin>
74 class aFunc: public FGFunction
75 {
76 public:
77  aFunc(const func_t& _f, FGFDMExec* fdmex, Element* el,
78  const string& prefix, FGPropertyValue* v, unsigned int Nmax=Nmin,
79  FGFunction::OddEven odd_even=FGFunction::OddEven::Either)
80  : FGFunction(fdmex->GetPropertyManager()), f(_f)
81  {
82  Load(el, v, fdmex, prefix);
83  CheckMinArguments(el, Nmin);
84  CheckMaxArguments(el, Nmax);
85  CheckOddOrEvenArguments(el, odd_even);
86  }
87 
88  double GetValue(void) const override {
89  return cached ? cachedValue : f(Parameters);
90  }
91 
92 protected:
93  void bind(Element* el, const string& Prefix) override {
94  string nName = CreateOutputNode(el, Prefix);
95  if (!nName.empty())
96  PropertyManager->Tie(nName, this, &aFunc<func_t, Nmin>::GetValue);
97  }
98 
99 private:
100  const func_t f;
101 };
102 
103 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
104 // Template specialization for functions without parameters.
105 
106 template<typename func_t>
107 class aFunc<func_t, 0>: public FGFunction
108 {
109 public:
110  aFunc(const func_t& _f, std::shared_ptr<FGPropertyManager> pm, Element* el,
111  const string& Prefix)
112  : FGFunction(pm), f(_f)
113  {
114  if (el->GetNumElements() != 0) {
115  ostringstream buffer;
116  buffer << el->ReadFrom() << fgred << highint
117  << "<" << el->GetName() << "> should have no arguments." << reset
118  << endl;
119  throw WrongNumberOfArguments(buffer.str(), Parameters, el);
120  }
121 
122  bind(el, Prefix);
123  }
124 
125  double GetValue(void) const override {
126  double result = cached ? cachedValue : f();
127  if (pNode) pNode->setDoubleValue(result);
128  return result;
129  }
130 
131  // Functions without parameters are assumed to be non-const
132  bool IsConstant(void) const override {
133  return false;
134  }
135 
136 protected:
137  // The method GetValue() is not bound for functions without parameters because
138  // we do not want the property to return a different value each time it is
139  // read.
140  void bind(Element* el, const string& Prefix) override {
141  CreateOutputNode(el, Prefix);
142  // Initialize the node to a sensible value.
143  if (pNode) pNode->setDoubleValue(f());
144  }
145 
146 private:
147  const func_t f;
148 };
149 
150 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
151 
152 bool GetBinary(double val, const string &ctxMsg)
153 {
154  val = fabs(val);
155  if (val < 1E-9) return false;
156  else if (val-1 < 1E-9) return true;
157  else {
158  cerr << ctxMsg << FGJSBBase::fgred << FGJSBBase::highint
159  << "Malformed conditional check in function definition."
160  << FGJSBBase::reset << endl;
161  throw("Fatal Error.");
162  }
163 }
164 
165 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
166 // Hides the machinery to create a class for functions from <math.h> such as
167 // sin, cos, exp, etc.
168 
169 FGFunction* make_MathFn(double(*math_fn)(double), FGFDMExec* fdmex, Element* el,
170  const string& prefix, FGPropertyValue* v)
171 {
172  auto f = [math_fn](const std::vector<FGParameter_ptr> &p)->double {
173  return math_fn(p[0]->GetValue());
174  };
175  return new aFunc<decltype(f), 1>(f, fdmex, el, prefix, v);
176 }
177 
178 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
179 // Manage the functions with a variable number of arguments.
180 // It handles the special case where a single argument is provided to the
181 // function: in that case the function is ignored and replaced by its argument.
182 
183 template<typename func_t>
184 FGParameter_ptr VarArgsFn(const func_t& _f, FGFDMExec* fdmex, Element* el,
185  const string& prefix, FGPropertyValue* v)
186 {
187  try {
188  return new aFunc<func_t, 2>(_f, fdmex, el, prefix, v, MaxArgs);
189  }
190  catch(WrongNumberOfArguments& e) {
191  if ((e.GetElement() == el) && (e.NumberOfArguments() == 1)) {
192  cerr << el->ReadFrom() << FGJSBBase::fgred
193  << "<" << el->GetName()
194  << "> only has one argument which makes it a no-op." << endl
195  << "Its argument will be evaluated but <" << el->GetName()
196  << "> will not be applied to the result." << FGJSBBase::reset << endl;
197  return e.FirstParameter();
198  }
199  else
200  throw e.what();
201  }
202 }
203 
204 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
205 
206 FGFunction::FGFunction(FGFDMExec* fdmex, Element* el, const string& prefix,
207  FGPropertyValue* var)
208  : FGFunction(fdmex->GetPropertyManager())
209 {
210  Load(el, var, fdmex, prefix);
211  CheckMinArguments(el, 1);
212  CheckMaxArguments(el, 1);
213 
214  string sCopyTo = el->GetAttributeValue("copyto");
215 
216  if (!sCopyTo.empty()) {
217  if (sCopyTo.find("#") != string::npos) {
218  if (is_number(prefix))
219  sCopyTo = replace(sCopyTo,"#",prefix);
220  else {
221  cerr << el->ReadFrom() << fgred
222  << "Illegal use of the special character '#'" << reset << endl
223  << "The 'copyto' argument in function " << Name << " is ignored."
224  << endl;
225  return;
226  }
227  }
228 
229  pCopyTo = PropertyManager->GetNode(sCopyTo);
230  if (!pCopyTo)
231  cerr << el->ReadFrom() << fgred
232  << "Property \"" << sCopyTo
233  << "\" must be previously defined in function " << Name << reset
234  << "The 'copyto' argument is ignored." << endl;
235  }
236 }
237 
238 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
239 
240 void FGFunction::CheckMinArguments(Element* el, unsigned int _min)
241 {
242  if (Parameters.size() < _min) {
243  ostringstream buffer;
244  buffer << el->ReadFrom() << fgred << highint
245  << "<" << el->GetName() << "> should have at least " << _min
246  << " argument(s)." << reset << endl;
247  throw WrongNumberOfArguments(buffer.str(), Parameters, el);
248  }
249 }
250 
251 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
252 
253 void FGFunction::CheckMaxArguments(Element* el, unsigned int _max)
254 {
255  if (Parameters.size() > _max) {
256  ostringstream buffer;
257  buffer << el->ReadFrom() << fgred << highint
258  << "<" << el->GetName() << "> should have no more than " << _max
259  << " argument(s)." << reset << endl;
260  throw WrongNumberOfArguments(buffer.str(), Parameters, el);
261  }
262 }
263 
264 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
265 
266 void FGFunction::CheckOddOrEvenArguments(Element* el, OddEven odd_even)
267 {
268 
269  switch(odd_even) {
270  case OddEven::Even:
271  if (Parameters.size() % 2 == 1) {
272  cerr << el->ReadFrom() << fgred << highint
273  << "<" << el->GetName() << "> must have an even number of arguments."
274  << reset << endl;
275  throw("Fatal Error");
276  }
277  break;
278  case OddEven::Odd:
279  if (Parameters.size() % 2 == 0) {
280  cerr << el->ReadFrom() << fgred << highint
281  << "<" << el->GetName() << "> must have an odd number of arguments."
282  << reset << endl;
283  throw("Fatal Error");
284  }
285  break;
286  default:
287  break;
288  }
289 }
290 
291 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
292 
293 shared_ptr<RandomNumberGenerator> makeRandomGenerator(Element *el, FGFDMExec* fdmex)
294 {
295  string seed_attr = el->GetAttributeValue("seed");
296  if (seed_attr.empty())
297  return fdmex->GetRandomGenerator();
298  else if (seed_attr == "time_now")
299  return make_shared<RandomNumberGenerator>();
300  else {
301  unsigned int seed = atoi(seed_attr.c_str());
302  return make_shared<RandomNumberGenerator>(seed);
303  }
304 }
305 
306 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
307 
308 void FGFunction::Load(Element* el, FGPropertyValue* var, FGFDMExec* fdmex,
309  const string& Prefix)
310 {
311  Name = el->GetAttributeValue("name");
312  Element* element = el->GetElement();
313 
314  auto sum = [](const decltype(Parameters)& Parameters)->double {
315  double temp = 0.0;
316 
317  for (auto p: Parameters)
318  temp += p->GetValue();
319 
320  return temp;
321  };
322 
323  while (element) {
324  string operation = element->GetName();
325 
326  // data types
327  if (operation == "property" || operation == "p") {
328  string property_name = element->GetDataLine();
329 
330  if (var && simgear::strutils::strip(property_name) == "#")
331  Parameters.push_back(var);
332  else {
333  if (property_name.find("#") != string::npos) {
334  if (is_number(Prefix)) {
335  property_name = replace(property_name,"#",Prefix);
336  }
337  else {
338  cerr << element->ReadFrom()
339  << fgred << "Illegal use of the special character '#'"
340  << reset << endl;
341  throw("Fatal Error.");
342  }
343  }
344 
345  if (element->HasAttribute("apply")) {
346  string function_str = element->GetAttributeValue("apply");
347  auto f = fdmex->GetTemplateFunc(function_str);
348  if (f)
349  Parameters.push_back(new FGFunctionValue(property_name,
350  PropertyManager, f, element));
351  else {
352  cerr << element->ReadFrom()
353  << fgred << highint << " No function by the name "
354  << function_str << " has been defined. This property will "
355  << "not be logged. You should check your configuration file."
356  << reset << endl;
357  }
358  }
359  else
360  Parameters.push_back(new FGPropertyValue(property_name,
361  PropertyManager, element));
362  }
363  } else if (operation == "value" || operation == "v") {
364  Parameters.push_back(new FGRealValue(element->GetDataAsNumber()));
365  } else if (operation == "pi") {
366  Parameters.push_back(new FGRealValue(M_PI));
367  } else if (operation == "table" || operation == "t") {
368  string call_type = element->GetAttributeValue("type");
369  if (call_type == "internal") {
370  std::cerr << el->ReadFrom()
371  << "An internal table cannot be nested within a function."
372  << endl;
373  throw BaseException("An internal table cannot be nested within a function.");
374  }
375  Parameters.push_back(new FGTable(PropertyManager, element, Prefix));
376  // operations
377  } else if (operation == "product") {
378  auto f = [](const decltype(Parameters)& Parameters)->double {
379  double temp = 1.0;
380 
381  for (auto p: Parameters)
382  temp *= p->GetValue();
383 
384  return temp;
385  };
386  Parameters.push_back(VarArgsFn<decltype(f)>(f, fdmex, element, Prefix, var));
387  } else if (operation == "sum") {
388  Parameters.push_back(VarArgsFn<decltype(sum)>(sum, fdmex, element, Prefix, var));
389  } else if (operation == "avg") {
390  auto avg = [&](const decltype(Parameters)& p)->double {
391  return sum(p) / p.size();
392  };
393  Parameters.push_back(VarArgsFn<decltype(avg)>(avg, fdmex, element, Prefix, var));
394  } else if (operation == "difference") {
395  auto f = [](const decltype(Parameters)& Parameters)->double {
396  double temp = Parameters[0]->GetValue();
397 
398  for (auto p = Parameters.begin()+1; p != Parameters.end(); ++p)
399  temp -= (*p)->GetValue();
400 
401  return temp;
402  };
403  Parameters.push_back(VarArgsFn<decltype(f)>(f, fdmex, element, Prefix, var));
404  } else if (operation == "min") {
405  auto f = [](const decltype(Parameters)& Parameters)->double {
406  double _min = HUGE_VAL;
407 
408  for (auto p : Parameters) {
409  double x = p->GetValue();
410  if (x < _min)
411  _min = x;
412  }
413 
414  return _min;
415  };
416  Parameters.push_back(VarArgsFn<decltype(f)>(f, fdmex, element, Prefix, var));
417  } else if (operation == "max") {
418  auto f = [](const decltype(Parameters)& Parameters)->double {
419  double _max = -HUGE_VAL;
420 
421  for (auto p : Parameters) {
422  double x = p->GetValue();
423  if (x > _max)
424  _max = x;
425  }
426 
427  return _max;
428  };
429  Parameters.push_back(VarArgsFn<decltype(f)>(f, fdmex, element, Prefix, var));
430  } else if (operation == "and") {
431  string ctxMsg = element->ReadFrom();
432  auto f = [ctxMsg](const decltype(Parameters)& Parameters)->double {
433  for (auto p : Parameters) {
434  if (!GetBinary(p->GetValue(), ctxMsg)) // As soon as one parameter is false, the expression is guaranteed to be false.
435  return 0.0;
436  }
437 
438  return 1.0;
439  };
440  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix,
441  var, MaxArgs));
442  } else if (operation == "or") {
443  string ctxMsg = element->ReadFrom();
444  auto f = [ctxMsg](const decltype(Parameters)& Parameters)->double {
445  for (auto p : Parameters) {
446  if (GetBinary(p->GetValue(), ctxMsg)) // As soon as one parameter is true, the expression is guaranteed to be true.
447  return 1.0;
448  }
449 
450  return 0.0;
451  };
452  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix,
453  var, MaxArgs));
454  } else if (operation == "quotient") {
455  auto f = [](const decltype(Parameters)& p)->double {
456  double y = p[1]->GetValue();
457  return y != 0.0 ? p[0]->GetValue()/y : HUGE_VAL;
458  };
459  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
460  } else if (operation == "pow") {
461  auto f = [](const decltype(Parameters)& p)->double {
462  return pow(p[0]->GetValue(), p[1]->GetValue());
463  };
464  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
465  } else if (operation == "toradians") {
466  auto f = [](const decltype(Parameters)& p)->double {
467  return p[0]->GetValue()*M_PI/180.;
468  };
469  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
470  } else if (operation == "todegrees") {
471  auto f = [](const decltype(Parameters)& p)->double {
472  return p[0]->GetValue()*180./M_PI;
473  };
474  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
475  } else if (operation == "sqrt") {
476  auto f = [](const decltype(Parameters)& p)->double {
477  double x = p[0]->GetValue();
478  return x >= 0.0 ? sqrt(x) : -HUGE_VAL;
479  };
480  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
481  } else if (operation == "log2") {
482  auto f = [](const decltype(Parameters)& p)->double {
483  double x = p[0]->GetValue();
484  return x > 0.0 ? log10(x)*invlog2val : -HUGE_VAL;
485  };
486  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
487  } else if (operation == "ln") {
488  auto f = [](const decltype(Parameters)& p)->double {
489  double x = p[0]->GetValue();
490  return x > 0.0 ? log(x) : -HUGE_VAL;
491  };
492  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
493  } else if (operation == "log10") {
494  auto f = [](const decltype(Parameters)& p)->double {
495  double x = p[0]->GetValue();
496  return x > 0.0 ? log10(x) : -HUGE_VAL;
497  };
498  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
499  } else if (operation == "sign") {
500  auto f = [](const decltype(Parameters)& p)->double {
501  return p[0]->GetValue() < 0.0 ? -1 : 1; // 0.0 counts as positive.
502  };
503  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
504  } else if (operation == "exp") {
505  Parameters.push_back(make_MathFn(exp, fdmex, element, Prefix, var));
506  } else if (operation == "abs") {
507  Parameters.push_back(make_MathFn(fabs, fdmex, element, Prefix, var));
508  } else if (operation == "sin") {
509  Parameters.push_back(make_MathFn(sin, fdmex, element, Prefix, var));
510  } else if (operation == "cos") {
511  Parameters.push_back(make_MathFn(cos, fdmex, element, Prefix, var));
512  } else if (operation == "tan") {
513  Parameters.push_back(make_MathFn(tan, fdmex, element, Prefix, var));
514  } else if (operation == "asin") {
515  Parameters.push_back(make_MathFn(asin, fdmex, element, Prefix, var));
516  } else if (operation == "acos") {
517  Parameters.push_back(make_MathFn(acos, fdmex, element, Prefix, var));
518  } else if (operation == "atan") {
519  Parameters.push_back(make_MathFn(atan, fdmex, element, Prefix, var));
520  } else if (operation == "floor") {
521  Parameters.push_back(make_MathFn(floor, fdmex, element, Prefix, var));
522  } else if (operation == "ceil") {
523  Parameters.push_back(make_MathFn(ceil, fdmex, element, Prefix, var));
524  } else if (operation == "fmod") {
525  auto f = [](const decltype(Parameters)& p)->double {
526  double y = p[1]->GetValue();
527  return y != 0.0 ? fmod(p[0]->GetValue(), y) : HUGE_VAL;
528  };
529  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
530  } else if (operation == "atan2") {
531  auto f = [](const decltype(Parameters)& p)->double {
532  return atan2(p[0]->GetValue(), p[1]->GetValue());
533  };
534  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
535  } else if (operation == "mod") {
536  auto f = [](const decltype(Parameters)& p)->double {
537  return static_cast<int>(p[0]->GetValue()) % static_cast<int>(p[1]->GetValue());
538  };
539  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
540  } else if (operation == "fraction") {
541  auto f = [](const decltype(Parameters)& p)->double {
542  double scratch;
543  return modf(p[0]->GetValue(), &scratch);
544  };
545  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
546  } else if (operation == "integer") {
547  auto f = [](const decltype(Parameters)& p)->double {
548  double result;
549  modf(p[0]->GetValue(), &result);
550  return result;
551  };
552  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
553  } else if (operation == "lt") {
554  auto f = [](const decltype(Parameters)& p)->double {
555  return p[0]->GetValue() < p[1]->GetValue() ? 1.0 : 0.0;
556  };
557  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
558  } else if (operation == "le") {
559  auto f = [](const decltype(Parameters)& p)->double {
560  return p[0]->GetValue() <= p[1]->GetValue() ? 1.0 : 0.0;
561  };
562  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
563  } else if (operation == "gt") {
564  auto f = [](const decltype(Parameters)& p)->double {
565  return p[0]->GetValue() > p[1]->GetValue() ? 1.0 : 0.0;
566  };
567  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
568  } else if (operation == "ge") {
569  auto f = [](const decltype(Parameters)& p)->double {
570  return p[0]->GetValue() >= p[1]->GetValue() ? 1.0 : 0.0;
571  };
572  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
573  } else if (operation == "eq") {
574  auto f = [](const decltype(Parameters)& p)->double {
575  return p[0]->GetValue() == p[1]->GetValue() ? 1.0 : 0.0;
576  };
577  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
578  } else if (operation == "nq") {
579  auto f = [](const decltype(Parameters)& p)->double {
580  return p[0]->GetValue() != p[1]->GetValue() ? 1.0 : 0.0;
581  };
582  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix, var));
583  } else if (operation == "not") {
584  string ctxMsg = element->ReadFrom();
585  auto f = [ctxMsg](const decltype(Parameters)& p)->double {
586  return GetBinary(p[0]->GetValue(), ctxMsg) ? 0.0 : 1.0;
587  };
588  Parameters.push_back(new aFunc<decltype(f), 1>(f, fdmex, element, Prefix, var));
589  } else if (operation == "ifthen") {
590  string ctxMsg = element->ReadFrom();
591  auto f = [ctxMsg](const decltype(Parameters)& p)->double {
592  if (GetBinary(p[0]->GetValue(), ctxMsg))
593  return p[1]->GetValue();
594  else
595  return p[2]->GetValue();
596  };
597  Parameters.push_back(new aFunc<decltype(f), 3>(f, fdmex, element, Prefix, var));
598  } else if (operation == "random") {
599  double mean = 0.0;
600  double stddev = 1.0;
601  string mean_attr = element->GetAttributeValue("mean");
602  string stddev_attr = element->GetAttributeValue("stddev");
603  if (!mean_attr.empty()) {
604  if (is_number(trim(mean_attr)))
605  mean = atof_locale_c(mean_attr);
606  else {
607  cerr << element->ReadFrom()
608  << "Expecting a number, but got: " << mean_attr <<endl;
609  throw BaseException("Invalid number");
610  }
611  }
612  if (!stddev_attr.empty()) {
613  if (is_number(trim(stddev_attr)))
614  stddev = atof_locale_c(stddev_attr);
615  else {
616  cerr << element->ReadFrom()
617  << "Expecting a number, but got: " << stddev_attr <<endl;
618  throw BaseException("Invalid number");
619  }
620  }
621  auto generator(makeRandomGenerator(element, fdmex));
622  auto f = [generator, mean, stddev]()->double {
623  double value = generator->GetNormalRandomNumber();
624  return value*stddev + mean;
625  };
626  Parameters.push_back(new aFunc<decltype(f), 0>(f, PropertyManager, element,
627  Prefix));
628  } else if (operation == "urandom") {
629  double lower = -1.0;
630  double upper = 1.0;
631  string lower_attr = element->GetAttributeValue("lower");
632  string upper_attr = element->GetAttributeValue("upper");
633  if (!lower_attr.empty()) {
634  if (is_number(trim(lower_attr)))
635  lower = atof_locale_c(lower_attr);
636  else {
637  cerr << element->ReadFrom()
638  << "Expecting a number, but got: " << lower_attr <<endl;
639  throw BaseException("Invalid number");
640  }
641  }
642  if (!upper_attr.empty()) {
643  if (is_number(trim(upper_attr)))
644  upper = atof_locale_c(upper_attr);
645  else {
646  cerr << element->ReadFrom()
647  << "Expecting a number, but got: " << upper_attr <<endl;
648  throw BaseException("Invalid number");
649  }
650  }
651  auto generator(makeRandomGenerator(element, fdmex));
652  double a = 0.5*(upper-lower);
653  double b = 0.5*(upper+lower);
654  auto f = [generator, a, b]()->double {
655  double value = generator->GetUniformRandomNumber();
656  return value*a + b;
657  };
658  Parameters.push_back(new aFunc<decltype(f), 0>(f, PropertyManager, element,
659  Prefix));
660  } else if (operation == "switch") {
661  string ctxMsg = element->ReadFrom();
662  auto f = [ctxMsg](const decltype(Parameters)& p)->double {
663  double temp = p[0]->GetValue();
664  if (temp < 0.0) {
665  cerr << ctxMsg << fgred << highint
666  << "The switch function index (" << temp
667  << ") is negative." << reset << endl;
668  throw("Fatal error");
669  }
670  size_t n = p.size()-1;
671  size_t i = static_cast<size_t>(temp+0.5);
672 
673  if (i < n)
674  return p[i+1]->GetValue();
675  else {
676  cerr << ctxMsg << fgred << highint
677  << "The switch function index (" << temp
678  << ") selected a value above the range of supplied values"
679  << "[0:" << n-1 << "]"
680  << " - not enough values were supplied." << reset << endl;
681  throw("Fatal error");
682  }
683  };
684  Parameters.push_back(new aFunc<decltype(f), 2>(f, fdmex, element, Prefix,
685  var, MaxArgs));
686  } else if (operation == "interpolate1d") {
687  auto f = [](const decltype(Parameters)& p)->double {
688  // This is using the bisection algorithm. Special care has been
689  // taken to evaluate each parameter only once.
690  size_t n = p.size();
691  double x = p[0]->GetValue();
692  double xmin = p[1]->GetValue();
693  double ymin = p[2]->GetValue();
694  if (x <= xmin) return ymin;
695 
696  double xmax = p[n-2]->GetValue();
697  double ymax = p[n-1]->GetValue();
698  if (x >= xmax) return ymax;
699 
700  size_t nmin = 0;
701  size_t nmax = (n-3)/2;
702  while (nmax-nmin > 1) {
703  size_t m = (nmax-nmin)/2+nmin;
704  double xm = p[2*m+1]->GetValue();
705  double ym = p[2*m+2]->GetValue();
706  if (x < xm) {
707  xmax = xm;
708  ymax = ym;
709  nmax= m;
710  } else if (x > xm) {
711  xmin = xm;
712  ymin = ym;
713  nmin = m;
714  }
715  else
716  return ym;
717  }
718 
719  return ymin + (x-xmin)*(ymax-ymin)/(xmax-xmin);
720  };
721  Parameters.push_back(new aFunc<decltype(f), 5>(f, fdmex, element, Prefix,
722  var, MaxArgs, OddEven::Odd));
723  } else if (operation == "rotation_alpha_local") {
724  // Calculates local angle of attack for skydiver body component.
725  // Euler angles from the intermediate body frame to the local body frame
726  // must be from a z-y-x axis rotation order
727  auto f = [](const decltype(Parameters)& p)->double {
728  double alpha = p[0]->GetValue()*degtorad; //angle of attack of intermediate body frame
729  double beta = p[1]->GetValue()*degtorad; //sideslip angle of intermediate body frame
730  double phi = p[3]->GetValue()*degtorad; //x-axis Euler angle from the intermediate body frame to the local body frame
731  double theta = p[4]->GetValue()*degtorad; //y-axis Euler angle from the intermediate body frame to the local body frame
732  double psi = p[5]->GetValue()*degtorad; //z-axis Euler angle from the intermediate body frame to the local body frame
733 
734  FGQuaternion qTb2l(phi, theta, psi);
735  double cos_beta = cos(beta);
736  FGColumnVector3 wind_body(cos(alpha)*cos_beta, sin(beta),
737  sin(alpha)*cos_beta);
738  FGColumnVector3 wind_local = qTb2l.GetT()*wind_body;
739 
740  if (fabs(fabs(wind_local(eY)) - 1.0) < 1E-9)
741  return 0.0;
742  else
743  return atan2(wind_local(eZ), wind_local(eX))*radtodeg;
744  };
745  Parameters.push_back(new aFunc<decltype(f), 6>(f, fdmex, element, Prefix, var));
746  } else if (operation == "rotation_beta_local") {
747  // Calculates local angle of sideslip for skydiver body component.
748  // Euler angles from the intermediate body frame to the local body frame
749  // must be from a z-y-x axis rotation order
750  auto f = [](const decltype(Parameters)& p)->double {
751  double alpha = p[0]->GetValue()*degtorad; //angle of attack of intermediate body frame
752  double beta = p[1]->GetValue()*degtorad; //sideslip angle of intermediate body frame
753  double phi = p[3]->GetValue()*degtorad; //x-axis Euler angle from the intermediate body frame to the local body frame
754  double theta = p[4]->GetValue()*degtorad; //y-axis Euler angle from the intermediate body frame to the local body frame
755  double psi = p[5]->GetValue()*degtorad; //z-axis Euler angle from the intermediate body frame to the local body frame
756  FGQuaternion qTb2l(phi, theta, psi);
757  double cos_beta = cos(beta);
758  FGColumnVector3 wind_body(cos(alpha)*cos_beta, sin(beta),
759  sin(alpha)*cos_beta);
760  FGColumnVector3 wind_local = qTb2l.GetT()*wind_body;
761 
762  if (fabs(fabs(wind_local(eY)) - 1.0) < 1E-9)
763  return wind_local(eY) > 0.0 ? 0.5*M_PI : -0.5*M_PI;
764 
765  double alpha_local = atan2(wind_local(eZ), wind_local(eX));
766  double cosa = cos(alpha_local);
767  double sina = sin(alpha_local);
768  double cosb;
769 
770  if (fabs(cosa) > fabs(sina))
771  cosb = wind_local(eX) / cosa;
772  else
773  cosb = wind_local(eZ) / sina;
774 
775  return atan2(wind_local(eY), cosb)*radtodeg;
776  };
777  Parameters.push_back(new aFunc<decltype(f), 6>(f, fdmex, element, Prefix, var));
778  } else if (operation == "rotation_gamma_local") {
779  // Calculates local roll angle for skydiver body component.
780  // Euler angles from the intermediate body frame to the local body frame
781  // must be from a z-y-x axis rotation order
782  auto f = [](const decltype(Parameters)& p)->double {
783  double alpha = p[0]->GetValue()*degtorad; //angle of attack of intermediate body frame
784  double beta = p[1]->GetValue()*degtorad; //sideslip angle of intermediate body frame
785  double gamma = p[2]->GetValue()*degtorad; //roll angle of intermediate body frame
786  double phi = p[3]->GetValue()*degtorad; //x-axis Euler angle from the intermediate body frame to the local body frame
787  double theta = p[4]->GetValue()*degtorad; //y-axis Euler angle from the intermediate body frame to the local body frame
788  double psi = p[5]->GetValue()*degtorad; //z-axis Euler angle from the intermediate body frame to the local body frame
789  double cos_alpha = cos(alpha), sin_alpha = sin(alpha);
790  double cos_beta = cos(beta), sin_beta = sin(beta);
791  double cos_gamma = cos(gamma), sin_gamma = sin(gamma);
792  FGQuaternion qTb2l(phi, theta, psi);
793  FGColumnVector3 wind_body_X(cos_alpha*cos_beta, sin_beta,
794  sin_alpha*cos_beta);
795  FGColumnVector3 wind_body_Y(-sin_alpha*sin_gamma-sin_beta*cos_alpha*cos_gamma,
796  cos_beta*cos_gamma,
797  -sin_alpha*sin_beta*cos_gamma+sin_gamma*cos_alpha);
798  FGColumnVector3 wind_local_X = qTb2l.GetT()*wind_body_X;
799  FGColumnVector3 wind_local_Y = qTb2l.GetT()*wind_body_Y;
800  double cosacosb = wind_local_X(eX);
801  double sinb = wind_local_X(eY);
802  double sinacosb = wind_local_X(eZ);
803  double sinc, cosc;
804 
805  if (fabs(sinb) < 1E-9) { // cos(beta_local) == 1.0
806  cosc = wind_local_Y(eY);
807 
808  if (fabs(cosacosb) > fabs(sinacosb))
809  sinc = wind_local_Y(eZ) / cosacosb;
810  else
811  sinc = -wind_local_Y(eX) / sinacosb;
812  }
813  else if (fabs(fabs(sinb)-1.0) < 1E-9) { // cos(beta_local) == 0.0
814  sinc = wind_local_Y(eZ);
815  cosc = -wind_local_Y(eX);
816  }
817  else {
818  sinc = cosacosb*wind_local_Y(eZ)-sinacosb*wind_local_Y(eX);
819  cosc = (-sinacosb*wind_local_Y(eZ)-cosacosb*wind_local_Y(eX))/sinb;
820  }
821 
822  return atan2(sinc, cosc)*radtodeg;
823  };
824  Parameters.push_back(new aFunc<decltype(f), 6>(f, fdmex, element, Prefix, var));
825  } else if (operation == "rotation_bf_to_wf") {
826  // Transforms the input vector from a body frame to a wind frame. The
827  // origin of the vector remains the same.
828  string ctxMsg = element->ReadFrom();
829  auto f = [ctxMsg](const decltype(Parameters)& p)->double {
830  double rx = p[0]->GetValue(); //x component of input vector
831  double ry = p[1]->GetValue(); //y component of input vector
832  double rz = p[2]->GetValue(); //z component of input vector
833  double alpha = p[3]->GetValue()*degtorad; //angle of attack of the body frame
834  double beta = p[4]->GetValue()*degtorad; //sideslip angle of the body frame
835  double gamma = p[5]->GetValue()*degtorad; //roll angle of the body frame
836  int idx = static_cast<int>(p[6]->GetValue());
837 
838  if ((idx < 1) || (idx > 3)) {
839  cerr << ctxMsg << fgred << highint
840  << "The index must be one of the integer value 1, 2 or 3."
841  << reset << endl;
842  throw("Fatal error");
843  }
844 
845  FGQuaternion qa(eY, -alpha), qb(eZ, beta), qc(eX, -gamma);
846  FGMatrix33 mT = (qa*qb*qc).GetT();
847  FGColumnVector3 r0(rx, ry, rz);
848  FGColumnVector3 r = mT*r0;
849 
850  return r(idx);
851  };
852  Parameters.push_back(new aFunc<decltype(f), 7>(f, fdmex, element, Prefix, var));
853  } else if (operation == "rotation_wf_to_bf") {
854  // Transforms the input vector from q wind frame to a body frame. The
855  // origin of the vector remains the same.
856  string ctxMsg = element->ReadFrom();
857  auto f = [ctxMsg](const decltype(Parameters)& p)->double {
858  double rx = p[0]->GetValue(); //x component of input vector
859  double ry = p[1]->GetValue(); //y component of input vector
860  double rz = p[2]->GetValue(); //z component of input vector
861  double alpha = p[3]->GetValue()*degtorad; //angle of attack of the body frame
862  double beta = p[4]->GetValue()*degtorad; //sideslip angle of the body frame
863  double gamma = p[5]->GetValue()*degtorad; //roll angle of the body frame
864  int idx = static_cast<int>(p[6]->GetValue());
865 
866  if ((idx < 1) || (idx > 3)) {
867  cerr << ctxMsg << fgred << highint
868  << "The index must be one of the integer value 1, 2 or 3."
869  << reset << endl;
870  throw("Fatal error");
871  }
872 
873  FGQuaternion qa(eY, -alpha), qb(eZ, beta), qc(eX, -gamma);
874  FGMatrix33 mT = (qa*qb*qc).GetT();
875  FGColumnVector3 r0(rx, ry, rz);
876  mT.T();
877  FGColumnVector3 r = mT*r0;
878 
879  return r(idx);
880  };
881  Parameters.push_back(new aFunc<decltype(f), 7>(f, fdmex, element, Prefix, var));
882  } else if (operation != "description") {
883  cerr << element->ReadFrom() << fgred << highint
884  << "Bad operation <" << operation
885  << "> detected in configuration file" << reset << endl;
886  }
887 
888  // Optimize functions applied on constant parameters by replacing them by
889  // their constant result.
890  if (!Parameters.empty()){
891  FGFunction* p = dynamic_cast<FGFunction*>(Parameters.back().ptr());
892 
893  if (p && p->IsConstant()) {
894  double constant = p->GetValue();
895  FGPropertyNode_ptr node = p->pNode;
896  string pName = p->GetName();
897 
898  Parameters.pop_back();
899  Parameters.push_back(new FGRealValue(constant));
900  if (debug_lvl > 0)
901  cout << element->ReadFrom() << fggreen << highint
902  << "<" << operation << "> is applied on constant parameters."
903  << endl << "It will be replaced by its result ("
904  << constant << ")";
905 
906  if (node) {
907  node->setDoubleValue(constant);
908  node->setAttribute(SGPropertyNode::WRITE, false);
909  if (debug_lvl > 0)
910  cout << " and the property " << pName
911  << " will be unbound and made read only.";
912  }
913  cout << reset << endl << endl;
914  }
915  }
916  element = el->GetNextElement();
917  }
918 
919  bind(el, Prefix); // Allow any function to save its value
920 
921  Debug(0);
922 }
923 
924 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
925 
927 {
928  if (pNode && pNode->isTied())
929  PropertyManager->Untie(pNode);
930 
931  Debug(1);
932 }
933 
934 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
935 
936 bool FGFunction::IsConstant(void) const
937 {
938  for (auto p: Parameters) {
939  if (!p->IsConstant())
940  return false;
941  }
942 
943  return true;
944 }
945 
946 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
947 
948 void FGFunction::cacheValue(bool cache)
949 {
950  cached = false; // Must set cached to false prior to calling GetValue(), else
951  // it will _never_ calculate the value;
952  if (cache) {
953  cachedValue = GetValue();
954  cached = true;
955  }
956 }
957 
958 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
959 
960 double FGFunction::GetValue(void) const
961 {
962  if (cached) return cachedValue;
963 
964  double val = Parameters[0]->GetValue();
965 
966  if (pCopyTo) pCopyTo->setDoubleValue(val);
967 
968  return val;
969 }
970 
971 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
972 
974 {
975  ostringstream buffer;
976 
977  buffer << setw(9) << setprecision(6) << GetValue();
978  return buffer.str();
979 }
980 
981 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
982 
983 string FGFunction::CreateOutputNode(Element* el, const string& Prefix)
984 {
985  string nName;
986 
987  if ( !Name.empty() ) {
988  if (Prefix.empty())
989  nName = PropertyManager->mkPropertyName(Name, false);
990  else {
991  if (is_number(Prefix)) {
992  if (Name.find("#") != string::npos) { // if "#" is found
993  Name = replace(Name,"#",Prefix);
994  nName = PropertyManager->mkPropertyName(Name, false);
995  } else {
996  cerr << el->ReadFrom()
997  << "Malformed function name with number: " << Prefix
998  << " and property name: " << Name
999  << " but no \"#\" sign for substitution." << endl;
1000  }
1001  } else {
1002  nName = PropertyManager->mkPropertyName(Prefix + "/" + Name, false);
1003  }
1004  }
1005 
1006  pNode = PropertyManager->GetNode(nName, true);
1007  if (pNode->isTied()) {
1008  cerr << el->ReadFrom()
1009  << "Property " << nName << " has already been successfully bound (late)." << endl;
1010  throw("Failed to bind the property to an existing already tied node.");
1011  }
1012  }
1013 
1014  return nName;
1015 }
1016 
1017 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1018 
1019 void FGFunction::bind(Element* el, const string& Prefix)
1020 {
1021  string nName = CreateOutputNode(el, Prefix);
1022 
1023  if (!nName.empty())
1024  PropertyManager->Tie(nName, this, &FGFunction::GetValue);
1025 }
1026 
1027 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1028 // The bitmasked value choices are as follows:
1029 // unset: In this case (the default) JSBSim would only print
1030 // out the normally expected messages, essentially echoing
1031 // the config files as they are read. If the environment
1032 // variable is not set, debug_lvl is set to 1 internally
1033 // 0: This requests JSBSim not to output any messages
1034 // whatsoever.
1035 // 1: This value explicity requests the normal JSBSim
1036 // startup messages
1037 // 2: This value asks for a message to be printed out when
1038 // a class is instantiated
1039 // 4: When this value is set, a message is displayed when a
1040 // FGModel object executes its Run() method
1041 // 8: When this value is set, various runtime state variables
1042 // are printed out periodically
1043 // 16: When set various parameters are sanity checked and
1044 // a message is printed out when they go out of bounds
1045 
1046 void FGFunction::Debug(int from)
1047 {
1048  if (debug_lvl <= 0) return;
1049 
1050  if (debug_lvl & 1) { // Standard console startup message output
1051  if (from == 0) { // Constructor
1052  if (!Name.empty())
1053  cout << " Function: " << Name << endl;
1054  }
1055  }
1056  if (debug_lvl & 2 ) { // Instantiation/Destruction notification
1057  if (from == 0) cout << "Instantiated: FGFunction" << endl;
1058  if (from == 1) cout << "Destroyed: FGFunction" << endl;
1059  }
1060  if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
1061  }
1062  if (debug_lvl & 8 ) { // Runtime state variables
1063  }
1064  if (debug_lvl & 16) { // Sanity checking
1065  }
1066  if (debug_lvl & 64) {
1067  if (from == 0) { // Constructor
1068  }
1069  }
1070 }
1071 
1072 }
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
unsigned int GetNumElements(void)
Returns the number of child elements for this element.
Definition: FGXMLElement.h:192
std::string ReadFrom(void) const
Return a string that contains a description of the location where the current XML element was read fr...
const std::string & GetName(void) const
Retrieves the element name.
Definition: FGXMLElement.h:179
Encapsulates the JSBSim simulation executive.
Definition: FGFDMExec.h:184
std::shared_ptr< FGPropertyManager > GetPropertyManager(void) const
Returns a pointer to the property manager object.
Definition: FGFDMExec.h:421
Represents a mathematical function.
Definition: FGFunction.h:755
FGFunction()
Default constructor.
Definition: FGFunction.h:758
double GetValue(void) const override
Retrieves the value of the function object.
Definition: FGFunction.cpp:960
bool IsConstant(void) const override
Does the function always return the same result (i.e.
Definition: FGFunction.cpp:936
void cacheValue(bool shouldCache)
Specifies whether to cache the value of the function, so it is calculated only once per frame.
Definition: FGFunction.cpp:948
std::string GetValueAsString(void) const
The value that the function evaluates to, as a string.
Definition: FGFunction.cpp:973
~FGFunction(void) override
Destructor Make sure the function is untied before destruction.
Definition: FGFunction.cpp:926
static char fggreen[6]
green text
Definition: FGJSBBase.h:169
static char fgred[6]
red text
Definition: FGJSBBase.h:167
static char reset[5]
resets text properties
Definition: FGJSBBase.h:157
static char highint[5]
highlights text
Definition: FGJSBBase.h:151
Represents various types of parameters.
Definition: FGParameter.h:61
Represents a property value which can use late binding.