Skip to content

Commit 540fa30

Browse files
authored
Merge pull request #2117 from opensim-org/datatable_build_by_column
Allow building TimeSeriesTables by column from scratch
2 parents 27b8a8e + 4ea352a commit 540fa30

File tree

5 files changed

+166
-77
lines changed

5 files changed

+166
-77
lines changed

OpenSim/Common/AbstractDataTable.h

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,17 +131,6 @@ class IncorrectMetaDataLength : public Exception {
131131
}
132132
};
133133

134-
class MetaDataLengthZero : public Exception {
135-
public:
136-
MetaDataLengthZero(const std::string& file,
137-
size_t line,
138-
const std::string& func,
139-
const std::string& msg) :
140-
Exception(file, line, func) {
141-
addMessage(msg);
142-
}
143-
};
144-
145134
class EmptyTable : public Exception {
146135
public:
147136
EmptyTable(const std::string& file,
@@ -382,15 +371,11 @@ class OSIMCOMMON_API AbstractDataTable {
382371
\param last InputIterator representing the sentinel or one past the end of
383372
sequence of labels.
384373
385-
\throws MetaDataLengthZero If length of input sequence of labels is zero.
386374
\throws IncorrectMetaDataLength If length of the input sequence of labels is
387375
incorrect -- does not match the number of
388376
columns in the table. */
389377
template<typename InputIt>
390378
void setColumnLabels(InputIt first, InputIt last) {
391-
OPENSIM_THROW_IF(first == last,
392-
MetaDataLengthZero,
393-
"Length of provided sequence of column labels is 0.");
394379

395380
ValueArray<std::string> labels{};
396381
for(auto it = first; it != last; ++it)
@@ -413,7 +398,6 @@ class OSIMCOMMON_API AbstractDataTable {
413398
own) that supports begin() and end(). Type of the values
414399
produced by iterator should be std::string.
415400
416-
\throws MetaDataLengthZero If input sequence of labels is zero.
417401
\throws IncorrectMetaDataLength If length of the input sequence of labels is
418402
incorrect -- does not match the number of
419403
columns in the table. */
@@ -429,7 +413,6 @@ class OSIMCOMMON_API AbstractDataTable {
429413
setColumnLabels({"col1", "col2", "col3"});
430414
\endcode
431415
432-
\throws MetaDataLengthZero If input sequence of labels is zero.
433416
\throws IncorrectMetaDataLength If length of the input sequence of labels is
434417
incorrect -- does not match the number of
435418
columns in the table. */

OpenSim/Common/DataTable.h

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -646,20 +646,19 @@ class DataTable_ : public AbstractDataTable {
646646
void appendRow(const ETX& indRow, const RowVectorView& depRow) {
647647
validateRow(_indData.size(), indRow, depRow);
648648

649+
if (_dependentsMetaData.hasKey("labels")) {
650+
auto& labels =
651+
_dependentsMetaData.getValueArrayForKey("labels");
652+
OPENSIM_THROW_IF(static_cast<unsigned>(depRow.ncol()) !=
653+
labels.size(),
654+
IncorrectNumColumns,
655+
labels.size(),
656+
static_cast<size_t>(depRow.ncol()));
657+
}
658+
649659
_indData.push_back(indRow);
650660

651-
if(_depData.nrow() == 0 || _depData.ncol() == 0) {
652-
try {
653-
auto& labels =
654-
_dependentsMetaData.getValueArrayForKey("labels");
655-
OPENSIM_THROW_IF(static_cast<unsigned>(depRow.ncol()) !=
656-
labels.size(),
657-
IncorrectNumColumns,
658-
labels.size(),
659-
static_cast<size_t>(depRow.ncol()));
660-
} catch(KeyNotFound&) {
661-
// No "labels". So no operation.
662-
}
661+
if(_depData.nrow() == 0) {
663662
_depData.resize(1, depRow.size());
664663
}
665664
else
@@ -670,10 +669,8 @@ class DataTable_ : public AbstractDataTable {
670669

671670
/** Get row at index.
672671
673-
\throws EmptyTable If the table is empty.
674672
\throws RowIndexOutOfRange If index is out of range. */
675673
const RowVectorView getRowAtIndex(size_t index) const {
676-
OPENSIM_THROW_IF(isEmpty(), EmptyTable);
677674
OPENSIM_THROW_IF(isRowIndexOutOfRange(index),
678675
RowIndexOutOfRange,
679676
index, 0, static_cast<unsigned>(_indData.size() - 1));
@@ -699,10 +696,8 @@ class DataTable_ : public AbstractDataTable {
699696

700697
/** Update row at index.
701698
702-
\throws EmptyTable If the table is empty.
703699
\throws RowIndexOutOfRange If the index is out of range. */
704700
RowVectorView updRowAtIndex(size_t index) {
705-
OPENSIM_THROW_IF(isEmpty(), EmptyTable);
706701
OPENSIM_THROW_IF(isRowIndexOutOfRange(index),
707702
RowIndexOutOfRange,
708703
index, 0, static_cast<unsigned>(_indData.size() - 1));
@@ -731,7 +726,6 @@ class DataTable_ : public AbstractDataTable {
731726
updRowAtIndex(index) = depRow;
732727
```
733728
734-
\throws EmptyTable If the table is empty.
735729
\throws RowIndexOutOfRange If the index is out of range. */
736730
void setRowAtIndex(size_t index, const RowVectorView& depRow) {
737731
updRowAtIndex(index) = depRow;
@@ -742,7 +736,6 @@ class DataTable_ : public AbstractDataTable {
742736
updRowAtIndex(index) = depRow;
743737
```
744738
745-
\throws EmptyTable If the table is empty.
746739
\throws RowIndexOutOfRange If the index is out of range. */
747740
void setRowAtIndex(size_t index, const RowVector& depRow) {
748741
updRowAtIndex(index) = depRow;
@@ -780,10 +773,8 @@ class DataTable_ : public AbstractDataTable {
780773

781774
/** Remove row at index.
782775
783-
\throws EmptyTable If the table is empty.
784776
\throws RowIndexOutOfRange If the index is out of range. */
785777
void removeRowAtIndex(size_t index) {
786-
OPENSIM_THROW_IF(isEmpty(), EmptyTable);
787778
OPENSIM_THROW_IF(isRowIndexOutOfRange(index),
788779
RowIndexOutOfRange,
789780
index, 0, static_cast<unsigned>(_indData.size() - 1));
@@ -1199,6 +1190,15 @@ class DataTable_ : public AbstractDataTable {
11991190
_depData = depData;
12001191
}
12011192

1193+
/** Construct a table with only the independent column and 0
1194+
dependent columns. This constructor is useful when populating the table by
1195+
appending columns rather than by appending rows. */
1196+
DataTable_(const std::vector<ETX>& indVec) {
1197+
setColumnLabels({});
1198+
_indData = indVec;
1199+
_depData.resize((int)indVec.size(), 0);
1200+
}
1201+
12021202
// Implement toString.
12031203
std::string toString_impl(std::vector<int> rows = {},
12041204
std::vector<int> cols = {},
@@ -1476,7 +1476,6 @@ class DataTable_ : public AbstractDataTable {
14761476
14771477
\throws MissingMetaData If metadata for dependent columns does not
14781478
contain a key named "labels".
1479-
\throws MetaDataLengthZero If 'labels' metadata has length 0.
14801479
\throws IncorrectMetaDataLength (1) If ValueArray for key "labels" does not
14811480
have length equal to the number of columns in the
14821481
table. (2) If not all entries in the metadata for
@@ -1491,10 +1490,6 @@ class DataTable_ : public AbstractDataTable {
14911490
OPENSIM_THROW(MissingMetaData, "labels");
14921491
}
14931492

1494-
OPENSIM_THROW_IF(numCols == 0,
1495-
MetaDataLengthZero,
1496-
"Length of 'labels' metadata is 0.");
1497-
14981493
OPENSIM_THROW_IF(_depData.ncol() != 0 &&
14991494
numCols != static_cast<unsigned>(_depData.ncol()),
15001495
IncorrectMetaDataLength, "labels",
@@ -1513,7 +1508,7 @@ class DataTable_ : public AbstractDataTable {
15131508
15141509
\throws InvalidRow If the given row considered invalid by the derived
15151510
class. */
1516-
virtual void validateRow(size_t rowIndex,
1511+
virtual void validateRow(size_t rowIndex,
15171512
const ETX&,
15181513
const RowVector&) const {
15191514
// No operation.

OpenSim/Common/Test/testDataTable.cpp

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ int main() {
5858

5959
table.setColumnLabel(0, "zero");
6060
table.setColumnLabel(2, "two");
61-
61+
6262
ASSERT(table.getColumnLabel(0) == "zero");
6363
ASSERT(table.getColumnLabel(2) == "two");
6464

6565
table.setColumnLabel(0, "0");
6666
table.setColumnLabel(2, "2");
6767

6868
const auto& labels = table.getColumnLabels();
69-
for(size_t i = 0; i < labels.size(); ++i)
69+
for(size_t i = 0; i < labels.size(); ++i)
7070
if(labels.at(i) != std::to_string(i))
7171
throw Exception{"Test failed: labels.at(i) != "
7272
"std::to_string(i)"};
@@ -78,10 +78,6 @@ int main() {
7878
}
7979

8080
// Test exceptions (table should be empty here).
81-
SimTK_TEST_MUST_THROW_EXC(table.getRowAtIndex(0),
82-
OpenSim::EmptyTable);
83-
SimTK_TEST_MUST_THROW_EXC(table.updRowAtIndex(0),
84-
OpenSim::EmptyTable);
8581
SimTK_TEST_MUST_THROW_EXC(table.getDependentColumnAtIndex(0),
8682
OpenSim::EmptyTable);
8783
SimTK_TEST_MUST_THROW_EXC(table.updDependentColumnAtIndex(0),
@@ -134,7 +130,7 @@ int main() {
134130
table.updMatrixBlock(0, 0, table.getNumRows(), table.getNumColumns()) -= 2;
135131

136132
table.updTableMetaData().setValueForKey("DataRate", 600);
137-
table.updTableMetaData().setValueForKey("Filename",
133+
table.updTableMetaData().setValueForKey("Filename",
138134
std::string{"/path/to/file"});
139135

140136
ASSERT(table.hasColumn(0));
@@ -168,16 +164,16 @@ int main() {
168164
" != std::to_string(i + 1)"};
169165
}
170166

171-
const auto& col_index_ref
167+
const auto& col_index_ref
172168
= dep_metadata_ref.getValueArrayForKey("column-index");
173169
for(unsigned i = 0; i < 5; ++i)
174170
if(col_index_ref[i].getValue<unsigned>() != i + 1)
175171
throw Exception{"Test failed: col_index_ref[i].getValue<unsigned>()"
176172
" != i + 1"};
177173

178174
const auto& ind_metadata_ref = table.getIndependentMetaData();
179-
180-
if(ind_metadata_ref.getValueForKey("labels").getValue<std::string>()
175+
176+
if(ind_metadata_ref.getValueForKey("labels").getValue<std::string>()
181177
!= std::string{"0"})
182178
throw Exception{"Test failed: ind_metadata_ref.getValueForKey"
183179
"(\"labels\").getValue<std::string>() != std::string{\"0\"}"};
@@ -216,7 +212,7 @@ int main() {
216212
// ASSERT(table.getNumRows() == 5 && table.getNumColumns() == 7);
217213

218214
const auto& tab_metadata_ref = table.getTableMetaData();
219-
if(tab_metadata_ref.getValueForKey("DataRate").getValue<int>()
215+
if(tab_metadata_ref.getValueForKey("DataRate").getValue<int>()
220216
!= 600)
221217
throw Exception{"Test failed: tab_metadata_ref.getValueForKey"
222218
"(\"DataRate\").getValue<int>() != 600"};
@@ -312,7 +308,7 @@ int main() {
312308
}
313309
}
314310
}
315-
311+
316312
std::cout << "Test DataTable flatten() for Vec3." << std::endl;
317313
auto tableFlat = tableVec3.flatten({"_x", "_y", "_z"});
318314
expLabels = {"col0_x", "col0_y", "col0_z",
@@ -337,7 +333,7 @@ int main() {
337333

338334
std::cout << "Test DataTable flattening constructor for Quaternion."
339335
<< std::endl;
340-
DataTable_<double, Quaternion> tableQuat{};
336+
DataTable_<double, Quaternion> tableQuat{};
341337
tableQuat.setColumnLabels({"col0", "col1", "col2"});
342338
tableQuat.appendRow(0.1, {{1, 1, 1, 1}, {2, 2, 2, 2}, {3, 3, 3, 3}});
343339
tableQuat.appendRow(0.2, {{3, 3, 3, 3}, {1, 1, 1, 1}, {2, 2, 2, 2}});
@@ -412,7 +408,7 @@ int main() {
412408
ASSERT(nearRowVec3[0][i] == 2);
413409

414410
std::cout << tableVec3 << std::endl;
415-
411+
416412
TimeSeriesTable_<double> tableDouble{tableVec3};
417413
std::vector<std::string> expLabels{"col0_1", "col0_2", "col0_3",
418414
"col1_1", "col1_2", "col1_3",
@@ -458,7 +454,7 @@ int main() {
458454

459455
std::cout << "Test TimeSeriesTable flattening constructor for "
460456
"Quaternion" << std::endl;
461-
TimeSeriesTable_<Quaternion> tableQuat{};
457+
TimeSeriesTable_<Quaternion> tableQuat{};
462458
tableQuat.setColumnLabels({"col0", "col1", "col2"});
463459
tableQuat.appendRow(0.1, {{1, 1, 1, 1}, {2, 2, 2, 2}, {3, 3, 3, 3}});
464460
tableQuat.appendRow(0.2, {{3, 3, 3, 3}, {1, 1, 1, 1}, {2, 2, 2, 2}});
@@ -539,7 +535,7 @@ int main() {
539535
ASSERT(tableDouble.getColumnLabels().size() == 18);
540536
ASSERT(tableDouble.getNumRows() == 3);
541537
ASSERT(tableDouble.getNumColumns() == 18);
542-
538+
543539
std::cout << tableDouble << std::endl;
544540
}
545541
{
@@ -571,7 +567,7 @@ int main() {
571567
ASSERT(tableVec3_1.getTableMetaData<std::string>("string") == "string");
572568
ASSERT(tableVec3_1.getTableMetaData<int>("int") == 10);
573569
std::cout << tableVec3_1 << std::endl;
574-
570+
575571
std::cout << "Test DataTable packing for Vec3 with suffix unspecified."
576572
<< std::endl;
577573
auto tableVec3_2 = tableDouble.pack<SimTK::Vec3>();
@@ -649,7 +645,7 @@ int main() {
649645
ASSERT(tableVec3_1.getTableMetaData<std::string>("string") == "string");
650646
ASSERT(tableVec3_1.getTableMetaData<int>("int") == 10);
651647
std::cout << tableVec3_1 << std::endl;
652-
648+
653649
std::cout << "Test TimeSeriesTable packing for Vec3 with suffix"
654650
" unspecified."
655651
<< std::endl;
@@ -701,5 +697,62 @@ int main() {
701697
std::cout << tableSVec << std::endl;
702698
}
703699

700+
{
701+
std::cout << "Test that TimeSeriesTable can have 0 columns."
702+
<< std::endl;
703+
TimeSeriesTable table(std::vector<double>{1.5, 2.5, 3.5});
704+
std::vector<std::string> expLabels = {};
705+
ASSERT(table.getColumnLabels() == expLabels);
706+
ASSERT(table.getNumRows() == 3);
707+
ASSERT(table.getNumColumns() == 0);
708+
709+
// Can append an empty row.
710+
table.appendRow(4.5, {});
711+
ASSERT(table.getNumRows() == 4);
712+
ASSERT(table.getNumColumns() == 0);
713+
714+
table.removeRowAtIndex(3);
715+
ASSERT(table.getNumRows() == 3);
716+
ASSERT(table.getNumColumns() == 0);
717+
718+
table.appendRow(4.5, {});
719+
ASSERT(table.getNumRows() == 4);
720+
ASSERT(table.getNumColumns() == 0);
721+
722+
// Cannot append a non-empty row.
723+
SimTK_TEST_MUST_THROW_EXC(table.appendRow(5.5, {6.1}),
724+
IncorrectNumColumns);
725+
726+
// Can append a column to a table that has no columns yet.
727+
table.appendColumn("col1", {5.4, 5.3, 5.6, 5.8});
728+
ASSERT(table.getNumRows() == 4);
729+
ASSERT(table.getNumColumns() == 1);
730+
731+
// Appending a column with an incorrect number of rows.
732+
SimTK_TEST_MUST_THROW_EXC(table.appendColumn("col2", {5.4, 5.3, 5.6}),
733+
IncorrectNumRows);
734+
735+
// Can appendRow after appendColumn.
736+
table.appendRow(5.5, {1.3});
737+
ASSERT(table.getNumRows() == 5);
738+
ASSERT(table.getNumColumns() == 1);
739+
740+
// Can create an empty table by providing an empty indVec.
741+
TimeSeriesTable emptyTable(std::vector<double>{});
742+
// Bu we can't append columns to an empty table.
743+
SimTK_TEST_MUST_THROW_EXC(emptyTable.appendColumn("col0", {}),
744+
InvalidCall);
745+
746+
// Ensure that we are validating the time stamps are increasing
747+
SimTK_TEST_MUST_THROW_EXC(
748+
TimeSeriesTable(std::vector<double>{1.5, 1.6, 1.6}),
749+
TimestampGreaterThanEqualToNext);
750+
SimTK_TEST_MUST_THROW_EXC(
751+
TimeSeriesTable(std::vector<double>{1.5, 1.6, 1.4}),
752+
TimestampGreaterThanEqualToNext);
753+
SimTK_TEST_MUST_THROW_EXC(table.appendRow(-0.3, {0.6}),
754+
TimestampLessThanEqualToPrevious);
755+
}
756+
704757
return 0;
705758
}

0 commit comments

Comments
 (0)