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