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