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