45#include "input_output/FGXMLElement.h"
46#include "input_output/string_utilities.h"
54unsigned int FindNumColumns(
const string& test_line)
59 while ((position = test_line.find_first_not_of(
" \t", position)) != string::npos) {
61 position = test_line.find_first_of(
" \t", position);
66unsigned int InferLeafDimension(Element* tableData)
68 const unsigned int nLines = tableData->GetNumDataLines();
70 XMLLogException err(tableData);
71 err <<
"<tableData> is empty.\n";
75 const unsigned int firstLineColumns = FindNumColumns(tableData->GetDataLine(0));
78 if (firstLineColumns != 2u) {
79 XMLLogException err(tableData);
80 err <<
"Too many columns for a 1D table\n";
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";
99 if (firstLineColumns != 2u) {
100 XMLLogException err(tableData);
101 err <<
"Too many columns for a 1D table\n";
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";
117std::optional<unsigned int> ParseLookupAxis(
const string& lookup_axis)
119 if (lookup_axis.empty() || lookup_axis ==
"row" || lookup_axis ==
"axis1")
121 if (lookup_axis ==
"column" || lookup_axis ==
"axis2")
123 if (lookup_axis ==
"table" || lookup_axis ==
"axis3")
127 if (lookup_axis.rfind(
"axis", 0) == 0) {
128 const string suffix = lookup_axis.substr(4);
129 const unsigned long axis = std::stoul(suffix);
131 return static_cast<unsigned int>(axis - 1ul);
133 }
catch (std::exception& e) {
134 FGLogging err(LogLevel::ERROR);
135 err << e.what() <<
"\n";
141string LookupAxisName(
unsigned int axis)
149 return "axis" + std::to_string(axis + 1u);
152void AppendNumericData(Element* tableData, stringstream& buf)
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";
173 : nRows(NRows), nCols(1u), nDims(1u)
177 Data.push_back(std::numeric_limits<double>::quiet_NaN());
178 Data.push_back(std::numeric_limits<double>::quiet_NaN());
184FGTable::FGTable(
int NRows,
int NCols)
185 : nRows(NRows), nCols(NCols), nDims(2u)
189 Data.push_back(std::numeric_limits<double>::quiet_NaN());
196 : PropertyManager(t.PropertyManager)
202 internal = t.internal;
204 lookupProperty = t.lookupProperty;
207 Tables.reserve(t.Tables.size());
208 for(
const auto& table: t.Tables)
209 Tables.push_back(std::make_unique<FGTable>(*table));
211 lookupPropertyValues.resize(nDims);
218 const std::string& Prefix)
219 : PropertyManager(pm)
225 if (call_type ==
"internal") {
227 }
else if (!call_type.empty()) {
229 err <<
" An unknown table type attribute is listed: " << call_type <<
"\n";
237 unsigned int declared_dimension = 0u;
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;
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);
259 PropertyManager, axisElement);
262 const std::optional<unsigned int> axis = ParseLookupAxis(lookup_axis);
264 if (!axis.has_value()) {
266 err <<
"FGTable: lookup axis specification not understood: " << lookup_axis <<
"\n";
270 if (HasLookupProperty(*axis)) {
272 err <<
"FGTable: duplicate lookup axis \"" << LookupAxisName(*axis) <<
"\"\n";
276 SetLookupProperty(*axis, node);
277 declared_dimension = std::max(declared_dimension, *axis + 1u);
282 for (
unsigned int axis=0u; axis<declared_dimension; ++axis) {
283 if (!HasLookupProperty(axis)) {
285 err <<
"FGTable: missing lookup axis \"" << LookupAxisName(axis) <<
"\"\n";
290 }
else if (!internal && el->
GetName() !=
"tableData") {
294 err <<
"No independentVars found, and table is not marked as internal,"
295 <<
" nor is it a nested sub-table.\n";
302 const unsigned int nChildTableData = el->
GetNumElements(
"tableData");
304 if (el->
GetName() ==
"tableData" && nChildTableData == 0u) {
307 }
else if (el->
GetName() !=
"tableData" && nChildTableData == 1u) {
310 }
else if (nChildTableData > 1u) {
315 Data.push_back(std::numeric_limits<double>::quiet_NaN());
322 if (brkpt_string.empty()) {
324 err <<
"FGTable: missing breakPoint on <tableData>\n";
328 auto subtable = std::make_unique<FGTable>(PropertyManager, child);
331 nDims = subtable->nDims + 1u;
334 err <<
"FGTable: nested tables must contain at least 2D subtables\n";
337 }
else if (subtable->nDims + 1u != nDims) {
339 err <<
"FGTable: inconsistent sub-table dimensionality in nested table\n";
344 Tables.push_back(std::move(subtable));
348 nRows =
static_cast<unsigned int>(Tables.size());
351 err <<
"FGTable: <tableData> elements are missing\n";
357 AppendNumericData(leafData, buf);
359 nDims = InferLeafDimension(leafData);
367 Data.push_back(std::numeric_limits<double>::quiet_NaN());
368 Data.push_back(std::numeric_limits<double>::quiet_NaN());
376 Data.push_back(std::numeric_limits<double>::quiet_NaN());
385 if (declared_dimension != 0u && declared_dimension != nDims) {
387 err <<
"FGTable: " << declared_dimension
388 <<
" lookup axes were declared, but the tableData nesting implies a "
389 << nDims <<
"D table.\n";
405 for (
unsigned int b=2; b<=Tables.size(); ++b) {
406 if (Data[b] <= Data[b-1]) {
408 err << LogFormat::RED << LogFormat::BOLD
409 <<
" FGTable: breakpoint lookup is not monotonically increasing\n"
410 <<
" in breakpoint " << b;
412 err <<
":\n" << LogFormat::RESET
413 <<
" " << Data[b] <<
"<=" << Data[b-1] <<
"\n";
421 for (
unsigned int c=2; c<=nCols; ++c) {
422 if (Data[c] <= Data[c-1]) {
424 err << LogFormat::RED << LogFormat::BOLD
425 <<
" FGTable: column lookup is not monotonically increasing\n"
426 <<
" in column " << c;
428 err <<
":\n" << LogFormat::RESET
429 <<
" " << Data[c] <<
"<=" << Data[c-1] <<
"\n";
437 for (
size_t r=2; r<=nRows; ++r) {
438 if (Data[r*(nCols+1)]<=Data[(r-1)*(nCols+1)]) {
440 err << LogFormat::RED << LogFormat::BOLD
441 <<
" FGTable: row lookup is not monotonically increasing\n"
444 err <<
":\n" << LogFormat::RESET
445 <<
" " << Data[r*(nCols+1)] <<
"<=" << Data[(r-1)*(nCols+1)] <<
"\n";
454 if (Data.size() != 2*nRows+2) missingData(el, 2*nRows, Data.size()-2);
457 if (Data.size() !=
static_cast<size_t>(nRows+1)*(nCols+1))
458 missingData(el, (nRows+1)*(nCols+1)-1, Data.size()-1);
461 if (Data.size() != nRows+1) missingData(el, nRows, Data.size()-1);
468 lookupPropertyValues.resize(nDims);
472 if (debug_lvl & 1) Print();
477void FGTable::missingData(
Element* el,
unsigned int expected_size,
size_t actual_size)
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";
495 if (!Name.empty() && !internal) {
496 string tmp = PropertyManager->mkPropertyName(Name,
false);
498 if (node && node->
isTied())
499 PropertyManager->Untie(node);
507double FGTable::GetElement(
unsigned int r,
unsigned int c)
const
509 assert(r <= nRows && c <= nCols);
511 assert(Data.size() == nRows+1);
514 assert(Data.size() == (nCols+1)*(nRows+1));
515 return Data[r*(nCols+1)+c];
527 return GetValue(lookupProperty[eRow]->getDoubleValue());
529 return GetValue(lookupProperty[eRow]->getDoubleValue(),
530 lookupProperty[eColumn]->getDoubleValue());
533 for (
unsigned int axis=0u; axis<nDims; ++axis) {
534 assert(HasLookupProperty(axis));
535 lookupPropertyValues[axis] = lookupProperty[axis]->getDoubleValue();
538 return GetValue(lookupPropertyValues.data());
549 if (keys.size() < nDims) {
552 err <<
"Keys size does not match the table dimensions.\n";
569 assert(Type == ttND);
570 assert(Data.size() == nRows+1);
572 const double outerKey = keys[nDims-1];
576 if (outerKey <= Data[1])
577 return Tables[0]->GetValue(keys);
578 else if (outerKey >= Data[nRows])
579 return Tables[nRows-1]->GetValue(keys);
584 while (Data[r] < outerKey) r++;
586 double x0 = Data[r-1u];
587 double Span = Data[r] - x0;
589 double Factor = (outerKey - x0) / Span;
590 assert(Factor >= 0.0 && Factor <= 1.0);
592 double y0 = Tables[r-2u]->GetValue(keys);
593 return Factor*(Tables[r-1u]->GetValue(keys) - y0) + y0;
600 assert((Type == tt1D) || (Type == tt2D && nCols == 1));
601 assert(Data.size() == 2*nRows+2);
606 else if (key >= Data[2*nRows])
607 return Data[2*nRows+1];
612 while (Data[2*r] < key) r++;
614 double x0 = Data[2*r-2];
615 double Span = Data[2*r] - x0;
617 double Factor = (key - x0) / Span;
618 assert(Factor >= 0.0 && Factor <= 1.0);
620 double y0 = Data[2*r-1];
621 return Factor*(Data[2*r+1] - y0) + y0;
628 assert(Type == tt2D);
629 assert(Data.size() == (nCols+1)*(nRows+1));
631 if (nCols == 1)
return GetValue(rowKey);
634 while(Data[c] < colKey && c < nCols) c++;
635 double x0 = Data[c-1];
636 double Span = Data[c] - x0;
638 double cFactor =
Constrain(0.0, (colKey - x0) / Span, 1.0);
641 double y0 = Data[(nCols+1)+c-1];
642 return cFactor*(Data[(nCols+1)+c] - y0) + y0;
646 while(Data[r*(nCols+1)] < rowKey && r < nRows) r++;
647 x0 = Data[(r-1)*(nCols+1)];
648 Span = Data[r*(nCols+1)] - x0;
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];
654 return cFactor*(col2temp-col1temp)+col1temp;
661 const double keys[3] = {rowKey, colKey, tableKey};
670 const double keys[4] = {a1, a2, a3, a4};
677double FGTable::GetValue(
double a1,
double a2,
double a3,
double a4,
double a5)
const
679 const double keys[5] = {a1, a2, a3, a4, a5};
687 double a5,
double a6)
const
689 const double keys[6] = {a1, a2, a3, a4, a5, a6};
696double FGTable::GetMinValue(
void)
const
698 assert(Type == tt1D);
699 assert(Data.size() == 2*nRows+2);
701 double minValue = HUGE_VAL;
703 for(
unsigned int i=1; i<=nRows; ++i)
704 minValue = std::min(minValue, Data[2*i+1]);
714 assert(Type != ttND);
727 assert(Type != ttND);
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");
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");
749void FGTable::Print(
void)
751 FGLogging out(LogLevel::STDOUT);
752 out << std::setprecision(4);
756 out <<
" 1 dimensional table with " << nRows <<
" rows.\n";
759 out <<
" 2 dimensional table with " << nRows <<
" rows, " << nCols <<
" columns.\n";
762 out <<
" " << nDims <<
" dimensional table with " << nRows
763 <<
" breakpoints, " << Tables.size() <<
" subtables.\n";
766 unsigned int startCol=1, startRow=1;
773 if (Type == tt2D) startRow = 0;
775 for (
unsigned int r=startRow; r<=nRows; r++) {
784 for (
unsigned int c=startCol; c<=nCols; c++) {
785 out << Data[p++] <<
"\t";
788 Tables[r-1]->Print();
798void FGTable::bind(Element* el,
const string& Prefix)
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);
806 XMLLogException err(el);
807 err <<
"Malformed table name with number: " << Prefix
808 <<
" and property name: " << Name
809 <<
" but no \"#\" sign for substitution.\n";
813 Name = Prefix +
"/" + Name;
816 string tmp = PropertyManager->mkPropertyName(Name,
false);
818 if (PropertyManager->HasNode(tmp)) {
820 if (_property->
isTied()) {
821 XMLLogException err(el);
822 err <<
"Property " << tmp <<
" has already been successfully bound (late).\n";
849void FGTable::Debug(
int from)
851 if (debug_lvl <= 0)
return;
856 if (debug_lvl & 2 ) {
857 FGLogging log(LogLevel::DEBUG);
858 if (from == 0) log <<
"Instantiated: FGTable\n";
859 if (from == 1) log <<
"Destroyed: FGTable\n";
861 if (debug_lvl & 4 ) {
863 if (debug_lvl & 8 ) {
865 if (debug_lvl & 16) {
867 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.