45#include "input_output/FGXMLElement.h"
46#include "input_output/string_utilities.h"
63#if __cplusplus >= 202302L
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);
71#define UNREACHABLE(msg) do { } while(0);
74#define UNREACHABLE(msg) assert(false && msg); std::abort();
83unsigned int FindNumColumns(
const string& test_line)
88 while ((position = test_line.find_first_not_of(
" \t", position)) != string::npos) {
90 position = test_line.find_first_of(
" \t", position);
95unsigned int InferLeafDimension(Element* tableData)
97 const unsigned int nLines = tableData->GetNumDataLines();
99 XMLLogException err(tableData);
100 err <<
"<tableData> is empty.\n";
104 const unsigned int firstLineColumns = FindNumColumns(tableData->GetDataLine(0));
107 if (firstLineColumns != 2u) {
108 XMLLogException err(tableData);
109 err <<
"Too many columns for a 1D table\n";
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";
128 if (firstLineColumns != 2u) {
129 XMLLogException err(tableData);
130 err <<
"Too many columns for a 1D table\n";
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";
146std::optional<unsigned int> ParseLookupAxis(
const string& lookup_axis)
148 if (lookup_axis.empty() || lookup_axis ==
"row" || lookup_axis ==
"axis1")
150 if (lookup_axis ==
"column" || lookup_axis ==
"axis2")
152 if (lookup_axis ==
"table" || lookup_axis ==
"axis3")
156 if (lookup_axis.rfind(
"axis", 0) == 0) {
157 const string suffix = lookup_axis.substr(4);
158 const unsigned long axis = std::stoul(suffix);
160 return static_cast<unsigned int>(axis - 1ul);
162 }
catch (std::exception& e) {
163 FGLogging err(LogLevel::ERROR);
164 err << e.what() <<
"\n";
170string LookupAxisName(
unsigned int axis)
178 return "axis" + std::to_string(axis + 1u);
181void AppendNumericData(Element* tableData, stringstream& buf)
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";
202 : nRows(NRows), nCols(1u), nDims(1u)
206 Data.push_back(std::numeric_limits<double>::quiet_NaN());
207 Data.push_back(std::numeric_limits<double>::quiet_NaN());
213FGTable::FGTable(
int NRows,
int NCols)
214 : nRows(NRows), nCols(NCols), nDims(2u)
218 Data.push_back(std::numeric_limits<double>::quiet_NaN());
225 : PropertyManager(t.PropertyManager)
231 internal = t.internal;
233 lookupProperty = t.lookupProperty;
236 Tables.reserve(t.Tables.size());
237 for(
const auto& table: t.Tables)
238 Tables.push_back(std::make_unique<FGTable>(*table));
240 lookupPropertyValues.resize(nDims);
247 const std::string& Prefix)
248 : PropertyManager(pm)
254 if (call_type ==
"internal") {
256 }
else if (!call_type.empty()) {
258 err <<
" An unknown table type attribute is listed: " << call_type <<
"\n";
266 unsigned int declared_dimension = 0u;
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;
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);
288 PropertyManager, axisElement);
291 const std::optional<unsigned int> axis = ParseLookupAxis(lookup_axis);
293 if (!axis.has_value()) {
295 err <<
"FGTable: lookup axis specification not understood: " << lookup_axis <<
"\n";
299 if (HasLookupProperty(*axis)) {
301 err <<
"FGTable: duplicate lookup axis \"" << LookupAxisName(*axis) <<
"\"\n";
305 SetLookupProperty(*axis, node);
306 declared_dimension = std::max(declared_dimension, *axis + 1u);
311 for (
unsigned int axis=0u; axis<declared_dimension; ++axis) {
312 if (!HasLookupProperty(axis)) {
314 err <<
"FGTable: missing lookup axis \"" << LookupAxisName(axis) <<
"\"\n";
319 }
else if (!internal && el->
GetName() !=
"tableData") {
323 err <<
"No independentVars found, and table is not marked as internal,"
324 <<
" nor is it a nested sub-table.\n";
331 const unsigned int nChildTableData = el->
GetNumElements(
"tableData");
333 if (el->
GetName() ==
"tableData" && nChildTableData == 0u) {
336 }
else if (el->
GetName() !=
"tableData" && nChildTableData == 1u) {
339 }
else if (nChildTableData > 1u) {
344 Data.push_back(std::numeric_limits<double>::quiet_NaN());
351 if (brkpt_string.empty()) {
353 err <<
"FGTable: missing breakPoint on <tableData>\n";
357 auto subtable = std::make_unique<FGTable>(PropertyManager, child);
360 nDims = subtable->nDims + 1u;
363 err <<
"FGTable: nested tables must contain at least 2D subtables\n";
366 }
else if (subtable->nDims + 1u != nDims) {
368 err <<
"FGTable: inconsistent sub-table dimensionality in nested table\n";
373 Tables.push_back(std::move(subtable));
377 nRows =
static_cast<unsigned int>(Tables.size());
380 err <<
"FGTable: <tableData> elements are missing\n";
386 AppendNumericData(leafData, buf);
388 nDims = InferLeafDimension(leafData);
396 Data.push_back(std::numeric_limits<double>::quiet_NaN());
397 Data.push_back(std::numeric_limits<double>::quiet_NaN());
405 Data.push_back(std::numeric_limits<double>::quiet_NaN());
409 UNREACHABLE(
"invalid table type")
414 if (declared_dimension != 0u && declared_dimension != nDims) {
416 err <<
"FGTable: " << declared_dimension
417 <<
" lookup axes were declared, but the tableData nesting implies a "
418 << nDims <<
"D table.\n";
434 for (
unsigned int b=2; b<=Tables.size(); ++b) {
435 if (Data[b] <= Data[b-1]) {
437 err << LogFormat::RED << LogFormat::BOLD
438 <<
" FGTable: breakpoint lookup is not monotonically increasing\n"
439 <<
" in breakpoint " << b;
441 err <<
":\n" << LogFormat::RESET
442 <<
" " << Data[b] <<
"<=" << Data[b-1] <<
"\n";
450 for (
unsigned int c=2; c<=nCols; ++c) {
451 if (Data[c] <= Data[c-1]) {
453 err << LogFormat::RED << LogFormat::BOLD
454 <<
" FGTable: column lookup is not monotonically increasing\n"
455 <<
" in column " << c;
457 err <<
":\n" << LogFormat::RESET
458 <<
" " << Data[c] <<
"<=" << Data[c-1] <<
"\n";
466 for (
size_t r=2; r<=nRows; ++r) {
467 if (Data[r*(nCols+1)]<=Data[(r-1)*(nCols+1)]) {
469 err << LogFormat::RED << LogFormat::BOLD
470 <<
" FGTable: row lookup is not monotonically increasing\n"
473 err <<
":\n" << LogFormat::RESET
474 <<
" " << Data[r*(nCols+1)] <<
"<=" << Data[(r-1)*(nCols+1)] <<
"\n";
483 if (Data.size() != 2*nRows+2) missingData(el, 2*nRows, Data.size()-2);
486 if (Data.size() !=
static_cast<size_t>(nRows+1)*(nCols+1))
487 missingData(el, (nRows+1)*(nCols+1)-1, Data.size()-1);
490 if (Data.size() != nRows+1) missingData(el, nRows, Data.size()-1);
493 UNREACHABLE(
"invalid table type")
497 lookupPropertyValues.resize(nDims);
509void FGTable::missingData(
Element* el,
unsigned int expected_size,
size_t actual_size)
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";
527 if (!Name.empty() && !internal) {
528 string tmp = PropertyManager->mkPropertyName(Name,
false);
530 if (node && node->
isTied())
531 PropertyManager->Untie(node);
539double FGTable::GetElement(
unsigned int r,
unsigned int c)
const
541 assert(r <= nRows && c <= nCols);
543 assert(Data.size() == nRows+1);
546 assert(Data.size() == (nCols+1)*(nRows+1));
547 return Data[r*(nCols+1)+c];
559 return GetValue(lookupProperty[eRow]->getDoubleValue());
561 return GetValue(lookupProperty[eRow]->getDoubleValue(),
562 lookupProperty[eColumn]->getDoubleValue());
565 for (
unsigned int axis=0u; axis<nDims; ++axis) {
566 assert(HasLookupProperty(axis));
567 lookupPropertyValues[axis] = lookupProperty[axis]->getDoubleValue();
570 return GetValue(lookupPropertyValues.data());
573 UNREACHABLE(
"invalid table type")
581 if (keys.size() < nDims) {
584 err <<
"Keys size does not match the table dimensions.\n";
601 assert(Type == ttND);
602 assert(Data.size() == nRows+1);
604 const double outerKey = keys[nDims-1];
608 if (outerKey <= Data[1])
609 return Tables[0]->GetValue(keys);
610 else if (outerKey >= Data[nRows])
611 return Tables[nRows-1]->GetValue(keys);
616 while (Data[r] < outerKey) r++;
618 double x0 = Data[r-1u];
619 double Span = Data[r] - x0;
621 double Factor = (outerKey - x0) / Span;
622 assert(Factor >= 0.0 && Factor <= 1.0);
624 double y0 = Tables[r-2u]->GetValue(keys);
625 return Factor*(Tables[r-1u]->GetValue(keys) - y0) + y0;
632 assert((Type == tt1D) || (Type == tt2D && nCols == 1));
633 assert(Data.size() == 2*nRows+2);
638 else if (key >= Data[2*nRows])
639 return Data[2*nRows+1];
644 while (Data[2*r] < key) r++;
646 double x0 = Data[2*r-2];
647 double Span = Data[2*r] - x0;
649 double Factor = (key - x0) / Span;
650 assert(Factor >= 0.0 && Factor <= 1.0);
652 double y0 = Data[2*r-1];
653 return Factor*(Data[2*r+1] - y0) + y0;
660 assert(Type == tt2D);
661 assert(Data.size() == (nCols+1)*(nRows+1));
663 if (nCols == 1)
return GetValue(rowKey);
666 while(Data[c] < colKey && c < nCols) c++;
667 double x0 = Data[c-1];
668 double Span = Data[c] - x0;
670 double cFactor =
Constrain(0.0, (colKey - x0) / Span, 1.0);
673 double y0 = Data[(nCols+1)+c-1];
674 return cFactor*(Data[(nCols+1)+c] - y0) + y0;
678 while(Data[r*(nCols+1)] < rowKey && r < nRows) r++;
679 x0 = Data[(r-1)*(nCols+1)];
680 Span = Data[r*(nCols+1)] - x0;
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];
686 return cFactor*(col2temp-col1temp)+col1temp;
693 const double keys[3] = {rowKey, colKey, tableKey};
702 const double keys[4] = {a1, a2, a3, a4};
709double FGTable::GetValue(
double a1,
double a2,
double a3,
double a4,
double a5)
const
711 const double keys[5] = {a1, a2, a3, a4, a5};
719 double a5,
double a6)
const
721 const double keys[6] = {a1, a2, a3, a4, a5, a6};
728double FGTable::GetMinValue(
void)
const
730 assert(Type == tt1D);
731 assert(Data.size() == 2*nRows+2);
733 double minValue = HUGE_VAL;
735 for(
unsigned int i=1; i<=nRows; ++i)
736 minValue = std::min(minValue, Data[2*i+1]);
746 assert(Type != ttND);
759 assert(Type != ttND);
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");
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");
783 FGLogging out(LogLevel::STDOUT);
787void FGTable::Print(FGLogging& out)
789 out << std::setprecision(4);
793 out <<
" 1 dimensional table with " << nRows <<
" rows.\n";
796 out <<
" 2 dimensional table with " << nRows <<
" rows, " << nCols <<
" columns.\n";
799 out <<
" " << nDims <<
" dimensional table with " << nRows
800 <<
" breakpoints, " << Tables.size() <<
" subtables.\n";
803 unsigned int startCol=1, startRow=1;
810 if (Type == tt2D) startRow = 0;
812 for (
unsigned int r=startRow; r<=nRows; r++) {
821 for (
unsigned int c=startCol; c<=nCols; c++) {
822 out << Data[p++] <<
"\t";
825 Tables[r-1]->Print(out);
835void FGTable::bind(Element* el,
const string& Prefix)
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);
843 XMLLogException err(el);
844 err <<
"Malformed table name with number: " << Prefix
845 <<
" and property name: " << Name
846 <<
" but no \"#\" sign for substitution.\n";
850 Name = Prefix +
"/" + Name;
853 string tmp = PropertyManager->mkPropertyName(Name,
false);
855 if (PropertyManager->HasNode(tmp)) {
857 if (_property->
isTied()) {
858 XMLLogException err(el);
859 err <<
"Property " << tmp <<
" has already been successfully bound (late).\n";
886void FGTable::Debug(
int from)
888 if (debug_lvl <= 0)
return;
893 if (debug_lvl & 2 ) {
894 FGLogging log(LogLevel::DEBUG);
895 if (from == 0) log <<
"Instantiated: FGTable\n";
896 if (from == 1) log <<
"Destroyed: FGTable\n";
898 if (debug_lvl & 4 ) {
900 if (debug_lvl & 8 ) {
902 if (debug_lvl & 16) {
904 if (debug_lvl & 64) {
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.
Represents a property value which can use late binding.
void operator<<(std::istream &)
Read the table in.
FGTable(const FGTable &table)
This is the very important copy constructor.
double GetValue(void) const
Get the current table value.
A node in a property tree.
bool isTied() const
Test whether this node is bound to an external data source.
Main namespace for the JSBSim Flight Dynamics Model.
Type
The possible types of an SGPropertyNode.