1 /* -------------------------------------------------------------------------- * 2 * OpenSim: DataTable.h * 3 * -------------------------------------------------------------------------- * 4 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * 5 * See http://opensim.stanford.edu and the NOTICE file for more information. * 6 * OpenSim is developed at Stanford University and supported by the US * 7 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * 8 * through the Warrior Web program. * 9 * * 10 * Copyright (c) 2005-2017 Stanford University and the Authors * 11 * Authors: * 12 * * 13 * Licensed under the Apache License, Version 2.0 (the "License"); you may * 14 * not use this file except in compliance with the License. You may obtain a * 15 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * 16 * * 17 * Unless required by applicable law or agreed to in writing, software * 18 * distributed under the License is distributed on an "AS IS" BASIS, * 19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 20 * See the License for the specific language governing permissions and * 21 * limitations under the License. * 22 * -------------------------------------------------------------------------- */ 23 24 #ifndef OPENSIM_DATA_TABLE_H_ 25 #define OPENSIM_DATA_TABLE_H_ 26 27 /** \file 28 This file defines the DataTable_ class, which is used by OpenSim to provide an 29 in-memory container for data access and manipulation. */ 30 31 #include "AbstractDataTable.h" 32 #include "FileAdapter.h" 33 #include "SimTKcommon/internal/BigMatrix.h" 34 #include "SimTKcommon/internal/Quaternion.h" 35 #include <OpenSim/Common/IO.h> 36 37 #include <iomanip> 38 #include <numeric> 39 40 namespace OpenSim { 41 42 /** DataTable_ is an in-memory storage container for data with support for 43 holding metadata (using the base class AbstractDataTable). Data contains an 44 independent column and a set of dependent columns. The type of the independent 45 column can be configured using ETX (template param). The type of the dependent 46 columns, which together form a matrix, can be configured using ETY (template 47 param). Independent and dependent columns can contain metadata. DataTable_ as a 48 whole can contain metadata. 49 50 \tparam ETX Type of each element of the column holding independent data. 51 \tparam ETY Type of each element of the underlying matrix holding dependent 52 data. */ 53 template<typename ETX = double, typename ETY = SimTK::Real> 54 class DataTable_ : public AbstractDataTable { 55 static_assert(!std::is_reference<ETY>::value, 56 "Template argument ETY cannot be a 'reference'."); 57 static_assert(!std::is_pointer<ETY>::value, 58 "Template argument ETY cannot be a 'pointer'."); 59 static_assert(!std::is_const<ETY>::value && !std::is_volatile<ETY>::value, 60 "Template argument ETY cannot be 'const' or 'volatile'."); 61 62 public: 63 /** Type of each row of matrix holding dependent data. */ 64 typedef SimTK::RowVector_<ETY> RowVector; 65 /** (Read only view) Type of each row of matrix. */ 66 typedef SimTK::RowVectorView_<ETY> RowVectorView; 67 /** Type of each column of matrix holding dependent data. */ 68 typedef SimTK::Vector_<ETY> Vector; 69 /** Type of each column of matrix holding dependent data. */ 70 typedef SimTK::VectorView_<ETY> VectorView; 71 /** Type of the matrix holding the dependent data. */ 72 typedef SimTK::Matrix_<ETY> Matrix; 73 /** (Read only view) Type of the matrix holding the dependent data. */ 74 typedef SimTK::MatrixView_<ETY> MatrixView; 75 76 DataTable_() = default; 77 DataTable_(const DataTable_&) = default; 78 DataTable_(DataTable_&&) = default; 79 DataTable_& operator=(const DataTable_&) = default; 80 DataTable_& operator=(DataTable_&&) = default; 81 ~DataTable_() = default; 82 clone()83 std::shared_ptr<AbstractDataTable> clone() const override { 84 return std::shared_ptr<AbstractDataTable>{new DataTable_{*this}}; 85 } 86 87 /** Construct DataTable_ from a file. 88 89 \param filename Name of the file. File should contain only one table. For 90 example, trc, csv & sto files contain one table whereas a 91 c3d file can contain more than. 92 \param tablename Name of the table in file to construct this DataTable_ 93 from. For example, a c3d file contains tables named 94 'markers' and 'forces'. 95 96 \throws InvalidArgument If the input file contains more than one table and 97 tablename was not specified. 98 \throws InvalidArgument If the input file contains a table that is not of 99 this DataTable_ type. */ DataTable_(const std::string & filename,const std::string & tablename)100 DataTable_(const std::string& filename, 101 const std::string& tablename) { 102 auto absTables = FileAdapter::createAdapterFromExtension(filename)->read(filename); 103 104 OPENSIM_THROW_IF(absTables.size() > 1 && tablename.empty(), 105 InvalidArgument, 106 "File '" + filename + 107 "' contains more than one table and tablename not" 108 " specified."); 109 110 AbstractDataTable* absTable{}; 111 if(tablename.empty()) { 112 absTable = (absTables.cbegin()->second).get(); 113 } else { 114 try { 115 absTable = absTables.at(tablename).get(); 116 } catch (const std::out_of_range&) { 117 OPENSIM_THROW(InvalidArgument, 118 "File '" + filename + "' contains no table named " 119 "'" + tablename + "'."); 120 } 121 } 122 auto table = dynamic_cast<DataTable_*>(absTable); 123 OPENSIM_THROW_IF(table == nullptr, 124 IncorrectTableType, 125 "DataTable cannot be created from file '" + filename + 126 "'. Type mismatch."); 127 128 *this = std::move(*table); 129 } 130 131 /** Construct DataTable_<double, double> from 132 DataTable_<double, ThatETY> where ThatETY can be SimTK::Vec<X>. Each column 133 of the other table is split into multiple columns of this table. For example 134 , DataTable_<double, Vec3> with 3 columns and 4 rows will construct 135 DataTable<double, double> of 9 columns and 4 rows where each component of 136 SimTK::Vec3 ends up in one column. Column labels of the resulting DataTable 137 will use column labels of source table appended with suffixes provided. 138 This constructor only makes sense for DataTable_<double, double>. 139 140 \tparam ThatETY Datatype of the matrix underlying the given DataTable. 141 142 \param that DataTable to copy-construct this table from. This table can be 143 of different SimTK::Vec<X> types, for example DataTable_<double, 144 Vec<3>>, DataTable_<double, Quaternion>, DataTable_<double, 145 Vec6> etc. 146 \param suffixes Suffixes to be used for column-labels of individual 147 components/columns in this table when splitting columns of 148 'that' table. For example a column labeled 'marker' from 149 DataTable_<double, Vec3> will be split into 3 columns named 150 \code 151 std::string{'marker' + suffixes[0]}, 152 std::string{'marker' + suffixes[1]}, 153 std::string{'marker' + suffixes[2]} 154 \endcode 155 156 \throws InvalidArgument If 'that' DataTable has no column-labels. 157 \throws InvalidArgument If 'that' DataTable has zero number of rows/columns. 158 \throws InvalidArgument If 'suffixes' does not contain same number of 159 elements as that.numComponentsPerElement(). */ 160 template<typename ThatETY> DataTable_(const DataTable_<double,ThatETY> & that,const std::vector<std::string> & suffixes)161 DataTable_(const DataTable_<double, ThatETY>& that, 162 const std::vector<std::string>& suffixes) : 163 AbstractDataTable{that} { 164 static_assert(std::is_same<ETY, double>::value, 165 "This constructor can only be used to construct " 166 "DataTable_<double, double>."); 167 static_assert(!std::is_same<ThatETY, double>::value, 168 "This constructor cannot be used to construct from " 169 "DataTable_<double, double>. Use the copy constructor " 170 "instead."); 171 172 std::vector<std::string> thatLabels{}; 173 OPENSIM_THROW_IF(!that.hasColumnLabels(), 174 InvalidArgument, 175 "DataTable 'that' has no column labels."); 176 OPENSIM_THROW_IF(that.getNumRows() == 0 || that.getNumColumns() == 0, 177 InvalidArgument, 178 "DataTable 'that' has zero rows/columns."); 179 OPENSIM_THROW_IF(!suffixes.empty() && 180 suffixes.size() != that.numComponentsPerElement(), 181 InvalidArgument, 182 "'suffixes' must contain same number of elements as " 183 "number of components per element of DataTable 'that'." 184 "See documentation for numComponentsPerElement()."); 185 186 // If the dependents metadata is of std::string type, 187 // replicate it to match the new number of columns. If not of 188 // std::string type, drop the metadata because type information is 189 // required to interpret them. 190 // Column-labels will be handled separately as they need suffixing. 191 for(const auto& key : _dependentsMetaData.getKeys()) { 192 if(key == "labels") 193 continue; 194 195 auto absValueArray = &_dependentsMetaData.updValueArrayForKey(key); 196 ValueArray<std::string>* valueArray{}; 197 try { 198 valueArray = 199 dynamic_cast<ValueArray<std::string>*>(absValueArray); catch(const std::bad_cast &)200 } catch (const std::bad_cast&) { 201 _dependentsMetaData.removeValueArrayForKey(key); 202 continue; 203 } 204 auto& values = valueArray->upd(); 205 std::vector<SimTK::Value<std::string>> newValues{}; 206 for(const auto& value : values) 207 for(auto i = 0u; i < that.numComponentsPerElement(); ++i) 208 newValues.push_back(value); 209 values = std::move(newValues); 210 } 211 212 std::vector<std::string> thisLabels{}; 213 thisLabels.reserve(that.getNumColumns() * 214 that.numComponentsPerElement()); 215 for(const auto& label : that.getColumnLabels()) { 216 if(suffixes.empty()) { 217 for(unsigned i = 1; i <= that.numComponentsPerElement(); ++i) 218 thisLabels.push_back(label + "_" + std::to_string(i)); 219 } else { 220 for(const auto& suffix : suffixes) 221 thisLabels.push_back(label + suffix); 222 } 223 } 224 // This calls validateDependentsMetadata, so no need for explicit call. 225 setColumnLabels(thisLabels); 226 227 // Construct matrix for this table from that table. 228 _depData.resize((int)that.getNumRows(), 229 (int)that.getNumColumns() * that.numComponentsPerElement()); 230 for(unsigned r = 0; r < that.getNumRows(); ++r) { 231 const auto& thatRow = that.getRowAtIndex(r); 232 for (unsigned c = 0; c < that.getNumColumns(); ++c) { 233 splitAndAssignElement(_depData.updRow(r).begin() + 234 c*that.numComponentsPerElement(), 235 _depData.updRow(r).end(), 236 thatRow[c]); 237 } 238 } 239 _indData = that.getIndependentColumn(); 240 } 241 242 /** Construct this DataTable from a DataTable_<double, double>. This is the 243 opposite operation of flatten(). Multiple consecutive columns of the given 244 DataTable will be 'packed' together to form columns of this DataTable. For 245 example, if this DataTable is of type DataTable_<double, Vec3>, then every 246 3 consecutive columns of the given DataTable will form one column of this 247 DataTable. The column labels of this table will be formed by stripping out 248 the suffixes from the column labels of the given DataTable. For the same 249 example above, if columns labels of the given DataTable are -- 250 "col0.x", "col0.y", "col0.x", "col1.x", "col1.y", "col1.x" -- the column 251 labels of this DataTable will be -- "col0", "col1" -- where suffixes are 252 stripped out. This constructor will try to guess the suffixes used. If 253 unable to do so, it will throw an exception. Suffixes used can also be 254 specified as arguments. 255 This constructor only makes sense for DataTable_<double, SimTKType> where 256 SimTKType is not 'double'. SimTKType can be for example, SimTK::Vec3, 257 SimTK::Vec6, SimTK::Quaternion, SimTK::SpatialVec etc. 258 259 \param that DataTable to copy-construct this DataTable from. 260 \param suffixes Suffixes used in the input DataTable to distinguish 261 individual components. For example, if column labels are -- 262 "force.x", "force.y", "force.z" -- suffixes will be -- ".x", 263 ".y", ".z". 264 265 \throws InvalidArgument If 'that' DataTable has no column-labels. 266 \throws InvalidArgument If 'that' DataTable has no rows/columns. 267 \throws InvalidArgument If 'suffixes' does not contain same number of 268 elements as this->numComponentsPerElement(). 269 \throws InvalidArgument If number of columns in 'that' DataTable is not a 270 multiple of this->numComponentsPerElement(). 271 \throws InvalidArgument If suffixes cannot be extracted from column-labels 272 of 'that' DataTable. */ DataTable_(const DataTable_<double,double> & that,const std::vector<std::string> & suffixes)273 explicit DataTable_(const DataTable_<double, double>& that, 274 const std::vector<std::string>& suffixes) : 275 AbstractDataTable{that} { 276 static_assert(!std::is_same<ETY, double>::value, 277 "This constructor cannot be used to construct " 278 "DataTable_<double, double>. Maybe use the copy " 279 "constructor instead."); 280 281 OPENSIM_THROW_IF(!that.hasColumnLabels(), 282 InvalidArgument, 283 "DataTable 'that' has no column labels."); 284 OPENSIM_THROW_IF(that.getNumRows() == 0 || that.getNumColumns() == 0, 285 InvalidArgument, 286 "DataTable 'that' has zero rows/columns."); 287 OPENSIM_THROW_IF(!suffixes.empty() && 288 suffixes.size() != numComponentsPerElement(), 289 InvalidArgument, 290 "'suffixes' must contain same number of elements as " 291 "number of components per element of 'this' DataTable." 292 " See documentation for numComponentsPerElement()."); 293 OPENSIM_THROW_IF(std::lldiv(that.getNumColumns(), 294 numComponentsPerElement()).rem != 0, 295 InvalidArgument, 296 "Input DataTable must contain " + 297 std::to_string(numComponentsPerElement()) + "x " 298 "number of columns."); 299 300 const auto& thatLabels = that.getColumnLabels(); 301 for(unsigned i = 0; i < thatLabels.size(); ++i) 302 OPENSIM_THROW_IF(thatLabels[i].length() < 2, 303 InvalidArgument, 304 "Column label at index " + std::to_string(i) + 305 " is too short to have a suffix."); 306 307 // Guess suffixes from columns labels of that table. 308 std::vector<std::string> suffs{suffixes}; 309 if(suffs.empty()) { 310 for(unsigned i = 0; i < numComponentsPerElement(); ++i) { 311 std::string suff{thatLabels[i][thatLabels[i].size() - 1]}; 312 char nonSuffChar{thatLabels[i][thatLabels[i].size() - 2]}; 313 bool foundNonSuffChar{false}; 314 while(!foundNonSuffChar) { 315 for(unsigned c = i; 316 c < thatLabels.size(); 317 c += numComponentsPerElement()) { 318 try { 319 if(thatLabels[c].at(thatLabels[c].size() - 1 - 320 suff.length()) != nonSuffChar) { 321 foundNonSuffChar = true; 322 break; 323 } catch(const std::out_of_range &)324 } catch(const std::out_of_range&) { 325 OPENSIM_THROW(InvalidArgument, 326 "Cannot guess the suffix from column" 327 " label at index " + 328 std::to_string(c)); 329 } 330 } 331 if(!foundNonSuffChar) { 332 suff.insert(suff.begin(), nonSuffChar); 333 try { 334 nonSuffChar = 335 thatLabels[i].at(thatLabels[i].size() - 1 - 336 suff.length()); catch(const std::out_of_range &)337 } catch(const std::out_of_range&) { 338 OPENSIM_THROW(InvalidArgument, 339 "Cannot guess the suffix from column" 340 " label at index " + 341 std::to_string(i)); 342 } 343 } 344 } 345 suffs.push_back(suff); 346 } 347 } 348 349 // Form column labels for this table from that table. 350 std::vector<std::string> thisLabels{}; 351 thisLabels.reserve(that.getNumColumns() / numComponentsPerElement()); 352 for(unsigned c = 0; c < thatLabels.size(); ) { 353 std::string thisLabel{}; 354 for(unsigned i = 0; i < numComponentsPerElement(); ++i, ++c) { 355 const auto& thatLabel = thatLabels[c]; 356 OPENSIM_THROW_IF(thatLabel.compare(thatLabel.length() - 357 suffs[i].length(), 358 suffs[i].length(), 359 suffs[i]) != 0, 360 InvalidArgument, 361 "Suffix not found in column label '" + 362 thatLabel + "'. Expected suffix '" + 363 suffs[i] + "'."); 364 365 if(i == 0) { 366 thisLabel = thatLabel.substr(0, 367 thatLabel.length() - 368 suffs[i].length()); 369 thisLabels.push_back(thisLabel); 370 } else { 371 OPENSIM_THROW_IF(thisLabel != 372 thatLabel.substr(0, 373 thatLabel.length() - 374 suffs[i].length()), 375 InvalidArgument, 376 "Unexpected column-label '" + thatLabel + 377 "'. Expected: '" + thisLabel + suffs[i] + 378 "'."); 379 } 380 } 381 } 382 setColumnLabels(thisLabels); 383 384 // Construct matrix for this table from that table. 385 _depData.resize((int)that.getNumRows(), 386 (int)that.getNumColumns() / numComponentsPerElement()); 387 for(unsigned r = 0; r < that.getNumRows(); ++r) { 388 auto thatRow = that.getRowAtIndex(r).getAsRowVector(); 389 for(unsigned c = 0; c < this->getNumColumns(); ++c) { 390 _depData.updElt(r,c) = makeElement( 391 thatRow.begin() + c*numComponentsPerElement(), 392 thatRow.end()); 393 } 394 } 395 _indData = that.getIndependentColumn(); 396 } 397 398 /** Construct DataTable_<double, double> from 399 DataTable_<double, ThatETY> where ThatETY can be SimTK::Vec<X>. Each column 400 of the other table is split into multiple columns of this table. For example 401 , DataTable_<double, Vec3> with 3 columns and 4 rows will construct 402 DataTable<double, double> of 9 columns and 4 rows where each component of 403 SimTK::Vec3 ends up in one column. Column labels of the resulting DataTable 404 will use column labels of source table appended with suffixes of form "_1", 405 "_2", "_3" and so on. 406 407 \tparam ThatETY Datatype of the matrix underlying the given DataTable. 408 409 \param that DataTable to copy-construct this table from. This table can be 410 of for example DataTable_<double, Quaternion>, 411 DataTable_<double, Vec6>. 412 413 \throws InvalidArgument If 'that' DataTable has no column-labels. 414 \throws InvalidArgument If 'that' DataTable has zero number of rows/columns. 415 \throws InvalidArgument If 'suffixes' does not contain same number of 416 elements as that.numComponentsPerElement(). */ 417 template<typename ThatETY> DataTable_(const DataTable_<double,ThatETY> & that)418 explicit DataTable_(const DataTable_<double, ThatETY>& that) : 419 DataTable_(that, std::vector<std::string>{}) { 420 // No operation. 421 } 422 423 /** Copy assign a DataTable_<double, double> from 424 DataTable_<double, ThatETY> where ThatETY can be SimTK::Vec<X>. Each column 425 of the other table is split into multiple columns of this table. For example 426 , DataTable_<double, Vec3> with 3 columns and 4 rows will construct 427 DataTable<double, double> of 9 columns and 4 rows where each component of 428 SimTK::Vec3 ends up in one column. Column labels of the resulting DataTable 429 will use column labels of source table appended with suffixes of form "_1", 430 "_2", "_3" and so on. 431 432 \tparam ThatETY Datatype of the matrix underlying the given DataTable. 433 434 \param that DataTable to copy assign from. This table can be 435 of for example DataTable_<double, Quaternion>, 436 DataTable_<double, Vec6>. 437 438 \throws InvalidArgument If 'that' DataTable has no column-labels. 439 \throws InvalidArgument If 'that' DataTable has zero number of rows/columns. 440 \throws InvalidArgument If 'suffixes' does not contain same number of 441 elements as that.numComponentsPerElement(). */ 442 template<typename ThatETY> 443 DataTable_& operator=(const DataTable_<double, ThatETY>& that) { 444 return operator=(DataTable_{that}); 445 } 446 447 /** Flatten the columns of this table to create a 448 DataTable_<double, double>. Each column will be split into its 449 constituent components. For example, each column of a 450 DataTable_<double, Vec3> will be split into 3 columns. The column-labels of 451 the resulting columns will be suffixed "_1", "_2", "_3" and so on. See 452 documentation for constructor DataTable_::DataTable_(). */ flatten()453 DataTable_<double, double> flatten() const { 454 return DataTable_<double, double>{*this}; 455 } 456 457 /** Flatten the columns of this table to create a 458 DataTable_<double, double>. Each column will be split into its 459 constituent components. For example, each column of a 460 DataTable_<double, Vec3> will be split into 3 columns. The column-labels of 461 the resulting columns will be appended with 'suffixes' provided. See 462 documentation for constructor DataTable_::DataTable_(). */ 463 DataTable_<double, double> flatten(const std::vector<std::string> & suffixes)464 flatten(const std::vector<std::string>& suffixes) const { 465 return DataTable_<double, double>{*this, suffixes}; 466 } 467 468 /** Pack the columns of this table to create a DataTable_<double, ThatETY>, 469 where 'ThatETY' is the template parameter which can be SimTK::Vec3, 470 SimTK::UnitVec3, SimTK::Quaternion, SimTK::SpatialVec and so on. Multiple 471 consecutive columns of this table will be packed into one column of the 472 resulting table. For example while creating a DataTable_<double, Quaternion> 473 , every group of 4 consecutive columns of this table will form one column 474 of the resulting table. The column-labels of the resulting table will be 475 formed by stripping the suffixes in the column-labels of this table. 476 This function will attempt to guess the suffixes of column-labels. See 477 documentation for constructor DataTable_::DataTable_(). */ 478 template<typename ThatETY> pack()479 DataTable_<double, ThatETY> pack() const { 480 return DataTable_<double, ThatETY>{*this}; 481 } 482 483 /** Pack the columns of this table to create a DataTable_<double, ThatETY>, 484 where 'ThatETY' is the template parameter which can be SimTK::Vec3, 485 SimTK::UnitVec3, SimTK::Quaternion, SimTK::SpatialVec and so on. Multiple 486 consecutive columns of this table will be packed into one column of the 487 resulting table. For example while creating a DataTable_<double, Quaternion> 488 , every group of 4 consecutive columns of this table will form one column 489 of the resulting table. The column-labels of the resulting table will be 490 formed by stripping the suffixes in the column-labels of this table. See 491 documentation for constructor DataTable_::DataTable_(). */ 492 template<typename ThatETY> 493 DataTable_<double, ThatETY> pack(const std::vector<std::string> & suffixes)494 pack(const std::vector<std::string>& suffixes) const { 495 return DataTable_<double, ThatETY>{*this, suffixes}; 496 } 497 498 /** Retrieve the number of components each element (of type ETY) of the 499 table is made of. Some examples: 500 501 Table Type | Element Type | Num of Components 502 ------------------------------|--------------|------------------ 503 DataTable<double, double> | double | 1 504 DataTable<double, Vec3> | Vec3 | 3 505 DataTable<double, Quaternion> | Quaternion | 4 */ numComponentsPerElement()506 unsigned numComponentsPerElement() const override { 507 return numComponentsPerElement_impl(ETY{}); 508 } 509 510 /// @name Row accessors/mutators. 511 /// Following get/upd functions operate on matrix and not the independent 512 /// column. 513 /// The function appendRow() is pretty flexible and it is possible to 514 /// append a row with any sequence of elements. Following are some examples: 515 /// \code 516 /// // Easiest way to append a row is to provide the list of elements 517 /// // directly to appendRow. 518 /// // For a table with elements of type double, this could look like below. 519 /// table.appendRow(0.1, // Independent column. 520 /// {0.3, 0.4, 0.5, 0.6}); // 4 elements of type double. 521 /// // For a table with elements of type SimTK::Vec3, this could like below. 522 /// table.appendRow(0.1, // Independent column. 523 /// {{0.31, 0.32, 0.33}, 524 /// {0.41, 0.42, 0.43}, 525 /// {0.51, 0.52, 0.53}, 526 /// {0.61, 0.62, 0.63}}); // 4 elements of SimTK::Vec3. 527 /// \endcode 528 /// \code 529 /// // It is possible to append a sequence container like std::vector or 530 /// // std::list by providing it directly to appendRow. 531 /// // For a table with elements of type double, this could look like below. 532 /// std::vector<double> row{0.3, 0.4, 0.5, 0.6}; 533 /// table.appendRow(0.1, row); 534 /// // For a table with elements of type SimTK::Vec3, this could look like 535 /// // below. 536 /// std::vector<SimTK::Vec3> row{{0.31, 0.32, 0.33}, 537 /// {0.41, 0.42, 0.43}, 538 /// {0.51, 0.52, 0.53}, // 4 elements of 539 /// {0.61, 0.62, 0.63}}); // SimTK::Vec3. 540 /// table.appendRow(0.1, row); 541 /// \endcode 542 /// \code 543 /// // A SimTK::RowVector can be provided to appendRow as well. 544 /// // For a table with elements of type double, this could look like below. 545 /// SimTK::RowVector row{0.3, 0.4, 0.5, 0.6}; 546 /// table.appendRow(0.1, row); 547 /// // For a table with elements of type SimTK::Vec3, this could look like 548 /// // below. 549 /// SimTK::RowVector_<SimTK::Vec3> row{{0.31, 0.32, 0.33}, 550 /// {0.41, 0.42, 0.43}, 551 /// {0.51, 0.52, 0.53}, // 4 elements of 552 /// {0.61, 0.62, 0.63}}); // SimTK::Vec3. 553 /// table.appendRow(0.1, row); 554 /// \endcode 555 /// \code 556 /// // It is possible to be use a pair of iterators to append a row as well. 557 /// // This could arise in situations where you might want to append a row 558 /// // using a subset of elements in a sequence. 559 /// // For a table with elements of type double, this could look like below. 560 /// std::vector<double> row{0.3, 0.4, 0.5, 0.6, 0.7, 0.8}; 561 /// table.appendRow(0.1, // Independent column. 562 /// row.begin() + 1, // Start from second element (0.4). 563 /// row.end() - 1); // End at last but one (0.7). 564 /// // For a table with elements of type SimTK::Vec3, this could look like 565 /// // below. 566 /// std::vector<SimTK::Vec3> row{{0.31, 0.32, 0.33}, 567 /// {0.41, 0.42, 0.43}, 568 /// {0.51, 0.52, 0.53}, 569 /// {0.61, 0.62, 0.63}, 570 /// {0.71, 0.72, 0.73}, // 6 elements of 571 /// {0.81, 0.82, 0.83}}); // SimTK::Vec3. 572 /// table.appendRow(0.1, // Independent column. 573 /// row.begin() + 1, // Start from second element. 574 /// row.end() - 1); // End at last but one. 575 /// \endcode 576 /// @{ 577 578 /** Append row to the DataTable_. 579 580 \param indRow Entry for the independent column corresponding to the row to 581 be appended. 582 \param container Sequence container holding the elements of the row to be 583 appended. 584 585 \throws IncorrectNumColumns If the row added is invalid. Validity of the 586 row added is decided by the derived class. */ 587 template<typename Container> appendRow(const ETX & indRow,const Container & container)588 void appendRow(const ETX& indRow, const Container& container) { 589 using Value = decltype(*(container.begin())); 590 using RmrefValue = typename std::remove_reference<Value>::type; 591 using RmcvRmrefValue = typename std::remove_cv<RmrefValue>::type; 592 static_assert(std::is_same<ETY, RmcvRmrefValue>::value, 593 "The 'container' specified does not provide an iterator " 594 "which when dereferenced provides elements that " 595 "are of same type as elements of this table."); 596 597 appendRow(indRow, container.begin(), container.end()); 598 } 599 600 /** Append row to the DataTable_. 601 602 \param indRow Entry for the independent column corresponding to the row to 603 be appended. 604 \param container std::initializer_list containing elements of the row to be 605 appended. 606 607 \throws IncorrectNumColumns If the row added is invalid. Validity of the 608 row added is decided by the derived class. */ appendRow(const ETX & indRow,const std::initializer_list<ETY> & container)609 void appendRow(const ETX& indRow, 610 const std::initializer_list<ETY>& container) { 611 appendRow(indRow, container.begin(), container.end()); 612 } 613 614 /** Append row to the DataTable_. 615 616 \param indRow Entry for the independent column corresponding to the row to 617 be appended. 618 \param begin Iterator representing the beginning of the row to be appended. 619 \param end Iterator representing one past the end of the row to be appended. 620 621 \throws IncorrectNumColumns If the row added is invalid. Validity of the 622 row added is decided by the derived class. */ 623 template<typename RowIter> appendRow(const ETX & indRow,RowIter begin,RowIter end)624 void appendRow(const ETX& indRow, RowIter begin, RowIter end) { 625 using Value = decltype(*begin); 626 using RmrefValue = typename std::remove_reference<Value>::type; 627 using RmcvRmrefValue = typename std::remove_cv<RmrefValue>::type; 628 static_assert(std::is_same<ETY, RmcvRmrefValue>::value, 629 "The iterator 'begin' provided does not provide elements" 630 " that are of same type as elements of this table."); 631 632 RowVector row{static_cast<int>(std::distance(begin, end))}; 633 int ind{0}; 634 for(auto it = begin; it != end; ++it) 635 row[ind++] = *it; 636 637 appendRow(indRow, row); 638 } 639 640 /** Append row to the DataTable_. 641 642 \throws IncorrectNumColumns If the row added is invalid. Validity of the 643 row added is decided by the derived class. */ appendRow(const ETX & indRow,const RowVector & depRow)644 void appendRow(const ETX& indRow, const RowVector& depRow) { 645 appendRow(indRow, depRow.getAsRowVectorView()); 646 } 647 648 /** Append row to the DataTable_. 649 650 \throws IncorrectNumColumns If the row added is invalid. Validity of the 651 row added is decided by the derived class. */ appendRow(const ETX & indRow,const RowVectorView & depRow)652 void appendRow(const ETX& indRow, const RowVectorView& depRow) { 653 validateRow(_indData.size(), indRow, depRow); 654 655 if (_dependentsMetaData.hasKey("labels")) { 656 auto& labels = 657 _dependentsMetaData.getValueArrayForKey("labels"); 658 OPENSIM_THROW_IF(static_cast<unsigned>(depRow.ncol()) != 659 labels.size(), 660 IncorrectNumColumns, 661 labels.size(), 662 static_cast<size_t>(depRow.ncol())); 663 } 664 665 _indData.push_back(indRow); 666 667 if(_depData.nrow() == 0) { 668 _depData.resize(1, depRow.size()); 669 } 670 else 671 _depData.resizeKeep(_depData.nrow() + 1, _depData.ncol()); 672 673 _depData.updRow(_depData.nrow() - 1) = depRow; 674 } 675 676 /** Get row at index. 677 678 \throws RowIndexOutOfRange If index is out of range. */ getRowAtIndex(size_t index)679 const RowVectorView getRowAtIndex(size_t index) const { 680 OPENSIM_THROW_IF(isRowIndexOutOfRange(index), 681 RowIndexOutOfRange, 682 index, 0, static_cast<unsigned>(_indData.size() - 1)); 683 684 return _depData.row(static_cast<int>(index)); 685 } 686 687 /** Get row corresponding to the given entry in the independent column. This 688 function searches the independent column for exact equality, which may not 689 be appropriate if `ETX` is of type `double`. See 690 TimeSeriesTable_::getNearestRow(). 691 692 \throws KeyNotFound If the independent column has no entry with given 693 value. */ getRow(const ETX & ind)694 const RowVectorView getRow(const ETX& ind) const { 695 auto iter = std::find(_indData.cbegin(), _indData.cend(), ind); 696 697 OPENSIM_THROW_IF(iter == _indData.cend(), 698 KeyNotFound, std::to_string(ind)); 699 700 return _depData.row((int)std::distance(_indData.cbegin(), iter)); 701 } 702 703 /** Update row at index. 704 705 \throws RowIndexOutOfRange If the index is out of range. */ updRowAtIndex(size_t index)706 RowVectorView updRowAtIndex(size_t index) { 707 OPENSIM_THROW_IF(isRowIndexOutOfRange(index), 708 RowIndexOutOfRange, 709 index, 0, static_cast<unsigned>(_indData.size() - 1)); 710 711 return _depData.updRow((int)index); 712 } 713 714 /** Update row corresponding to the given entry in the independent column. 715 This function searches the independent column for exact equality, which may 716 not be appropriate if `ETX` is of type `double`. See 717 TimeSeriesTable_::updNearestRow(). 718 719 \throws KeyNotFound If the independent column has no entry with given 720 value. */ updRow(const ETX & ind)721 RowVectorView updRow(const ETX& ind) { 722 auto iter = std::find(_indData.cbegin(), _indData.cend(), ind); 723 724 OPENSIM_THROW_IF(iter == _indData.cend(), 725 KeyNotFound, std::to_string(ind)); 726 727 return _depData.updRow((int)std::distance(_indData.cbegin(), iter)); 728 } 729 730 /** Set row at index. Equivalent to 731 ``` 732 updRowAtIndex(index) = depRow; 733 ``` 734 735 \throws RowIndexOutOfRange If the index is out of range. */ setRowAtIndex(size_t index,const RowVectorView & depRow)736 void setRowAtIndex(size_t index, const RowVectorView& depRow) { 737 updRowAtIndex(index) = depRow; 738 } 739 740 /** Set row at index. Equivalent to 741 ``` 742 updRowAtIndex(index) = depRow; 743 ``` 744 745 \throws RowIndexOutOfRange If the index is out of range. */ setRowAtIndex(size_t index,const RowVector & depRow)746 void setRowAtIndex(size_t index, const RowVector& depRow) { 747 updRowAtIndex(index) = depRow; 748 } 749 750 /** Set row corresponding to the given entry in the independent column. 751 This function searches the independent column for exact equality, which may 752 not be appropriate if `ETX` is of type `double`. See 753 TimeSeriesTable_::updNearestRow(). 754 Equivalent to 755 ``` 756 updRow(ind) = depRow; 757 ``` 758 759 \throws KeyNotFound If the independent column has no entry with given 760 value. */ setRow(const ETX & ind,const RowVectorView & depRow)761 void setRow(const ETX& ind, const RowVectorView& depRow) { 762 updRow(ind) = depRow; 763 } 764 765 /** Set row corresponding to the given entry in the independent column. 766 This function searches the independent column for exact equality, which may 767 not be appropriate if `ETX` is of type `double`. See 768 TimeSeriesTable_::updNearestRow(). 769 Equivalent to 770 ``` 771 updRow(ind) = depRow; 772 ``` 773 774 \throws KeyNotFound If the independent column has no entry with given 775 value. */ setRow(const ETX & ind,const RowVector & depRow)776 void setRow(const ETX& ind, const RowVector& depRow) { 777 updRow(ind) = depRow; 778 } 779 780 /** Remove row at index. 781 782 \throws RowIndexOutOfRange If the index is out of range. */ removeRowAtIndex(size_t index)783 void removeRowAtIndex(size_t index) { 784 OPENSIM_THROW_IF(isRowIndexOutOfRange(index), 785 RowIndexOutOfRange, 786 index, 0, static_cast<unsigned>(_indData.size() - 1)); 787 788 if(index < getNumRows() - 1) 789 for(size_t r = index; r < getNumRows() - 1; ++r) 790 _depData.updRow((int)r) = _depData.row((int)(r + 1)); 791 792 _depData.resizeKeep(_depData.nrow() - 1, _depData.ncol()); 793 _indData.erase(_indData.begin() + index); 794 } 795 796 /** Remove row corresponding to the given entry in the independent column. 797 798 \throws KeyNotFound If the independent column has no entry with the given 799 value. */ removeRow(const ETX & ind)800 void removeRow(const ETX& ind) { 801 auto iter = std::find(_indData.cbegin(), _indData.cend(), ind); 802 803 OPENSIM_THROW_IF(iter == _indData.cend(), 804 KeyNotFound, std::to_string(ind)); 805 806 return removeRowAtIndex((int)std::distance(_indData.cbegin(), iter)); 807 } 808 809 /// @} End of Row accessors/mutators. 810 811 /// @name Dependent and Independent column accessors/mutators. 812 /// @{ 813 814 /** Get independent column. */ getIndependentColumn()815 const std::vector<ETX>& getIndependentColumn() const { 816 return _indData; 817 } 818 819 /** Append column to the DataTable_ using a sequence container. 820 \code 821 std::vector<double> col{1, 2, 3, 4}; 822 table.appendColumn("new-column", col); 823 \endcode 824 825 \param columnLabel Label of the column to be added. Must not be same as the 826 label of an existing column. 827 \param container Sequence container holding the elements of the column to be 828 appended. 829 \throws InvalidCall If DataTable_ contains no rows at the time of this call. 830 \throws InvalidArgument If columnLabel specified already exists in the 831 DataTable_. 832 \throws InvalidColumn If the input column contains incorrect number of 833 rows. */ 834 template<typename Container> appendColumn(const std::string & columnLabel,const Container & container)835 void appendColumn(const std::string& columnLabel, 836 const Container& container) { 837 using Value = decltype(*(container.begin())); 838 using RmrefValue = typename std::remove_reference<Value>::type; 839 using RmcvRmrefValue = typename std::remove_cv<RmrefValue>::type; 840 static_assert(std::is_same<ETY, RmcvRmrefValue>::value, 841 "The 'container' specified does not provide an iterator " 842 "which when dereferenced provides elements that " 843 "are of same type as elements of this table."); 844 845 appendColumn(columnLabel, container.begin(), container.end()); 846 } 847 848 /** Append column to the DataTable_ using an initializer list. 849 \code 850 table.appendColumn("new-column", {1, 2, 3, 4}); 851 \endcode 852 853 \param columnLabel Label of the column to be added. Must not be same as the 854 label of an existing column. 855 \param container Sequence container holding the elements of the column to be 856 appended. 857 \throws InvalidCall If DataTable_ contains no rows at the time of this call. 858 \throws InvalidArgument If columnLabel specified already exists in the 859 DataTable_. 860 \throws InvalidColumn If the input column contains incorrect number of 861 rows. */ appendColumn(const std::string & columnLabel,const std::initializer_list<ETY> & container)862 void appendColumn(const std::string& columnLabel, 863 const std::initializer_list<ETY>& container) { 864 appendColumn(columnLabel, container.begin(), container.end()); 865 } 866 867 /** Append column to the DataTable_ using an iterator pair. 868 \code 869 std::vector<double> col{}; 870 // ...... 871 // Fill up 'col'. 872 // ...... 873 table.append("new-column", col.begin(), col.end()); 874 \endcode 875 876 \param columnLabel Label of the column to be added. Must not be same as the 877 label of an existing column. 878 \param begin Iterator referring to the beginning of the range. 879 \param end Iterator referring to the end of the range. 880 881 \throws InvalidCall If DataTable_ contains no rows at the time of this call. 882 \throws InvalidArgument If columnLabel specified already exists in the 883 DataTable_. 884 \throws InvalidColumn If the input column contains incorrect number of 885 rows. */ 886 template<typename ColIter> appendColumn(const std::string & columnLabel,ColIter begin,ColIter end)887 void appendColumn(const std::string& columnLabel, 888 ColIter begin, ColIter end) { 889 using Value = decltype(*begin); 890 using RmrefValue = typename std::remove_reference<Value>::type; 891 using RmcvRmrefValue = typename std::remove_cv<RmrefValue>::type; 892 static_assert(std::is_same<ETY, RmcvRmrefValue>::value, 893 "The iterator 'begin' does not provide elements that are " 894 "of same type as elements of this table."); 895 896 Vector col{static_cast<int>(std::distance(begin, end))}; 897 int ind{0}; 898 for(auto it = begin; it != end; ++it) 899 col[ind++] = *it; 900 901 appendColumn(columnLabel, col); 902 } 903 904 /** Append column to the DataTable_ using a SimTK::Vector. 905 906 \param columnLabel Label of the column to be added. Must not be same as the 907 label of an existing column. 908 \param depCol Column vector to be appended to the table. 909 910 \throws InvalidCall If DataTable_ contains no rows at the time of this call. 911 \throws InvalidArgument If columnLabel specified already exists in the 912 DataTable_. 913 \throws InvalidColumn If the input column contains incorrect number of 914 rows. */ appendColumn(const std::string & columnLabel,const Vector & depCol)915 void appendColumn(const std::string& columnLabel, 916 const Vector& depCol) { 917 appendColumn(columnLabel, depCol.getAsVectorView()); 918 } 919 920 /** Append column to the DataTable_ using a SimTK::VectorView. 921 922 \param columnLabel Label of the column to be added. Must not be same as the 923 label of an existing column. 924 \param depCol Column vector to be appended to the table. 925 926 \throws InvalidCall If DataTable_ contains no rows at the time of this call. 927 \throws InvalidArgument If columnLabel specified already exists in the 928 DataTable_. 929 \throws InvalidColumn If the input column contains incorrect number of 930 rows. */ appendColumn(const std::string & columnLabel,const VectorView & depCol)931 void appendColumn(const std::string& columnLabel, 932 const VectorView& depCol) { 933 OPENSIM_THROW_IF(getNumRows() == 0, 934 InvalidCall, 935 "DataTable must have one or more rows before we can " 936 "append columns to it."); 937 OPENSIM_THROW_IF(hasColumn(columnLabel), 938 InvalidArgument, 939 "Column-label '" + columnLabel + "' already exists in " 940 "the DataTable."); 941 OPENSIM_THROW_IF(depCol.nrow() != getNumRows(), 942 IncorrectNumRows, 943 static_cast<size_t>(getNumRows()), 944 static_cast<size_t>(depCol.nrow())); 945 946 _depData.resizeKeep(_depData.nrow(), _depData.ncol() + 1); 947 _depData.updCol(_depData.ncol() - 1) = depCol; 948 appendColumnLabel(columnLabel); 949 } 950 951 /** Remove column corresponding to the given column index. 952 953 \throws ColumnIndexOutOfRange If the index is out of range. */ removeColumnAtIndex(size_t index)954 void removeColumnAtIndex(size_t index) { 955 OPENSIM_THROW_IF(isColumnIndexOutOfRange(index), 956 ColumnIndexOutOfRange, 957 index, 0, static_cast<unsigned>(_depData.ncol() - 1)); 958 959 // get copy of labels 960 auto labels = getColumnLabels(); 961 962 assert(labels.size() == _depData.ncol()); 963 964 // shift columns unless we're already at the last column 965 for (size_t c = index; c < getNumColumns()-1; ++c) { 966 _depData.updCol((int)c) = _depData.col((int)(c + 1)); 967 labels[c] = labels[c + 1]; 968 } 969 970 _depData.resizeKeep(_depData.nrow(), _depData.ncol()-1); 971 labels.resize(_depData.ncol()); 972 setColumnLabels(labels); 973 } 974 975 /** Remove column corresponding to the given dependent column label. The 976 independent column cannot be removed. 977 978 \throws KeyNotFound If the independent column has no entry with the given 979 value. */ removeColumn(const std::string & columnLabel)980 void removeColumn(const std::string& columnLabel) { 981 const auto& labels = getColumnLabels(); 982 auto iter = std::find(labels.cbegin(), labels.cend(), columnLabel); 983 984 OPENSIM_THROW_IF(iter == labels.cend(), 985 KeyNotFound, columnLabel); 986 987 return removeColumnAtIndex(std::distance(labels.cbegin(), iter)); 988 } 989 990 /** Get dependent column at index. 991 992 \throws EmptyTable If the table is empty. 993 \throws ColumnIndexOutOfRange If index is out of range for number of columns 994 in the table. */ getDependentColumnAtIndex(size_t index)995 VectorView getDependentColumnAtIndex(size_t index) const { 996 OPENSIM_THROW_IF(isEmpty(), EmptyTable); 997 OPENSIM_THROW_IF(isColumnIndexOutOfRange(index), 998 ColumnIndexOutOfRange, index, 0, 999 static_cast<size_t>(_depData.ncol() - 1)); 1000 1001 return _depData.col(static_cast<int>(index)); 1002 } 1003 1004 /** Get dependent Column which has the given column label. 1005 1006 \throws KeyNotFound If columnLabel is not found to be label of any existing 1007 column. */ getDependentColumn(const std::string & columnLabel)1008 VectorView getDependentColumn(const std::string& columnLabel) const { 1009 return _depData.col(static_cast<int>(getColumnIndex(columnLabel))); 1010 } 1011 1012 /** Update dependent column at index. 1013 1014 \throws EmptyTable If the table is empty. 1015 \throws ColumnIndexOutOfRange If index is out of range for number of columns 1016 in the table. */ updDependentColumnAtIndex(size_t index)1017 VectorView updDependentColumnAtIndex(size_t index) { 1018 OPENSIM_THROW_IF(isEmpty(), EmptyTable); 1019 OPENSIM_THROW_IF(isColumnIndexOutOfRange(index), 1020 ColumnIndexOutOfRange, index, 0, 1021 static_cast<size_t>(_depData.ncol() - 1)); 1022 1023 return _depData.updCol(static_cast<int>(index)); 1024 } 1025 1026 /** Update dependent Column which has the given column label. 1027 1028 \throws KeyNotFound If columnLabel is not found to be label of any existing 1029 column. */ updDependentColumn(const std::string & columnLabel)1030 VectorView updDependentColumn(const std::string& columnLabel) { 1031 return _depData.updCol(static_cast<int>(getColumnIndex(columnLabel))); 1032 } 1033 1034 /** %Set value of the independent column at index. 1035 1036 \throws EmptyTable If the table is empty. 1037 \throws RowIndexOutOfRange If rowIndex is out of range. 1038 \throws InvalidRow If this operation invalidates the row. Validation is 1039 performed by derived classes. */ setIndependentValueAtIndex(size_t rowIndex,const ETX & value)1040 void setIndependentValueAtIndex(size_t rowIndex, const ETX& value) { 1041 OPENSIM_THROW_IF(isEmpty(), EmptyTable); 1042 OPENSIM_THROW_IF(isRowIndexOutOfRange(rowIndex), 1043 RowIndexOutOfRange, 1044 rowIndex, 0, 1045 static_cast<unsigned>(_indData.size() - 1)); 1046 1047 validateRow(rowIndex, value, _depData.row((int)rowIndex)); 1048 _indData[rowIndex] = value; 1049 } 1050 1051 /// @} 1052 1053 /// @name Matrix accessors/mutators. 1054 /// Following functions operate on the matrix not including the independent 1055 /// column. 1056 /// @{ 1057 1058 /** Get a read-only view to the underlying matrix. */ getMatrix()1059 const MatrixView& getMatrix() const { 1060 return _depData.getAsMatrixView(); 1061 } 1062 1063 /** Get a read-only view of a block of the underlying matrix. 1064 1065 \throws InvalidArgument If numRows or numColumns is zero. 1066 \throws EmptyTable If the table is empty. 1067 \throws RowIndexOutOfRange If one or more rows of the desired block is out 1068 of range of the matrix. 1069 \throws ColumnIndexOutOfRange If one or more columns of the desired block is 1070 out of range of the matrix. */ getMatrixBlock(size_t rowStart,size_t columnStart,size_t numRows,size_t numColumns)1071 MatrixView getMatrixBlock(size_t rowStart, 1072 size_t columnStart, 1073 size_t numRows, 1074 size_t numColumns) const { 1075 OPENSIM_THROW_IF(numRows == 0 || numColumns == 0, 1076 InvalidArgument, 1077 "Either numRows or numColumns is zero."); 1078 OPENSIM_THROW_IF(isEmpty(), EmptyTable); 1079 OPENSIM_THROW_IF(isRowIndexOutOfRange(rowStart), 1080 RowIndexOutOfRange, 1081 rowStart, 0, 1082 static_cast<unsigned>(_depData.nrow() - 1)); 1083 OPENSIM_THROW_IF(isRowIndexOutOfRange(rowStart + numRows - 1), 1084 RowIndexOutOfRange, 1085 rowStart + numRows - 1, 0, 1086 static_cast<unsigned>(_depData.nrow() - 1)); 1087 OPENSIM_THROW_IF(isColumnIndexOutOfRange(columnStart), 1088 ColumnIndexOutOfRange, 1089 columnStart, 0, 1090 static_cast<unsigned>(_depData.ncol() - 1)); 1091 OPENSIM_THROW_IF(isColumnIndexOutOfRange(columnStart + numColumns - 1), 1092 ColumnIndexOutOfRange, 1093 columnStart + numColumns - 1, 0, 1094 static_cast<unsigned>(_depData.ncol() - 1)); 1095 1096 return _depData.block(static_cast<int>(rowStart), 1097 static_cast<int>(columnStart), 1098 static_cast<int>(numRows), 1099 static_cast<int>(numColumns)); 1100 } 1101 1102 /** Get a writable view to the underlying matrix. */ updMatrix()1103 MatrixView& updMatrix() { 1104 return _depData.updAsMatrixView(); 1105 } 1106 1107 /** Get a writable view of a block of the underlying matrix. 1108 1109 \throws InvalidArgument If numRows or numColumns is zero. 1110 \throws EmptyTable If the table is empty. 1111 \throws RowIndexOutOfRange If one or more rows of the desired block is out 1112 of range of the matrix. 1113 \throws ColumnIndexOutOfRange If one or more columns of the desired block is 1114 out of range of the matrix. */ updMatrixBlock(size_t rowStart,size_t columnStart,size_t numRows,size_t numColumns)1115 MatrixView updMatrixBlock(size_t rowStart, 1116 size_t columnStart, 1117 size_t numRows, 1118 size_t numColumns) { 1119 OPENSIM_THROW_IF(numRows == 0 || numColumns == 0, 1120 InvalidArgument, 1121 "Either numRows or numColumns is zero."); 1122 OPENSIM_THROW_IF(isEmpty(), EmptyTable); 1123 OPENSIM_THROW_IF(isRowIndexOutOfRange(rowStart), 1124 RowIndexOutOfRange, 1125 rowStart, 0, 1126 static_cast<unsigned>(_depData.nrow() - 1)); 1127 OPENSIM_THROW_IF(isRowIndexOutOfRange(rowStart + numRows - 1), 1128 RowIndexOutOfRange, 1129 rowStart + numRows - 1, 0, 1130 static_cast<unsigned>(_depData.nrow() - 1)); 1131 OPENSIM_THROW_IF(isColumnIndexOutOfRange(columnStart), 1132 ColumnIndexOutOfRange, 1133 columnStart, 0, 1134 static_cast<unsigned>(_depData.ncol() - 1)); 1135 OPENSIM_THROW_IF(isColumnIndexOutOfRange(columnStart + numColumns - 1), 1136 ColumnIndexOutOfRange, 1137 columnStart + numColumns - 1, 0, 1138 static_cast<unsigned>(_depData.ncol() - 1)); 1139 1140 return _depData.updBlock(static_cast<int>(rowStart), 1141 static_cast<int>(columnStart), 1142 static_cast<int>(numRows), 1143 static_cast<int>(numColumns)); 1144 } 1145 1146 /// @} 1147 1148 /** Get a string representation of the table, including the key-value pairs 1149 in the table metadata. Table metadata will be of the form: 1150 \code 1151 key => value-converted-to-string 1152 \endcode 1153 For example: 1154 \code 1155 DataRate => 2000.00000 1156 Units => mm 1157 \endcode 1158 For values in the table metadata that do not support the operation of stream 1159 insertion (operator<<), the value for metadata will be: 1160 \code 1161 key => <cannot-convert-to-string> 1162 \endcode 1163 Some examples to call this function: 1164 \code 1165 // All rows, all columns. 1166 auto tableAsString = table.toString(); 1167 // First 5 rows, all columns. 1168 auto tableAsString = table.toString({0, 1, 2, 3, 4}); 1169 // All rows, 3 columns with specified labels. 1170 auto tableAsString = table.toString({}, {"col12", "col35", "col4"}); 1171 // Rows 5th, 3rd, 1st (in that order) and columns with specified labels (in 1172 // that order). 1173 auto tableAsString = table.toString({4, 2, 0}, {"col10", "col5", "col2"}); 1174 // Lets say the table has 10 rows. Following will get last 3 rows in the 1175 // order specified. All columns. 1176 auto tableAsString = table.toString({-1, -2, -3}) 1177 \endcode 1178 1179 \param rows **[Default = all rows]** Sequence of indices of rows to be 1180 printed. Rows will be printed exactly in the order specified in 1181 the sequence. Index begins at 0 (i.e. first row is 0). Negative 1182 indices refer to rows starting from last row. Index -1 refers to 1183 last row, -2 refers to row previous to last row and so on. 1184 Default behavior is to print all rows. 1185 \param columnLabels **[Default = all rows]** Sequence of labels of columns 1186 to be printed. Columns will be printed exactly in the 1187 order specified in the sequence. Default behavior is to 1188 print all columns. 1189 \param withMetaData **[Default = true]** Whether or not table metadata 1190 should be printed. Default behavior is to print table 1191 metadata. 1192 \param splitSize **[Default = 25]** Number of rows to print at a time. 1193 Default behavior is to print 25 rows at a time. 1194 \param maxWidth **[Default = 80]** Maximum number of characters to print per 1195 line. The columns are split accordingly to make the table 1196 readable. This is useful in terminals/consoles with narrow 1197 width. Default behavior is to limit number characters per 1198 line to 80. 1199 \param precision **[Default = 4]** Precision of the floating-point numbers 1200 printed. Default behavior is to print floating-point 1201 numbers with 4 places to the right of decimal point. */ 1202 std::string toString(std::vector<int> rows = {}, 1203 std::vector<std::string> columnLabels = {}, 1204 const bool withMetaData = true, 1205 unsigned splitSize = 25, 1206 unsigned maxWidth = 80, 1207 unsigned precision = 4) const { 1208 std::vector<int> cols{}; 1209 for(const auto& label : columnLabels) 1210 cols.push_back(static_cast<int>(getColumnIndex(label))); 1211 return toString_impl(rows, cols, withMetaData, 1212 splitSize, maxWidth, precision); 1213 } 1214 1215 protected: 1216 /** Convenience constructor to efficiently populate a DataTable from 1217 known data. This is primarily useful for reading in large data tables 1218 without having to reallocate and copy memory. NOTE derived tables 1219 must validate the table according to the needs of the concrete type. 1220 The virtual validateRow() overridden by derived types cannot be 1221 invoked here - that is by the base class. A derived class must perform 1222 its own validation by invoking its own validateRow() implementation. */ DataTable_(const std::vector<ETX> & indVec,const SimTK::Matrix_<ETY> & depData,const std::vector<std::string> & labels)1223 DataTable_(const std::vector<ETX>& indVec, 1224 const SimTK::Matrix_<ETY>& depData, 1225 const std::vector<std::string>& labels) { 1226 OPENSIM_THROW_IF(indVec.size() != depData.nrow(), InvalidArgument, 1227 "Length of independent column does not match number of rows of " 1228 "dependent data."); 1229 OPENSIM_THROW_IF(labels.size() != depData.ncol(), InvalidArgument, 1230 "Number of labels does not match number of columns of " 1231 "dependent data."); 1232 1233 setColumnLabels(labels); 1234 _indData = indVec; 1235 _depData = depData; 1236 } 1237 1238 /** Construct a table with only the independent column and 0 1239 dependent columns. This constructor is useful when populating the table by 1240 appending columns rather than by appending rows. */ DataTable_(const std::vector<ETX> & indVec)1241 DataTable_(const std::vector<ETX>& indVec) { 1242 setColumnLabels({}); 1243 _indData = indVec; 1244 _depData.resize((int)indVec.size(), 0); 1245 } 1246 1247 // Implement toString. 1248 std::string toString_impl(std::vector<int> rows = {}, 1249 std::vector<int> cols = {}, 1250 const bool withMetaData = true, 1251 unsigned splitSize = 25, 1252 unsigned maxWidth = 80, 1253 unsigned precision = 4) const 1254 { 1255 if (isEmpty()) 1256 return "(Table is empty)\n"; 1257 1258 static_assert(std::is_same<ETX, double>::value, 1259 "This function can only be called for a table with " 1260 "independent column of type 'double'."); 1261 1262 // Defaults. 1263 const unsigned defSplitSize{25}; 1264 const unsigned defMaxWidth{80}; 1265 const unsigned defPrecision{4}; 1266 const unsigned columnSpacing{1}; 1267 const float excessAllocation{1.25}; 1268 const char rowNumSepChar{':'}; 1269 const char fillChar{' '}; 1270 const char newlineChar{'\n'}; 1271 const std::string indColLabel{"time"}; 1272 const std::string suffixChar{"_"}; 1273 const std::string metaDataSep{" => "}; 1274 1275 // Set all the un-specified parameters to defaults. 1276 if(splitSize == 0) 1277 splitSize = defSplitSize; 1278 if(defMaxWidth == 0) 1279 maxWidth = defMaxWidth; 1280 if(precision == 0) 1281 precision = defPrecision; 1282 if(rows.empty()) 1283 for(size_t i = 0u; i < getNumRows() ; ++i) 1284 rows.push_back(static_cast<int>(i)); 1285 if(cols.empty()) 1286 for(size_t i = 0u; i < getNumColumns(); ++i) 1287 cols.push_back(static_cast<int>(i)); 1288 1289 auto toStr = [&] (const double val) { 1290 std::ostringstream stream{}; 1291 stream << std::fixed << std::setprecision(precision) << val; 1292 return stream.str(); 1293 }; 1294 1295 std::vector<std::vector<std::string>> table{}; 1296 1297 // Fill up column labels, including row-number label (empty string), 1298 // time column label and all the column labels from table. 1299 table.push_back({std::string{}, indColLabel}); 1300 for(int col : cols) { 1301 if(col < 0) 1302 col += static_cast<int>(getNumColumns()); 1303 if(numComponentsPerElement() == 1) 1304 table.front().push_back(getColumnLabel(col)); 1305 else 1306 for(unsigned c = 0; c < numComponentsPerElement(); ++c) 1307 table.front().push_back(getColumnLabel(col) + 1308 suffixChar + 1309 std::to_string(c + 1)); 1310 } 1311 1312 // Fill up the rows, including row-number, time column, row data. 1313 for(int row : rows) { 1314 if(row < 0) 1315 row += static_cast<int>(getNumRows()); 1316 std::vector<std::string> rowData{}; 1317 rowData.push_back(std::to_string(row) + rowNumSepChar); 1318 rowData.push_back(toStr(getIndependentColumn()[row])); 1319 for(const auto& col : cols) 1320 for(const auto& comp : 1321 splitElement(getMatrix().getElt(row, col))) 1322 rowData.push_back(toStr(comp)); 1323 table.push_back(std::move(rowData)); 1324 } 1325 1326 // Compute width of each column. 1327 std::vector<size_t> columnWidths(table.front().size(), 0); 1328 for(const auto& row : table) { 1329 for(unsigned col = 0; col < row.size(); ++col) 1330 columnWidths.at(col) = 1331 std::max(columnWidths.at(col), 1332 row[col].length() + columnSpacing); 1333 } 1334 columnWidths.front() -= 1; 1335 1336 std::string result{}; 1337 1338 // Fill up metadata. 1339 if(withMetaData) { 1340 for(const auto& key : getTableMetaDataKeys()) { 1341 result.append(key); 1342 result.append(metaDataSep); 1343 result.append(getTableMetaDataAsString(key)); 1344 result.push_back(newlineChar); 1345 } 1346 } 1347 1348 const size_t totalWidth{std::accumulate(columnWidths.cbegin(), 1349 columnWidths.cend(), 1350 static_cast<size_t>(0))}; 1351 result.reserve(result.capacity() + static_cast<unsigned>( 1352 totalWidth * table.size() * excessAllocation)); 1353 1354 // Fill up the result string. 1355 size_t beginRow{1}; 1356 size_t endRow{std::min(beginRow + splitSize, table.size())}; 1357 while(beginRow < endRow) { 1358 size_t beginCol{1}; 1359 size_t endCol{columnWidths.size()}; 1360 while(beginCol < endCol) { 1361 size_t width = std::accumulate(columnWidths.cbegin() + beginCol, 1362 columnWidths.cbegin() + endCol, 1363 static_cast<size_t>(0)); 1364 while(width > maxWidth) { 1365 --endCol; 1366 width -= columnWidths[endCol]; 1367 } 1368 result.append(columnWidths[0], fillChar); 1369 for(size_t col = beginCol; col < endCol; ++col) { 1370 result.append(columnWidths[col] - table[0][col].length(), 1371 fillChar); 1372 result.append(table[0][col]); 1373 } 1374 result.push_back(newlineChar); 1375 for(size_t row = beginRow; row < endRow; ++row) { 1376 result.append(columnWidths[0] - table[row][0].length(), 1377 fillChar); 1378 result.append(table[row][0]); 1379 for(size_t col = beginCol; col < endCol; ++col) { 1380 result.append(columnWidths[col] - 1381 table[row][col].length(), fillChar); 1382 result.append(table[row][col]); 1383 } 1384 result.push_back(newlineChar); 1385 } 1386 beginCol = endCol; 1387 endCol = columnWidths.size(); 1388 } 1389 beginRow = endRow; 1390 endRow = std::min(beginRow + splitSize, table.size()); 1391 } 1392 return result; 1393 } 1394 1395 // Split element into constituent components and assign the components 1396 // according to the iterator argument. This function will write N elements 1397 // starting from *begin* but not necessarily up to *end*. An exception is 1398 // thrown if *end* is reached before assigning all components. 1399 // Example: Vec3 has 3 components. 1400 template<int N, typename Iter> 1401 static splitAndAssignElement(Iter begin,Iter end,const SimTK::Vec<N> & elem)1402 void splitAndAssignElement(Iter begin, Iter end, 1403 const SimTK::Vec<N>& elem) { 1404 for(unsigned i = 0; i < N; ++i) { 1405 OPENSIM_THROW_IF(begin == end, 1406 Exception, 1407 "Iterators do not produce enough elements. " 1408 "Expected: " + std::to_string(N) + " Received: " + 1409 std::to_string(i)); 1410 1411 *begin++ = elem[i]; 1412 } 1413 } 1414 // Split element into constituent components and assign the components 1415 // according to the iterator argument. This function will write M*N elements 1416 // starting from *begin* but not necessarily up to *end*. An exception is 1417 // thrown if *end* is reached before assigning all components. 1418 // Example: Vec<2, Vec3> has 6 components. 1419 template<int M, int N, typename Iter> 1420 static splitAndAssignElement(Iter begin,Iter end,const SimTK::Vec<M,SimTK::Vec<N>> & elem)1421 void splitAndAssignElement(Iter begin, Iter end, 1422 const SimTK::Vec<M, SimTK::Vec<N>>& elem) { 1423 for(unsigned i = 0; i < M; ++i) { 1424 for(unsigned j = 0; j < N; ++j) { 1425 OPENSIM_THROW_IF(begin == end, 1426 Exception, 1427 "Iterators do not produce enough elements. " 1428 "Expected: " + std::to_string(M * N) + 1429 " Received: " + std::to_string((i + 1) * j)); 1430 1431 *begin++ = elem[i][j]; 1432 } 1433 } 1434 } 1435 // Split element into constituent components and assign the components 1436 // according to the iterator argument. This function will write MxN matrix 1437 // elements starting from *begin* but not necessarily up to *end*. 1438 // Elements are written out row-wise. 1439 // An exception is thrown if *end* is reached before assigning all components. 1440 // Example: Mat<3, 3> has 9 components. 1441 template<int M, int N, typename Iter> 1442 static splitAndAssignElement(Iter begin,Iter end,const SimTK::Mat<M,N> & elem)1443 void splitAndAssignElement(Iter begin, Iter end, 1444 const SimTK::Mat<M, N>& elem) { 1445 for (unsigned i = 0; i < M; ++i) { 1446 for (unsigned j = 0; j < N; ++j) { 1447 OPENSIM_THROW_IF(begin == end, 1448 Exception, 1449 "Iterators do not produce enough elements. " 1450 "Expected: " + std::to_string(M * N) + 1451 " Received: " + std::to_string((i + 1) * j)); 1452 1453 *begin++ = elem[i][j]; 1454 } 1455 } 1456 } 1457 // Unsupported type. 1458 template<typename Iter> 1459 static splitAndAssignElement(Iter begin,Iter end,...)1460 void splitAndAssignElement(Iter begin, Iter end, 1461 ...) { 1462 static_assert(!std::is_same<ETY, double>::value, 1463 "This constructor cannot be used to construct from " 1464 "DataTable<double, ThatETY> where ThatETY is an " 1465 "unsupported type."); 1466 } 1467 template<typename ELT> 1468 static splitElement(const ELT & elt)1469 SimTK::RowVector_<double> splitElement(const ELT& elt) { 1470 SimTK::RowVector_<double> result(numComponentsPerElement_impl(elt)); 1471 splitAndAssignElement(result.begin(), result.end(), elt); 1472 return result; 1473 } 1474 static splitElement(const double & elt)1475 SimTK::RowVector_<double> splitElement(const double& elt) { 1476 return SimTK::RowVector_<double>(1, elt); 1477 } 1478 1479 template<typename Iter> 1480 static makeElement_helper(double & elem,Iter begin,Iter end)1481 void makeElement_helper(double& elem, 1482 Iter begin, Iter end) { 1483 OPENSIM_THROW_IF(begin == end, 1484 Exception, 1485 "Iterators do not produce enough elements. " 1486 "Expected: 1 Received: 0"); 1487 elem = *begin; 1488 } 1489 template<int N, typename Iter> 1490 static makeElement_helper(SimTK::Vec<N> & elem,Iter begin,Iter end)1491 void makeElement_helper(SimTK::Vec<N>& elem, 1492 Iter begin, Iter end) { 1493 for(unsigned i = 0; i < N; ++i) { 1494 OPENSIM_THROW_IF(begin == end, 1495 Exception, 1496 "Iterators do not produce enough elements. " 1497 "Expected: " + std::to_string(N) + " Received: " + 1498 std::to_string(i)); 1499 1500 elem[i] = *begin++; 1501 } 1502 } 1503 template<int M, int N, typename Iter> 1504 static makeElement_helper(SimTK::Vec<M,SimTK::Vec<N>> & elem,Iter begin,Iter end)1505 void makeElement_helper(SimTK::Vec<M, SimTK::Vec<N>>& elem, 1506 Iter begin, Iter end) { 1507 for(unsigned i = 0; i < M; ++i) { 1508 for(unsigned j = 0; j < N; ++j) { 1509 OPENSIM_THROW_IF(begin == end, 1510 Exception, 1511 "Iterators do not produce enough elements." 1512 "Expected: " + std::to_string(M * N) + 1513 " Received: " + std::to_string((i + 1) * j)); 1514 1515 elem[i][j] = *begin++; 1516 } 1517 } 1518 } 1519 template<int M, int N, typename Iter> 1520 static makeElement_helper(SimTK::Mat<M,N> & elem,Iter begin,Iter end)1521 void makeElement_helper(SimTK::Mat<M, N>& elem, 1522 Iter begin, Iter end) { 1523 for (unsigned i = 0; i < M; ++i) { 1524 for (unsigned j = 0; j < N; ++j) { 1525 OPENSIM_THROW_IF(begin == end, 1526 Exception, 1527 "Iterators do not produce enough elements." 1528 "Expected: " + std::to_string(M * N) + 1529 " Received: " + std::to_string((i + 1) * j)); 1530 1531 elem[i][j] = *begin++; 1532 } 1533 } 1534 } 1535 template<typename Iter> 1536 static makeElement(Iter begin,Iter end)1537 ETY makeElement(Iter begin, Iter end) { 1538 ETY elem{}; 1539 makeElement_helper(elem, begin, end); 1540 return elem; 1541 } 1542 1543 /** Determine whether table is empty. */ isEmpty()1544 bool isEmpty() const { 1545 return getNumRows() == 0 || getNumColumns() == 0; 1546 } 1547 1548 /** Check if row index is out of range. */ isRowIndexOutOfRange(size_t index)1549 bool isRowIndexOutOfRange(size_t index) const { 1550 return index >= _indData.size(); 1551 } 1552 1553 /** Check if column index is out of range. */ isColumnIndexOutOfRange(size_t index)1554 bool isColumnIndexOutOfRange(size_t index) const { 1555 return index >= static_cast<size_t>(_depData.ncol()); 1556 } 1557 1558 /** Get number of rows. */ implementGetNumRows()1559 size_t implementGetNumRows() const override { 1560 return _depData.nrow(); 1561 } 1562 1563 /** Get number of columns. */ implementGetNumColumns()1564 size_t implementGetNumColumns() const override { 1565 return _depData.ncol(); 1566 } 1567 1568 /** Validate metadata for independent column. 1569 1570 \throws MissingMetaData If independent column's metadata does not contain 1571 a key named "labels". */ validateIndependentMetaData()1572 void validateIndependentMetaData() const override { 1573 try { 1574 _independentMetaData.getValueForKey("labels"); 1575 } catch(KeyNotFound&) { 1576 OPENSIM_THROW(MissingMetaData, "labels"); 1577 } 1578 } 1579 1580 /** Validate metadata for dependent columns. 1581 1582 \throws MissingMetaData If metadata for dependent columns does not 1583 contain a key named "labels". 1584 \throws IncorrectMetaDataLength (1) If ValueArray for key "labels" does not 1585 have length equal to the number of columns in the 1586 table. (2) If not all entries in the metadata for 1587 dependent columns have the correct length (equal to 1588 number of columns). 1589 \throws InvalidColumnLabel (1) if label is an empty string, (2) if label 1590 contains tab or newline characters, or (3) if 1591 label has leading or trailing spaces.*/ validateDependentsMetaData()1592 void validateDependentsMetaData() const override { 1593 size_t numCols{}; 1594 1595 if (!_dependentsMetaData.hasKey("labels")) { 1596 OPENSIM_THROW(MissingMetaData, "labels"); 1597 } 1598 1599 const auto labels = getColumnLabels(); 1600 numCols = labels.size(); 1601 1602 // validate each label individually 1603 for (const auto& label : labels) { 1604 OPENSIM_THROW_IF(label.empty(), 1605 InvalidColumnLabel, 1606 "Empty column labels are not permitted."); 1607 1608 OPENSIM_THROW_IF( 1609 label.find_first_of("\t\r\n") != std::string::npos, 1610 InvalidColumnLabel, 1611 "Tabs and newlines are not permitted in column labels."); 1612 1613 auto front = label.find_first_not_of(" "); 1614 auto back = label.find_last_not_of(" "); 1615 OPENSIM_THROW_IF((front != 0 || back != label.size()-1), 1616 InvalidColumnLabel, 1617 "Leading/trailing spaces are not permitted in column labels."); 1618 } 1619 1620 OPENSIM_THROW_IF(_depData.ncol() != 0 && 1621 numCols != static_cast<unsigned>(_depData.ncol()), 1622 IncorrectMetaDataLength, "labels", 1623 static_cast<size_t>(_depData.ncol()), numCols); 1624 1625 for(const std::string& key : _dependentsMetaData.getKeys()) { 1626 OPENSIM_THROW_IF(numCols != 1627 _dependentsMetaData.getValueArrayForKey(key).size(), 1628 IncorrectMetaDataLength, key, numCols, 1629 _dependentsMetaData.getValueArrayForKey(key).size()); 1630 } 1631 } 1632 1633 /** Derived classes optionally can implement this function to validate 1634 append/update operations. 1635 1636 \throws InvalidRow If the given row considered invalid by the derived 1637 class. */ validateRow(size_t rowIndex,const ETX &,const RowVector &)1638 virtual void validateRow(size_t rowIndex, 1639 const ETX&, 1640 const RowVector&) const { 1641 // No operation. 1642 } 1643 1644 static constexpr numComponentsPerElement_impl(double)1645 unsigned numComponentsPerElement_impl(double) { 1646 return 1; 1647 } 1648 template<int M> 1649 static constexpr numComponentsPerElement_impl(SimTK::Vec<M>)1650 unsigned numComponentsPerElement_impl(SimTK::Vec<M>) { 1651 return M; 1652 } 1653 template<int M, int N> 1654 static constexpr numComponentsPerElement_impl(SimTK::Mat<M,N>)1655 unsigned numComponentsPerElement_impl(SimTK::Mat<M, N>) { 1656 return M * N; 1657 } 1658 template<int M, int N> 1659 static constexpr numComponentsPerElement_impl(SimTK::Vec<M,SimTK::Vec<N>>)1660 unsigned numComponentsPerElement_impl(SimTK::Vec<M, SimTK::Vec<N>>) { 1661 return M * N; 1662 } 1663 1664 std::vector<ETX> _indData; 1665 SimTK::Matrix_<ETY> _depData; 1666 }; // DataTable_ 1667 1668 1669 /** Print DataTable out to a stream. */ 1670 template<typename ETX, typename ETY> 1671 std::ostream& operator<<(std::ostream& outStream, 1672 const DataTable_<ETX, ETY>& table) { 1673 return (outStream << table.toString()); 1674 } 1675 1676 /** See DataTable_ for details on the interface. */ 1677 typedef DataTable_<double, double> DataTable; 1678 /** See DataTable_ for details on the interface. */ 1679 typedef DataTable_<double, SimTK::Vec3> DataTableVec3; 1680 1681 } // namespace OpenSim 1682 1683 #endif //OPENSIM_DATA_TABLE_H_ 1684