JSBSim Flight Dynamics Model 1.3.1 (17 May 2026)
An Open Source Flight Dynamics and Control Software Library in C++
Loading...
Searching...
No Matches
FGTable.cpp
1/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2
3 Module: FGTable.cpp
4 Author: Jon S. Berndt
5 Date started: 1/9/2001
6 Purpose: Models a lookup table
7
8 ------------- Copyright (C) 2001 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
27FUNCTIONAL DESCRIPTION
28--------------------------------------------------------------------------------
29Models a lookup table
30
31HISTORY
32--------------------------------------------------------------------------------
33JSB 1/9/00 Created
34ADM 2026/04/17 Added support for 4D and higher tables.
35
36%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37INCLUDES
38%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
39
40#include <assert.h>
41#include <limits>
42#include <optional>
43
44#include "FGTable.h"
45#include "input_output/FGXMLElement.h"
46#include "input_output/string_utilities.h"
47
48using namespace std;
49
50namespace JSBSim {
51
52namespace { // anonymous namespace for helper functions
53
54unsigned int FindNumColumns(const string& test_line)
55{
56 // determine number of data columns in table (first column is row lookup - don't count)
57 size_t position=0;
58 unsigned int nCols=0;
59 while ((position = test_line.find_first_not_of(" \t", position)) != string::npos) {
60 nCols++;
61 position = test_line.find_first_of(" \t", position);
62 }
63 return nCols;
64}
65
66unsigned int InferLeafDimension(Element* tableData)
67{
68 const unsigned int nLines = tableData->GetNumDataLines();
69 if (nLines == 0u) {
70 XMLLogException err(tableData);
71 err << "<tableData> is empty.\n";
72 throw err;
73 }
74
75 const unsigned int firstLineColumns = FindNumColumns(tableData->GetDataLine(0));
76
77 if (nLines == 1u) {
78 if (firstLineColumns != 2u) {
79 XMLLogException err(tableData);
80 err << "Too many columns for a 1D table\n";
81 throw err;
82 }
83 return 1u;
84 }
85
86 const unsigned int secondLineColumns = FindNumColumns(tableData->GetDataLine(1));
87 if (secondLineColumns == firstLineColumns + 1u) {
88 for(unsigned int i=1; i<nLines; ++i) {
89 if (FindNumColumns(tableData->GetDataLine(i)) != secondLineColumns) {
90 XMLLogException err(tableData);
91 err << "Invalid number of columns in line "
92 << tableData->GetLineNumber()+i << "\n";
93 throw err;
94 }
95 }
96 return 2u;
97 }
98
99 if (firstLineColumns != 2u) {
100 XMLLogException err(tableData);
101 err << "Too many columns for a 1D table\n";
102 throw err;
103 }
104
105 for(unsigned int i=1; i<nLines; ++i) {
106 if (FindNumColumns(tableData->GetDataLine(i)) != 2u) {
107 XMLLogException err(tableData);
108 err << "Invalid number of columns in line "
109 << tableData->GetLineNumber()+i << "\n";
110 throw err;
111 }
112 }
113
114 return 1u;
115}
116
117std::optional<unsigned int> ParseLookupAxis(const string& lookup_axis)
118{
119 if (lookup_axis.empty() || lookup_axis == "row" || lookup_axis == "axis1")
120 return 0u;
121 if (lookup_axis == "column" || lookup_axis == "axis2")
122 return 1u;
123 if (lookup_axis == "table" || lookup_axis == "axis3")
124 return 2u;
125
126 try {
127 if (lookup_axis.rfind("axis", 0) == 0) {
128 const string suffix = lookup_axis.substr(4);
129 const unsigned long axis = std::stoul(suffix);
130 if (axis >= 1ul)
131 return static_cast<unsigned int>(axis - 1ul);
132 }
133 } catch (std::exception& e) {
134 FGLogging err(LogLevel::ERROR);
135 err << e.what() << "\n";
136 }
137
138 return std::nullopt;
139}
140
141string LookupAxisName(unsigned int axis)
142{
143 if (axis == 0u)
144 return "row";
145 if (axis == 1u)
146 return "column";
147 if (axis == 2u)
148 return "table";
149 return "axis" + std::to_string(axis + 1u);
150}
151
152void AppendNumericData(Element* tableData, stringstream& buf)
153{
154 for (unsigned int i=0; i<tableData->GetNumDataLines(); ++i) {
155 const string line = tableData->GetDataLine(i);
156 if (line.find_first_not_of("0123456789.-+eE \t\n") != string::npos) {
157 XMLLogException err(tableData);
158 err << " Illegal character found in line "
159 << tableData->GetLineNumber() + i + 1 << ": \n" << line << "\n";
160 throw err;
161 }
162 buf << line << " ";
163 }
164}
165
166} // anonymous namespace
167
168/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
169CLASS IMPLEMENTATION
170%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
171
172FGTable::FGTable(int NRows)
173 : nRows(NRows), nCols(1u), nDims(1u)
174{
175 Type = tt1D;
176 // Fill unused elements with NaNs to detect illegal access.
177 Data.push_back(std::numeric_limits<double>::quiet_NaN());
178 Data.push_back(std::numeric_limits<double>::quiet_NaN());
179 Debug(0);
180}
181
182//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
183
184FGTable::FGTable(int NRows, int NCols)
185 : nRows(NRows), nCols(NCols), nDims(2u)
186{
187 Type = tt2D;
188 // Fill unused elements with NaNs to detect illegal access.
189 Data.push_back(std::numeric_limits<double>::quiet_NaN());
190 Debug(0);
191}
192
193//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
194
195FGTable::FGTable(const FGTable& t)
196 : PropertyManager(t.PropertyManager)
197{
198 Type = t.Type;
199 nRows = t.nRows;
200 nCols = t.nCols;
201 nDims = t.nDims;
202 internal = t.internal;
203 Name = t.Name;
204 lookupProperty = t.lookupProperty;
205
206 // Deep copy of t.Tables
207 Tables.reserve(t.Tables.size());
208 for(const auto& table: t.Tables)
209 Tables.push_back(std::make_unique<FGTable>(*table));
210
211 lookupPropertyValues.resize(nDims);
212 Data = t.Data;
213}
214
215//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
216
217FGTable::FGTable(std::shared_ptr<FGPropertyManager> pm, Element* el,
218 const std::string& Prefix)
219 : PropertyManager(pm)
220{
221 // Is this an internal lookup table?
222
223 Name = el->GetAttributeValue("name"); // Allow this table to be named with a property
224 const string call_type = el->GetAttributeValue("type");
225 if (call_type == "internal") {
226 internal = true;
227 } else if (!call_type.empty()) {
228 XMLLogException err(el);
229 err << " An unknown table type attribute is listed: " << call_type << "\n";
230 throw err;
231 }
232
233 // Determine and store the lookup properties for this table unless this table
234 // is part of a higher-dimensional table, in which case its independentVar
235 // property indexes will be set by a call from the owning table during creation
236
237 unsigned int declared_dimension = 0u;
238
239 Element* axisElement = el->FindElement("independentVar");
240 if (axisElement) {
241
242 // The 'internal' attribute of the table element cannot be specified
243 // at the same time that independentVars are specified.
244 if (internal) {
245 FGXMLLogging log(el, LogLevel::ERROR);
246 log << LogFormat::RED << " This table specifies both 'internal' call type\n"
247 << " and specific lookup properties via the 'independentVar' element.\n"
248 << " These are mutually exclusive specifications. The 'internal'\n"
249 << " attribute will be ignored.\n" << LogFormat::DEFAULT;
250 internal = false;
251 }
252
253 while (axisElement) {
254 string property_string = axisElement->GetDataLine();
255 if (property_string.find("#") != string::npos && is_number(Prefix))
256 property_string = replace(property_string, "#", Prefix);
257
258 FGPropertyValue_ptr node = new FGPropertyValue(property_string,
259 PropertyManager, axisElement);
260
261 const std::string lookup_axis = axisElement->GetAttributeValue("lookup");
262 const std::optional<unsigned int> axis = ParseLookupAxis(lookup_axis);
263
264 if (!axis.has_value()) {
265 XMLLogException err(axisElement);
266 err << "FGTable: lookup axis specification not understood: " << lookup_axis <<"\n";
267 throw err;
268 }
269
270 if (HasLookupProperty(*axis)) {
271 XMLLogException err(axisElement);
272 err << "FGTable: duplicate lookup axis \"" << LookupAxisName(*axis) << "\"\n";
273 throw err;
274 }
275
276 SetLookupProperty(*axis, node);
277 declared_dimension = std::max(declared_dimension, *axis + 1u);
278 axisElement = el->FindNextElement("independentVar");
279 }
280
281 // Check that the lookup axes match the declared dimension of the table.
282 for (unsigned int axis=0u; axis<declared_dimension; ++axis) {
283 if (!HasLookupProperty(axis)) {
284 XMLLogException err(el);
285 err << "FGTable: missing lookup axis \"" << LookupAxisName(axis) << "\"\n";
286 throw err;
287 }
288 }
289
290 } else if (!internal && el->GetName() != "tableData") {
291 // no independentVars found, and table is not marked as internal, nor is it
292 // a nested sub-table
293 XMLLogException err(el);
294 err << "No independentVars found, and table is not marked as internal,"
295 << " nor is it a nested sub-table.\n";
296 throw err;
297 }
298 // end lookup property code
299
300 Element* leafData = nullptr;
301
302 const unsigned int nChildTableData = el->GetNumElements("tableData");
303
304 if (el->GetName() == "tableData" && nChildTableData == 0u) {
305 // This is a leaf <tableData> element with numeric content
306 leafData = el;
307 } else if (el->GetName() != "tableData" && nChildTableData == 1u) {
308 // 1D and 2D tables
309 leafData = el->FindElement("tableData");
310 } else if (nChildTableData > 1u) {
311 // N-dimensional table: multiple <tableData> children (3D+), or a container
312 // <tableData> with nested <tableData> children (4D+)
313 Type = ttND;
314 // Fill unused elements with NaNs to detect illegal access.
315 Data.push_back(std::numeric_limits<double>::quiet_NaN());
316 nCols = 1u;
317
318 Element* child = el->FindElement("tableData");
319
320 while (child) {
321 const string brkpt_string = child->GetAttributeValue("breakPoint");
322 if (brkpt_string.empty()) {
323 XMLLogException err(child);
324 err << "FGTable: missing breakPoint on <tableData>\n";
325 throw err;
326 }
327
328 auto subtable = std::make_unique<FGTable>(PropertyManager, child);
329
330 if (nDims == 0u) {
331 nDims = subtable->nDims + 1u;
332 if (nDims < 3u) {
333 XMLLogException err(child);
334 err << "FGTable: nested tables must contain at least 2D subtables\n";
335 throw err;
336 }
337 } else if (subtable->nDims + 1u != nDims) {
338 XMLLogException err(child);
339 err << "FGTable: inconsistent sub-table dimensionality in nested table\n";
340 throw err;
341 }
342
343 Data.push_back(child->GetAttributeValueAsNumber("breakPoint"));
344 Tables.push_back(std::move(subtable));
345 child = el->FindNextElement("tableData");
346 }
347
348 nRows = static_cast<unsigned int>(Tables.size());
349 } else {
350 XMLLogException err(el);
351 err << "FGTable: <tableData> elements are missing\n";
352 throw err;
353 }
354
355 if (leafData) {
356 stringstream buf;
357 AppendNumericData(leafData, buf);
358
359 nDims = InferLeafDimension(leafData);
360
361 switch (nDims) {
362 case 1u:
363 nRows = leafData->GetNumDataLines();
364 nCols = 1u;
365 Type = tt1D;
366 // Fill unused elements with NaNs to detect illegal access.
367 Data.push_back(std::numeric_limits<double>::quiet_NaN());
368 Data.push_back(std::numeric_limits<double>::quiet_NaN());
369 *this << buf;
370 break;
371 case 2u:
372 nRows = leafData->GetNumDataLines()-1u;
373 nCols = FindNumColumns(leafData->GetDataLine(0));
374 Type = tt2D;
375 // Fill unused elements with NaNs to detect illegal access.
376 Data.push_back(std::numeric_limits<double>::quiet_NaN());
377 *this << buf;
378 break;
379 default:
380 assert(false); // Should never be called
381 break;
382 }
383 }
384
385 if (declared_dimension != 0u && declared_dimension != nDims) {
386 XMLLogException err(el);
387 err << "FGTable: " << declared_dimension
388 << " lookup axes were declared, but the tableData nesting implies a "
389 << nDims << "D table.\n";
390 throw err;
391 }
392
393 Debug(0);
394
395 // Sanity checks: lookup indices must be increasing monotonically
396
397 // find next xml element containing a name attribute
398 // to indicate where the error occured
399 Element* nameel = el;
400 while (nameel && nameel->GetAttributeValue("name") == "")
401 nameel=nameel->GetParent();
402
403 // check breakpoints, if applicable
404 if (Type == ttND) {
405 for (unsigned int b=2; b<=Tables.size(); ++b) {
406 if (Data[b] <= Data[b-1]) {
407 XMLLogException err(el);
408 err << LogFormat::RED << LogFormat::BOLD
409 << " FGTable: breakpoint lookup is not monotonically increasing\n"
410 << " in breakpoint " << b;
411 if (nameel) err << " of table in " << nameel->GetAttributeValue("name");
412 err << ":\n" << LogFormat::RESET
413 << " " << Data[b] << "<=" << Data[b-1] << "\n";
414 throw err;
415 }
416 }
417 }
418
419 // check columns, if applicable
420 if (Type == tt2D) {
421 for (unsigned int c=2; c<=nCols; ++c) {
422 if (Data[c] <= Data[c-1]) {
423 XMLLogException err(el);
424 err << LogFormat::RED << LogFormat::BOLD
425 << " FGTable: column lookup is not monotonically increasing\n"
426 << " in column " << c;
427 if (nameel != 0) err << " of table in " << nameel->GetAttributeValue("name");
428 err << ":\n" << LogFormat::RESET
429 << " " << Data[c] << "<=" << Data[c-1] << "\n";
430 throw err;
431 }
432 }
433 }
434
435 // check rows
436 if (Type != ttND) { // in ND tables, check only rows of subtables
437 for (size_t r=2; r<=nRows; ++r) {
438 if (Data[r*(nCols+1)]<=Data[(r-1)*(nCols+1)]) {
439 XMLLogException err(el);
440 err << LogFormat::RED << LogFormat::BOLD
441 << " FGTable: row lookup is not monotonically increasing\n"
442 << " in row " << r;
443 if (nameel != 0) err << " of table in " << nameel->GetAttributeValue("name");
444 err << ":\n" << LogFormat::RESET
445 << " " << Data[r*(nCols+1)] << "<=" << Data[(r-1)*(nCols+1)] << "\n";
446 throw err;
447 }
448 }
449 }
450
451 // Check the table has been entirely populated.
452 switch (Type) {
453 case tt1D:
454 if (Data.size() != 2*nRows+2) missingData(el, 2*nRows, Data.size()-2);
455 break;
456 case tt2D:
457 if (Data.size() != static_cast<size_t>(nRows+1)*(nCols+1))
458 missingData(el, (nRows+1)*(nCols+1)-1, Data.size()-1);
459 break;
460 case ttND:
461 if (Data.size() != nRows+1) missingData(el, nRows, Data.size()-1);
462 break;
463 default:
464 assert(false); // Should never be called
465 break;
466 }
467
468 lookupPropertyValues.resize(nDims);
469
470 bind(el, Prefix);
471
472 if (debug_lvl & 1) Print();
473}
474
475//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
476
477void FGTable::missingData(Element* el, unsigned int expected_size, size_t actual_size)
478{
479 XMLLogException err(el);
480 err << LogFormat::RED << LogFormat::BOLD
481 << " FGTable: Missing data";
482 if (!Name.empty()) err << " in table " << Name;
483 err << ":\n" << LogFormat::RESET
484 << " Expecting " << expected_size << " elements while "
485 << actual_size << " elements were provided.\n";
486 throw err;
487}
488
489//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
490
492{
493 // Untie the bound property so that it makes no further reference to this
494 // instance of FGTable after the destruction is completed.
495 if (!Name.empty() && !internal) {
496 string tmp = PropertyManager->mkPropertyName(Name, false);
497 SGPropertyNode* node = PropertyManager->GetNode(tmp);
498 if (node && node->isTied())
499 PropertyManager->Untie(node);
500 }
501
502 Debug(1);
503}
504
505//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
506
507double FGTable::GetElement(unsigned int r, unsigned int c) const
508{
509 assert(r <= nRows && c <= nCols);
510 if (Type == ttND) {
511 assert(Data.size() == nRows+1);
512 return Data[r];
513 }
514 assert(Data.size() == (nCols+1)*(nRows+1));
515 return Data[r*(nCols+1)+c];
516}
517
518//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
519
520double FGTable::GetValue(void) const
521{
522 assert(!internal);
523 assert(nDims > 0u);
524
525 switch(Type) {
526 case tt1D:
527 return GetValue(lookupProperty[eRow]->getDoubleValue());
528 case tt2D:
529 return GetValue(lookupProperty[eRow]->getDoubleValue(),
530 lookupProperty[eColumn]->getDoubleValue());
531 case ttND:
532 {
533 for (unsigned int axis=0u; axis<nDims; ++axis) {
534 assert(HasLookupProperty(axis));
535 lookupPropertyValues[axis] = lookupProperty[axis]->getDoubleValue();
536 }
537
538 return GetValue(lookupPropertyValues.data());
539 }
540 default:
541 assert(false);
542 }
543}
544
545//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
546
547double FGTable::GetValue(const std::vector<double>& keys) const
548{
549 if (keys.size() < nDims) {
550 // If we do not throw here then we will segfault in FGTable::GetValue(const double*)
551 LogException err;
552 err << "Keys size does not match the table dimensions.\n";
553 throw err;
554 }
555
556 return GetValue(keys.data());
557}
558
559//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
560
561double FGTable::GetValue(const double* keys) const
562{
563 if (Type == tt1D)
564 return GetValue(keys[0]);
565
566 if (Type == tt2D)
567 return GetValue(keys[0], keys[1]);
568
569 assert(Type == ttND);
570 assert(Data.size() == nRows+1);
571
572 const double outerKey = keys[nDims-1];
573
574 // If the key is off the end (or before the beginning) of the table, just
575 // return the boundary-table value, do not extrapolate.
576 if (outerKey <= Data[1])
577 return Tables[0]->GetValue(keys);
578 else if (outerKey >= Data[nRows])
579 return Tables[nRows-1]->GetValue(keys);
580
581 // Search for the right breakpoint.
582 // This is a linear search, the algorithm is O(n).
583 unsigned int r = 2u;
584 while (Data[r] < outerKey) r++;
585
586 double x0 = Data[r-1u];
587 double Span = Data[r] - x0;
588 assert(Span > 0.0);
589 double Factor = (outerKey - x0) / Span;
590 assert(Factor >= 0.0 && Factor <= 1.0);
591
592 double y0 = Tables[r-2u]->GetValue(keys);
593 return Factor*(Tables[r-1u]->GetValue(keys) - y0) + y0;
594}
595
596//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
597
598double FGTable::GetValue(double key) const
599{
600 assert((Type == tt1D) || (Type == tt2D && nCols == 1));
601 assert(Data.size() == 2*nRows+2);
602 // If the key is off the end (or before the beginning) of the table, just
603 // return the boundary-table value, do not extrapolate.
604 if (key <= Data[2])
605 return Data[3];
606 else if (key >= Data[2*nRows])
607 return Data[2*nRows+1];
608
609 // Search for the right breakpoint.
610 // This is a linear search, the algorithm is O(n).
611 unsigned int r = 2;
612 while (Data[2*r] < key) r++;
613
614 double x0 = Data[2*r-2];
615 double Span = Data[2*r] - x0;
616 assert(Span > 0.0);
617 double Factor = (key - x0) / Span;
618 assert(Factor >= 0.0 && Factor <= 1.0);
619
620 double y0 = Data[2*r-1];
621 return Factor*(Data[2*r+1] - y0) + y0;
622}
623
624//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
625
626double FGTable::GetValue(double rowKey, double colKey) const
627{
628 assert(Type == tt2D);
629 assert(Data.size() == (nCols+1)*(nRows+1));
630
631 if (nCols == 1) return GetValue(rowKey);
632
633 unsigned int c = 2;
634 while(Data[c] < colKey && c < nCols) c++;
635 double x0 = Data[c-1];
636 double Span = Data[c] - x0;
637 assert(Span > 0.0);
638 double cFactor = Constrain(0.0, (colKey - x0) / Span, 1.0);
639
640 if (nRows == 1) {
641 double y0 = Data[(nCols+1)+c-1];
642 return cFactor*(Data[(nCols+1)+c] - y0) + y0;
643 }
644
645 size_t r = 2;
646 while(Data[r*(nCols+1)] < rowKey && r < nRows) r++;
647 x0 = Data[(r-1)*(nCols+1)];
648 Span = Data[r*(nCols+1)] - x0;
649 assert(Span > 0.0);
650 double rFactor = Constrain(0.0, (rowKey - x0) / Span, 1.0);
651 double col1temp = rFactor*Data[r*(nCols+1)+c-1]+(1.0-rFactor)*Data[(r-1)*(nCols+1)+c-1];
652 double col2temp = rFactor*Data[r*(nCols+1)+c]+(1.0-rFactor)*Data[(r-1)*(nCols+1)+c];
653
654 return cFactor*(col2temp-col1temp)+col1temp;
655}
656
657//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
658
659double FGTable::GetValue(double rowKey, double colKey, double tableKey) const
660{
661 const double keys[3] = {rowKey, colKey, tableKey};
662 assert(nDims == 3);
663 return GetValue(keys);
664}
665
666//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
667
668double FGTable::GetValue(double a1, double a2, double a3, double a4) const
669{
670 const double keys[4] = {a1, a2, a3, a4};
671 assert(nDims == 4);
672 return GetValue(keys);
673}
674
675//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
676
677double FGTable::GetValue(double a1, double a2, double a3, double a4, double a5) const
678{
679 const double keys[5] = {a1, a2, a3, a4, a5};
680 assert(nDims == 5);
681 return GetValue(keys);
682}
683
684//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
685
686double FGTable::GetValue(double a1, double a2, double a3, double a4,
687 double a5, double a6) const
688{
689 const double keys[6] = {a1, a2, a3, a4, a5, a6};
690 assert(nDims == 6);
691 return GetValue(keys);
692}
693
694//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
695
696double FGTable::GetMinValue(void) const
697{
698 assert(Type == tt1D);
699 assert(Data.size() == 2*nRows+2);
700
701 double minValue = HUGE_VAL;
702
703 for(unsigned int i=1; i<=nRows; ++i)
704 minValue = std::min(minValue, Data[2*i+1]);
705
706 return minValue;
707}
708
709//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
710
711void FGTable::operator<<(istream& in_stream)
712{
713 double x;
714 assert(Type != ttND);
715
716 in_stream >> x;
717 while(in_stream) {
718 Data.push_back(x);
719 in_stream >> x;
720 }
721}
722
723//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
724
725FGTable& FGTable::operator<<(const double x)
726{
727 assert(Type != ttND);
728 Data.push_back(x);
729
730 // Check column is monotically increasing
731 size_t n = Data.size();
732 if (Type == tt2D && nCols > 1 && n >= 3 && n <= nCols+1) {
733 if (Data.at(n-1) <= Data.at(n-2))
734 throw BaseException("FGTable: column lookup is not monotonically increasing");
735 }
736
737 // Check row is monotically increasing
738 size_t row = (n-1) / (nCols+1);
739 if (row >=2 && row*(nCols+1) == n-1) {
740 if (Data.at(row*(nCols+1)) <= Data.at((row-1)*(nCols+1)))
741 throw BaseException("FGTable: row lookup is not monotonically increasing");
742 }
743
744 return *this;
745}
746
747//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
748
749void FGTable::Print(void)
750{
751 FGLogging out(LogLevel::STDOUT);
752 out << std::setprecision(4);
753
754 switch(Type) {
755 case tt1D:
756 out << " 1 dimensional table with " << nRows << " rows.\n";
757 break;
758 case tt2D:
759 out << " 2 dimensional table with " << nRows << " rows, " << nCols << " columns.\n";
760 break;
761 case ttND:
762 out << " " << nDims << " dimensional table with " << nRows
763 << " breakpoints, " << Tables.size() << " subtables.\n";
764 break;
765 }
766 unsigned int startCol=1, startRow=1;
767 unsigned int p = 1;
768
769 if (Type == tt1D) {
770 startCol = 0;
771 p = 2;
772 }
773 if (Type == tt2D) startRow = 0;
774
775 for (unsigned int r=startRow; r<=nRows; r++) {
776 out << "\t";
777 if (Type == tt2D) {
778 if (r == startRow)
779 out << "\t";
780 else
781 startCol = 0;
782 }
783
784 for (unsigned int c=startCol; c<=nCols; c++) {
785 out << Data[p++] << "\t";
786 if (Type == ttND) {
787 out << "\n";
788 Tables[r-1]->Print();
789 }
790 }
791 out << "\n";
792 }
793 out << std::fixed;
794}
795
796//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
797
798void FGTable::bind(Element* el, const string& Prefix)
799{
800 if ( !Name.empty() && !internal) {
801 if (!Prefix.empty()) {
802 if (is_number(Prefix)) {
803 if (Name.find("#") != string::npos) {
804 Name = replace(Name, "#", Prefix);
805 } else {
806 XMLLogException err(el);
807 err << "Malformed table name with number: " << Prefix
808 << " and property name: " << Name
809 << " but no \"#\" sign for substitution.\n";
810 throw err;
811 }
812 } else {
813 Name = Prefix + "/" + Name;
814 }
815 }
816 string tmp = PropertyManager->mkPropertyName(Name, false);
817
818 if (PropertyManager->HasNode(tmp)) {
819 SGPropertyNode* _property = PropertyManager->GetNode(tmp);
820 if (_property->isTied()) {
821 XMLLogException err(el);
822 err << "Property " << tmp << " has already been successfully bound (late).\n";
823 throw err;
824 }
825 }
826
827 PropertyManager->Tie<FGTable, double>(tmp, this, &FGTable::GetValue);
828 }
829}
830//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
831// The bitmasked value choices are as follows:
832// unset: In this case (the default) JSBSim would only print
833// out the normally expected messages, essentially echoing
834// the config files as they are read. If the environment
835// variable is not set, debug_lvl is set to 1 internally
836// 0: This requests JSBSim not to output any messages
837// whatsoever.
838// 1: This value explicity requests the normal JSBSim
839// startup messages
840// 2: This value asks for a message to be printed out when
841// a class is instantiated
842// 4: When this value is set, a message is displayed when a
843// FGModel object executes its Run() method
844// 8: When this value is set, various runtime state variables
845// are printed out periodically
846// 16: When set various parameters are sanity checked and
847// a message is printed out when they go out of bounds
848
849void FGTable::Debug(int from)
850{
851 if (debug_lvl <= 0) return;
852
853 if (debug_lvl & 1) { // Standard console startup message output
854 if (from == 0) { } // Constructor
855 }
856 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
857 FGLogging log(LogLevel::DEBUG);
858 if (from == 0) log << "Instantiated: FGTable\n";
859 if (from == 1) log << "Destroyed: FGTable\n";
860 }
861 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
862 }
863 if (debug_lvl & 8 ) { // Runtime state variables
864 }
865 if (debug_lvl & 16) { // Sanity checking
866 }
867 if (debug_lvl & 64) {
868 if (from == 0) { // Constructor
869 }
870 }
871}
872}
Element * FindElement(const std::string &el="")
Searches for a specified element.
const std::string & GetName(void) const
Retrieves the element name.
double GetAttributeValueAsNumber(const std::string &key)
Retrieves an attribute value as a double precision real number.
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
std::string GetDataLine(unsigned int i=0)
Gets a line of data belonging to an element.
Element * GetParent(void)
Returns a pointer to the parent of an element.
unsigned int GetNumDataLines(void)
Returns the number of lines of data stored.
unsigned int GetNumElements(void)
Returns the number of child elements for this element.
Element * FindNextElement(const std::string &el="")
Searches for the next element as specified.
static constexpr double Constrain(double min, double value, double max)
Constrain a value between a minimum and a maximum value.
Definition FGJSBBase.h:289
Represents a property value which can use late binding.
Lookup table class.
Definition FGTable.h:331
~FGTable()
Destructor.
Definition FGTable.cpp:491
void operator<<(std::istream &)
Read the table in.
Definition FGTable.cpp:711
FGTable(const FGTable &table)
This is the very important copy constructor.
Definition FGTable.cpp:195
double GetValue(void) const
Get the current table value.
Definition FGTable.cpp:520
A node in a property tree.
Definition props.hxx:747
bool isTied() const
Test whether this node is bound to an external data source.
Definition props.hxx:1349
Main namespace for the JSBSim Flight Dynamics Model.
Definition FGFDMExec.cpp:71
Type
The possible types of an SGPropertyNode.
Definition props.hxx:153