1 /* -------------------------------------------------------------------------- *
2 * OpenSim: Storage.cpp *
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 * Author(s): Frank C. Anderson *
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 /* Note: This code was originally developed by Realistic Dynamics Inc.
25 * Author: Frank C. Anderson
26 */
27
28
29 // INCLUDES
30 #include <iostream>
31 #include "IO.h"
32 #include "Signal.h"
33 #include "Storage.h"
34 #include "GCVSplineSet.h"
35 #include "SimmMacros.h"
36 #include "SimTKcommon.h"
37 #include "GCVSpline.h"
38 #include "StateVector.h"
39 #include "STOFileAdapter.h"
40 #include "TimeSeriesTable.h"
41
42 using namespace OpenSim;
43 using namespace std;
44
convertTableToStorage(const AbstractDataTable * table,Storage & sto)45 void convertTableToStorage(const AbstractDataTable* table, Storage& sto)
46 {
47 sto.purge();
48 TimeSeriesTable out;
49
50 if (auto td = dynamic_cast<const TimeSeriesTable*>(table))
51 // Table is already flattened, so clone for further processing
52 out = TimeSeriesTable{ *td };
53 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec2>*>(table))
54 out = tst->flatten();
55 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec3>*>(table))
56 out = tst->flatten({ "_x", "_y", "_z" });
57 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec4>*>(table))
58 out = tst->flatten();
59 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec5>*>(table))
60 out = tst->flatten();
61 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec6>*>(table))
62 out = tst->flatten();
63 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec7>*>(table))
64 out = tst->flatten();
65 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec8>*>(table))
66 out = tst->flatten();
67 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec9>*>(table))
68 out = tst->flatten();
69 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec<10>>*>(table))
70 out = tst->flatten();
71 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec<11>>*>(table))
72 out = tst->flatten();
73 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Vec<12>>*>(table))
74 out = tst->flatten();
75 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::UnitVec3>*>(table))
76 out = tst->flatten({ "_x", "_y", "_z" });
77 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::Quaternion>*>(table))
78 out = tst->flatten();
79 else if (auto tst = dynamic_cast<const TimeSeriesTable_<SimTK::SpatialVec>*>(table))
80 out = tst->flatten({ "_rx", "_ry", "_rz", "_tx", "_ty", "_tz" });
81 else {
82 OPENSIM_THROW( STODataTypeNotSupported, typeid(table).name());
83 }
84
85 OpenSim::Array<std::string> labels("", (int)out.getNumColumns() + 1);
86 labels[0] = "time";
87 for (int i = 0; i < (int)out.getNumColumns(); ++i) {
88 labels[i + 1] = out.getColumnLabel(i);
89 }
90 sto.setColumnLabels(labels);
91
92 const auto& times = out.getIndependentColumn();
93 for (unsigned i_time = 0; i_time < out.getNumRows(); ++i_time) {
94 const SimTK::Vector rowVector =
95 out.getRowAtIndex(i_time).transpose().getAsVector();
96 sto.append(times[i_time], rowVector);
97 }
98 }
99
100
101 //============================================================================
102 // DEFINES
103 //============================================================================
104
105
106 //============================================================================
107 // CONSTANTS
108 //============================================================================
109 //const double Storage::LARGE_NEGATIVE = -1.0e-30;
110 //const double Storage::LARGE_POSITIVE = 1.0e-30;
111 const char* Storage::DEFAULT_HEADER_TOKEN = "endheader";
112 const char* Storage::DEFAULT_HEADER_SEPARATOR = " \t\r\n";
113 const int Storage::MAX_RESAMPLE_SIZE = 100000;
114 //============================================================================
115 // STATICS
116 //============================================================================
117 string Storage::simmReservedKeys[] = {
118 "#",
119 "range", "Range",
120 "wrap", "Wrap", "WRAP",
121 "event",
122 "enforce_loops",
123 "enforce_constraints",
124 "calc_derivatives"};
125 int numSimmReservedKeys=10; // Keep this number in sync with above array size
126
127 // up version to 20301 for separation of RRATool, CMCTool
128 const int Storage::LatestVersion = 1;
129
130 //=============================================================================
131 // DESTRUCTOR
132 //=============================================================================
133 //_____________________________________________________________________________
134 /**
135 * Destructor.
136 *
137 * The stored StateVectors are deleted during destruction.
138 */
~Storage()139 Storage::~Storage()
140 {
141 }
142
143 //=============================================================================
144 // CONSTRUCTOR(S)
145 //=============================================================================
146 //_____________________________________________________________________________
147 /**
148 * Default constructor.
149 */
Storage(int aCapacity,const string & aName)150 Storage::Storage(int aCapacity,const string &aName) :
151 StorageInterface(aName),
152 _storage(StateVector())
153 {
154 // SET NULL STATES
155 setNull();
156
157 // CAPACITY
158 _storage.ensureCapacity(aCapacity);
159 _storage.setCapacityIncrement(-1);
160
161 _fileVersion = Storage::LatestVersion;
162 // SET THE STATES
163 setName(aName);
164 }
165 //_____________________________________________________________________________
166 /*
167 * Construct an Storage instance from file.
168 *
169 */
Storage(const string & fileName,bool readHeadersOnly)170 Storage::Storage(const string &fileName, bool readHeadersOnly) :
171 StorageInterface(fileName),
172 _storage(StateVector())
173 {
174 // SET NULL STATES
175 setNull();
176
177 // OPEN FILE
178 std::unique_ptr<ifstream> fp{IO::OpenInputFile(fileName)};
179 OPENSIM_THROW_IF(fp == nullptr, Exception,
180 "Storage: Failed to open file '" + fileName +
181 "'. Verify that the file exists at the specified location." );
182
183 bool isMotFile = SimTK::String::toLower(fileName).rfind(".mot") != string::npos;
184 bool isStoFile = SimTK::String::toLower(fileName).rfind(".sto") != string::npos;
185 bool useFileAdpater = true;
186
187 int nr = 0, nc = 0;
188
189 if (isMotFile || isStoFile) {
190 parseHeaders(*fp, nr, nc);
191 if (_fileVersion <= 1) { // If an old .sto or .mot format
192 // Must have valid number of rows and columns
193 OPENSIM_THROW_IF(nr < 1 && nc < 1, Exception,
194 "Storage: Failed to parse headers of file "
195 + fileName);
196 // Checks out as a valid old format, so use legacy code to process
197 useFileAdpater = false;
198 }
199 }
200
201 if (useFileAdpater) { // For new .sto files and others that are not .mot
202 try {
203 // Try using FileAdpater to read all file types
204 OPENSIM_THROW_IF(readHeadersOnly, Exception,
205 "Cannot read headers only if not a STO file or its "
206 "version is greater than 1.");
207 auto dataAdapter = FileAdapter::createAdapterFromExtension(fileName);
208 FileAdapter::OutputTables tables = dataAdapter->read(fileName);
209 if (tables.size() > 1) {
210 cout << "Storage: cannot read data files with multiple tables. "
211 << "Only the first table '" << tables.begin()->first << "' will "
212 << "be loaded as Storage." << endl;
213 }
214 convertTableToStorage(tables.begin()->second.get(), *this);
215 return;
216 }
217 catch (const std::exception& x) {
218 cout << "Storage: FileAdpater failed to read data file.\n"
219 << x.what() << endl;
220 if (isStoFile)
221 cout << "Reverting to use conventional Storage reader." << endl;
222 else
223 throw x;
224 }
225 }
226
227 // Process file as if it were a .mot file
228 if (getDebugLevel() >= 0) {
229 cout << "Storage: read data file =" << fileName
230 << " (nr=" << nr << " nc=" << nc << ")" << endl;
231 }
232 // Motion files from SIMM are in degrees
233 if (_fileVersion < 1 && isMotFile) {
234 _inDegrees = true;
235 }
236 if (_fileVersion < 1) {
237 cout << ".. assuming rotations in "
238 << (_inDegrees?"Degrees.":"Radians.") << endl;
239 }
240
241 // IGNORE blank lines after header -- treat \r and \n as end of line chars
242 while(fp->good()) {
243 int c = fp->peek();
244 if(c!='\n' && c!='\r' && c!='\t' && c!=' ') break;
245 fp->get();
246 }
247 // Ayman: Support for oldStyleDescription dropped. 04/11/07
248
249 // COLUMN LABELS
250 string line;
251 getline(*fp, line);
252 parseColumnLabels(line.c_str());
253
254 if (_columnLabels.getSize()!= nc){
255 std::cout << "Storage: Warning- inconsistent headers in file " <<
256 fileName << ". nColumns=" << nc << " but "
257 << _columnLabels.getSize() << " were found" << std::endl;
258 }
259 // CAPACITY
260 _storage.ensureCapacity(nr);
261 _storage.setCapacityIncrement(-1);
262
263 // There are situations where we don't want to read the whole file in advance just header
264 if (readHeadersOnly) return;
265
266 //MM using the occurrence of time and range in the column labels to distinguish between
267 //SIMM and non SIMMOtion files.
268 Array<std::string> currentLabels = getColumnLabels();
269 int indexTime = currentLabels.findIndex("time");
270 int indexRange = currentLabels.findIndex("range");
271
272
273 // DATA
274 if(indexTime != -1 || indexRange != -1){ //MM edit
275 int ny = nc-1;
276 double time;
277 double *y = new double[ny];
278 for(int r=0;r<nr;r++) {
279 (*fp)>>time;
280 for(int i=0;i<ny;i++)
281 (*fp)>>y[i];
282 append(time,ny,y);
283 }
284 delete[] y;
285 }else{ //MM the modifications below are to make the Storage class
286 //well behaved when it is given data that does not contain a
287 //time or a range column
288 int ny = nc;
289 double time;
290 double *y = new double[ny];
291 for(int r=0;r<nr;r++) {
292 time=(double)r;
293 for(int i=0;i<ny;i++)
294 (*fp)>>y[i];
295 append(time,ny,y);
296 }
297 delete[] y;
298 }
299 // If what we read was really a sIMM motion file, adjust the data
300 // to account for different assumptions between SIMM.mot OpenSim.sto
301
302 //MM if this is a SIMM Motion file, post process it as one. Else don't touch the data
303 if(indexTime == -1){
304 postProcessSIMMMotion();
305 }
306 }
307 //_____________________________________________________________________________
308 /**
309 * Copy constructor.
310 */
Storage(const Storage & aStorage,bool aCopyData)311 Storage::Storage(const Storage &aStorage,bool aCopyData) :
312 StorageInterface(aStorage),
313 _storage(StateVector())
314 {
315 // NULL THE DATA
316 setNull();
317
318 // CAPACITY
319 _storage.ensureCapacity(aStorage._storage.getCapacity());
320 _storage.setCapacityIncrement(aStorage._storage.getCapacityIncrement());
321
322 // SET STATES
323 setName(aStorage.getName());
324 setDescription(aStorage.getDescription());
325 setHeaderToken(aStorage.getHeaderToken());
326 setColumnLabels(aStorage.getColumnLabels());
327 setStepInterval(aStorage.getStepInterval());
328 setInDegrees(aStorage.isInDegrees());
329 _fileVersion = aStorage._fileVersion;
330 // COPY STORED DATA
331 if(aCopyData) copyData(aStorage);
332 }
333 //_____________________________________________________________________________
334 /**
335 * Construct a copy of a specified storage taking only a subset of the states.
336 *
337 * @param aStorage Storage to be copied.
338 * @param aStateIndex Index of the state (column) at which to start the copy.
339 * @param aN Number of states to copy.
340 * @param aDelimiter Delimiter used to separate state labels (i.e., column
341 * labels). The delimiter is assumed to be a tab by default.
342 */
343 Storage::
Storage(const Storage & aStorage,int aStateIndex,int aN,const char * aDelimiter)344 Storage(const Storage &aStorage,int aStateIndex,int aN,
345 const char *aDelimiter) :
346 StorageInterface(aStorage),
347 _storage(StateVector())
348 {
349 // NULL THE DATA
350 setNull();
351
352 // CAPACITY
353 _storage.ensureCapacity(aStorage._storage.getCapacity());
354 _storage.setCapacityIncrement(aStorage._storage.getCapacityIncrement());
355
356 // SET STATES
357 setName(aStorage.getName());
358 setDescription(aStorage.getDescription());
359 setHeaderToken(aStorage.getHeaderToken());
360 setStepInterval(aStorage.getStepInterval());
361 setInDegrees(aStorage.isInDegrees());
362 _fileVersion = aStorage._fileVersion;
363 // ERROR CHECK
364 if(aStateIndex<0) return;
365 if(aN<=0) return;
366
367 // SET THE DATA
368 int i,n;
369 double time,*data = new double[aN];
370 for(i=0;i<aStorage._storage.getSize();i++) {
371 aStorage.getTime(i,time);
372 n = aStorage.getData(i,aStateIndex,aN,data);
373 append(time,n,data);
374 }
375 delete[] data;
376
377 // COPY THE FIRST COLUMN AND COLUMNS aStateIndex+1 through aStateIndex+aN (corresponding to states aStateIndex - aStateIndex+aN-1)
378 int originalNumCol = aStorage.getColumnLabels().getSize();
379 _columnLabels.setSize(0);
380 if(originalNumCol) {
381 _columnLabels.append(aStorage.getColumnLabels()[0]);
382 for(int i=0;i<aN && aStateIndex+1+i<originalNumCol;i++)
383 _columnLabels.append(aStorage.getColumnLabels()[aStateIndex+1+i]);
384 }
385 }
386
387
388 /**
389 * Assignment operator.
390 *
391 * @return Reference to this object.
392 */
operator =(const Storage & aStorage)393 Storage& Storage::operator=(const Storage &aStorage)
394 {
395 // BASE CLASS
396 StorageInterface::operator=(aStorage);
397
398 // Copy Members
399 copyData(aStorage);
400 return(*this);
401 }
402
403
404 //=============================================================================
405 // CONSTRUCTION METHODS
406 //=============================================================================
407 //_____________________________________________________________________________
408 /**
409 * Set all states to their null or default values.
410 */
411 void Storage::
setNull()412 setNull()
413 {
414 _writeSIMMHeader = false;
415 setHeaderToken(DEFAULT_HEADER_TOKEN);
416 _stepInterval = 1;
417 _lastI = 0;
418 _fp = 0;
419 _inDegrees = false;
420 }
421 //_____________________________________________________________________________
422 /**
423 * Copy the data stored by another storage instance.
424 *
425 * Note that this method only copies the stored data. It does not copy
426 * other members of aStorage such as the name and the description. To get
427 * a complete copy, the copy constructor should be used.
428 *
429 * If this instance does not have enough capacity to hold the states
430 * of the specified storage (aStorage), the capacity is increased.
431 */
432 void Storage::
copyData(const Storage & aStorage)433 copyData(const Storage &aStorage)
434 {
435 _units = aStorage._units;
436 setInDegrees(aStorage.isInDegrees());
437
438 // ENSURE CAPACITY
439 _storage.ensureCapacity(aStorage._storage.getCapacity());
440
441 // COPY
442 //rdPtrArray::reset();
443 _storage.setSize(0);
444 for(int i=0;i<aStorage._storage.getSize();i++) {
445 _storage.append(aStorage._storage[i]);
446 }
447 }
448
449
450
451 //=============================================================================
452 // GET AND SET
453 //=============================================================================
454 //-----------------------------------------------------------------------------
455 // SIMM HEADER
456 //-----------------------------------------------------------------------------
457 //_____________________________________________________________________________
458 /**
459 * Set the whether or not to write a header appropriate for a SIMM motion
460 * file.
461 *
462 * @param aTrueFalse Whether (true) or not (false) to write a SIMM header.
463 */
464 void Storage::
setWriteSIMMHeader(bool aTrueFalse)465 setWriteSIMMHeader(bool aTrueFalse)
466 {
467 _writeSIMMHeader = aTrueFalse;
468 if (_writeSIMMHeader) setInDegrees(true);
469 }
470 //_____________________________________________________________________________
471 /**
472 * Get the whether or not to write a header appropriate for a SIMM motion
473 * file.
474 *
475 * @param aTrueFalse Whether (true) or not (false) to write a SIMM header.
476 */
477 bool Storage::
getWriteSIMMHeader() const478 getWriteSIMMHeader() const
479 {
480 return(_writeSIMMHeader);
481 }
482
483 //-----------------------------------------------------------------------------
484 // HEADER TOKEN
485 //-----------------------------------------------------------------------------
486 //_____________________________________________________________________________
487 /**
488 * Set the header token.
489 * The header token is used to mark the end of the header
490 * portion of an Storage when an Storage is saved in a file.
491 *
492 * If the header token is NULL, a default header token is used.
493 *
494 * @param aToken Header token.
495 */
496 void Storage::
setHeaderToken(const std::string & aToken)497 setHeaderToken(const std::string &aToken)
498 {
499 if(aToken.empty()) _headerToken = DEFAULT_HEADER_TOKEN;
500 else _headerToken = aToken;
501 }
502 //_____________________________________________________________________________
503 /**
504 * Get the header token of this storage.
505 *
506 * @return Header token.
507 * @see setHeaderToken()
508 */
509 const string &Storage::
getHeaderToken() const510 getHeaderToken() const
511 {
512 return(_headerToken);
513 }
514
515 //-----------------------------------------------------------------------------
516 // COLUMN LABELS
517 //-----------------------------------------------------------------------------
518 //_____________________________________________________________________________
519 // added a default Parameter for startIndex. -Ayman
520 // TODO startIndex is being ignored.
521 const int Storage::
getStateIndex(const std::string & aColumnName,int startIndex) const522 getStateIndex(const std::string &aColumnName, int startIndex) const
523 {
524 int thisColumnIndex = -1;
525
526 // This uses the `do while(false)` idiom to run common code if one of a
527 // number of conditions succeeds (much like what a goto would be used for).
528 do {
529 thisColumnIndex = _columnLabels.findIndex(aColumnName);
530 if (thisColumnIndex != -1) break;
531
532 // 4.0 and its beta versions differ slightly in the absolute path but
533 // the <joint>/<coordinate>/value (or speed) will be common to both.
534 // Likewise, for muscle states <muscle>/activation (or fiber_length)
535 // must be common to the state variable (path) name and column label.
536 std::string shortPath = aColumnName;
537 std::string::size_type front = shortPath.find("/");
538 while (thisColumnIndex < 0 && front < std::string::npos) {
539 shortPath = shortPath.substr(front + 1, aColumnName.length());
540 thisColumnIndex = _columnLabels.findIndex(shortPath);
541 front = shortPath.find("/");
542 }
543 if (thisColumnIndex != -1) break;
544
545 // Assume column labels follow pre-v4.0 state variable labeling.
546 // Redo search with what the pre-v4.0 label might have been.
547
548 // First, try just the last element of the path.
549 std::string::size_type back = aColumnName.rfind("/");
550 std::string prefix = aColumnName.substr(0, back);
551 std::string shortName = aColumnName.substr(back + 1,
552 aColumnName.length() - back);
553 thisColumnIndex = _columnLabels.findIndex(shortName);
554 if (thisColumnIndex != -1) break;
555
556 // If that didn't work, specifically check for coordinate state names
557 // (<coord_name>/value and <coord_name>/speed) and muscle state names
558 // (<muscle_name>/activation <muscle_name>/fiber_length).
559 if (shortName == "value") {
560 // pre-v4.0 did not have "/value" so remove it if here
561 back = prefix.rfind("/");
562 shortName = prefix.substr(back + 1, prefix.length());
563 thisColumnIndex = _columnLabels.findIndex(shortName);
564 }
565 else if (shortName == "speed") {
566 // replace "/speed" (the v4.0 labeling for speeds) with "_u"
567 back = prefix.rfind("/");
568 shortName =
569 prefix.substr(back + 1, prefix.length() - back) + "_u";
570 thisColumnIndex = _columnLabels.findIndex(shortName);
571 }
572 else if (back < aColumnName.length()) {
573 // try replacing the '/' with '.' in the last segment
574 shortName = aColumnName;
575 shortName.replace(back, 1, ".");
576 back = shortName.rfind("/");
577 shortName = shortName.substr(back + 1,
578 shortName.length() - back);
579 thisColumnIndex = _columnLabels.findIndex(shortName);
580 }
581 if (thisColumnIndex != -1) break;
582
583 // If all of the above checks failed, return -1.
584 return -1;
585
586 } while (false);
587
588 // If we get here, we successfully found a column.
589 // Subtract 1 because time is included in the labels but not
590 // in the "state vector".
591 return thisColumnIndex - 1;
592 }
593
594
595 //_____________________________________________________________________________
596 /**
597 * Set a labels string for the columns in this Storage instance.
598 *
599 * A character string is used to label the columns. Each separate column
600 * label is usually delimited by a tab ("\t"), but any delimiter may
601 * be used.
602 *
603 * The first column is almost always "Time." The other columns
604 * correspond to the separate elements of a state vector (StateVector).
605 *
606 * If the labels string is set to NULL, the following default labels
607 * will be used when the Storage instance is saved to file:
608 *
609 * time state_0 state_1 state_2 ...
610 *
611 * @param aLabels Character string containing labels for the columns.
612 */
613 void Storage::
parseColumnLabels(const char * aLabels)614 parseColumnLabels(const char *aLabels)
615 {
616 _columnLabels.setSize(0);
617
618 // HANDLE NULL POINTER
619 if(aLabels==NULL) return;
620
621 // HANDLE ZERO LENGTH STRING
622 int len = (int)strlen(aLabels);
623 if(len==0) return;
624
625 // SET NEW
626 char *labelsCopy = new char[len+1];
627 if(aLabels[len-1]=='\n') {
628 // strip carriage return
629 strncpy(labelsCopy,aLabels,len-1);
630 labelsCopy[len-1] = 0;
631 } else {
632 strcpy(labelsCopy,aLabels);
633 }
634
635 // Parse
636 char *token = strtok(labelsCopy,DEFAULT_HEADER_SEPARATOR );
637 while(token!=NULL)
638 {
639 // Append column label
640 _columnLabels.append(token);
641
642 // Get next label
643 token = strtok( NULL, DEFAULT_HEADER_SEPARATOR );
644 }
645
646 delete[] labelsCopy;
647 }
648
649 //_____________________________________________________________________________
650 /**
651 * Set column labels array.
652 */
653 void Storage::
setColumnLabels(const Array<std::string> & aColumnLabels)654 setColumnLabels(const Array<std::string> &aColumnLabels)
655 {
656 _columnLabels = aColumnLabels;
657 }
658
659 //_____________________________________________________________________________
660 /**
661 * Get column labels array.
662 *
663 * @return Character string of column labels.
664 */
665 const Array<string> &Storage::
getColumnLabels() const666 getColumnLabels() const
667 {
668 return(_columnLabels);
669 }
670
671 //-----------------------------------------------------------------------------
672 // STEP INTERVAL
673 //-----------------------------------------------------------------------------
674 //_____________________________________________________________________________
675 /**
676 * Set the step interval.
677 */
678 void Storage::
setStepInterval(int aStepInterval)679 setStepInterval(int aStepInterval)
680 {
681 _stepInterval = aStepInterval;
682 if(_stepInterval<0) _stepInterval = 0;
683 }
684 //_____________________________________________________________________________
685 /**
686 * Get the step interval.
687 */
688 int Storage::
getStepInterval() const689 getStepInterval() const
690 {
691 return(_stepInterval);
692 }
693
694 //-----------------------------------------------------------------------------
695 // CAPACITY INCREMENT
696 //-----------------------------------------------------------------------------
697 //_____________________________________________________________________________
698 /**
699 * Set the capacity increment of this storage object. For details on what
700 * the capacity increment does see Array::setCapacityIncrement().
701 *
702 * @param aIncrement Capacity increment.
703 * @see Array
704 */
705 void Storage::
setCapacityIncrement(int aIncrement)706 setCapacityIncrement(int aIncrement)
707 {
708 _storage.setCapacityIncrement(aIncrement);
709 }
710 //_____________________________________________________________________________
711 /**
712 * Get the capacity increment of this storage object.
713 *
714 * @return Capacity increment of this storage. For details on what
715 * the capacity increment does see Array::setCapacityIncrement().
716 */
717 int Storage::
getCapacityIncrement() const718 getCapacityIncrement() const
719 {
720 return(_storage.getCapacityIncrement());
721 }
722
723 //-----------------------------------------------------------------------------
724 // STATEVECTORS
725 //-----------------------------------------------------------------------------
726 //_____________________________________________________________________________
727 /**
728 * Get the smallest number of states.
729 *
730 * Ordinarily, the number of states is the same in each state vector;
731 * however, this is not required.
732 * @return Smallest number of states.
733 */
734 int Storage::
getSmallestNumberOfStates() const735 getSmallestNumberOfStates() const
736 {
737 int n,nmin=0;
738 for(int i=0;i<_storage.getSize();i++) {
739 n = _storage[i].getSize();
740 if(i==0) {
741 nmin = n;
742 } else if(n<nmin) {
743 nmin = n;
744 }
745 }
746
747 return(nmin);
748 }
749 //_____________________________________________________________________________
750 /**
751 * Get the last states stored.
752 *
753 * @return Statevector. If no state vector is stored, NULL is returned.
754 */
755 StateVector* Storage::
getLastStateVector() const756 getLastStateVector() const
757 {
758 StateVector *vec = NULL;
759 try {
760 vec = &_storage.updLast();
761 } catch(const Exception&) {
762 //x.print(cout);
763 }
764 return(vec);
765 }
766 //_____________________________________________________________________________
767 /**
768 * Get the StateVector at a specified time index.
769 *
770 * @param aTimeIndex Time index at which to get the state vector:
771 * 0 <= aTimeIndex < _storage.getSize().
772 * @return Statevector. If no valid statevector exists at aTimeIndex, NULL
773 * is returned.
774 */
775 StateVector* Storage::
getStateVector(int aTimeIndex) const776 getStateVector(int aTimeIndex) const
777 {
778 return(&_storage.updElt(aTimeIndex));
779 }
780
781 //-----------------------------------------------------------------------------
782 // TIME
783 //-----------------------------------------------------------------------------
784 //_____________________________________________________________________________
785 /**
786 * Get the time of the first stored states.
787 *
788 * @return Time of the first stored states. If there is no stored state,
789 * the constant SimTK::NaN (not a number) is returned.
790 */
791 double Storage::
getFirstTime() const792 getFirstTime() const
793 {
794 if(_storage.getSize()<=0) {
795 return(SimTK::NaN);
796 }
797 return(_storage[0].getTime());
798 }
799 //_____________________________________________________________________________
800 /**
801 * Get the time of the last states.
802 *
803 * @return Time of the first stored states. If there is no stored state,
804 * the constant SimTK::NaN (not a number) is returned.
805 */
806 double Storage::
getLastTime() const807 getLastTime() const
808 {
809 if(_storage.getSize()<=0) {
810 return(SimTK::NaN);
811 }
812 return(_storage.getLast().getTime());
813 }
814 //_____________________________________________________________________________
815 /**
816 * Get the smallest time step.
817 *
818 * @return Smallest time step. If there are less than 2 state vectors, SimTK::Infinity is returned.
819 */
820 double Storage::
getMinTimeStep() const821 getMinTimeStep() const
822 {
823 double *time=NULL;
824 int n = getTimeColumn(time);
825 double dtmin = SimTK::Infinity;
826 for(int i=1; i<n; i++) {
827 double dt = time[i] - time[i-1];
828 if(dt<dtmin) dtmin = dt;
829 }
830 delete[] time;
831 return dtmin;
832 }
833 //_____________________________________________________________________________
834 /**
835 * Get the time at a specified time index for a specified state.
836 *
837 * @param aTimeIndex Time index (row) for which to get the time.
838 * @param rTime Time value.
839 * @param aStateIndex Index of the state for which to get the time.
840 * By default, aStateIndex has a value of -1, which means disregard whether
841 * or not there is a valid state- just get the time at aTimeIndex. If
842 * aStateIndex is non-negative, the time is returned only if there is a valid
843 * state at aStateIndex.
844 * @return true when the time was set, false when there was no valid state.
845 */
846 bool Storage::
getTime(int aTimeIndex,double & rTime,int aStateIndex) const847 getTime(int aTimeIndex,double &rTime,int aStateIndex) const
848 {
849 if(aTimeIndex<0) return false;
850 if(aTimeIndex>_storage.getSize()) return false;
851
852 // GET STATEVECTOR
853 StateVector &vec = _storage[aTimeIndex];
854
855 // CHECK FOR VALID STATE
856 if(aStateIndex >= vec.getSize()) return false;
857
858 // ASSIGN TIME
859 rTime = vec.getTime();
860 return true;
861 }
862 //_____________________________________________________________________________
863 /**
864 * Get the times for a specified state.
865 *
866 * @param rTime Array where times are set. If rTime is sent in as NULL,
867 * memory is allocated. If rTime is setn in as non-NULL, it is assumed that
868 * enough memory has been allocated at rTime to hold _storage.getSize() doubles.
869 * @param aStateIndex Index of the state for which to get the times.
870 * By default, aStateIndex has a value of -1, which means disregard whether
871 * or not there is a valid state- just get the times. If aStateIndex is
872 * non-negative, the time is set only if there is a valid state at aStateIndex.
873 * @return Number of times set. This can be less than _storage.getSize() if
874 * a state does not exist for all or a subset of the stored statevectors.
875 */
876 int Storage::
getTimeColumn(double * & rTimes,int aStateIndex) const877 getTimeColumn(double *&rTimes,int aStateIndex) const
878 {
879 if(_storage.getSize()<=0) return(0);
880
881 // ALLOCATE MEMORY
882 if(rTimes==NULL) {
883 rTimes = new double[_storage.getSize()];
884 }
885
886 // LOOP THROUGH STATEVECTORS
887 int i,nTimes;
888 StateVector *vec;
889 for(i=nTimes=0;i<_storage.getSize();i++) {
890 vec = getStateVector(i);
891 if(vec==NULL) continue;
892 if(aStateIndex >= vec->getSize()) continue;
893 rTimes[nTimes] = vec->getTime();
894 nTimes++;
895 }
896
897 return(nTimes);
898 }
899 int Storage::
getTimeColumn(Array<double> & rTimes,int aStateIndex) const900 getTimeColumn(Array<double> &rTimes,int aStateIndex) const
901 {
902 if(_storage.getSize()<=0) return(0);
903
904 rTimes.setSize(_storage.getSize());
905
906 // LOOP THROUGH STATEVECTORS
907 int i,nTimes;
908 for(i=nTimes=0;i<_storage.getSize();i++) {
909 StateVector *vec = getStateVector(i);
910 if(vec==NULL) continue;
911 if(aStateIndex >= vec->getSize()) continue;
912 rTimes[nTimes] = vec->getTime();
913 nTimes++;
914 }
915
916 rTimes.setSize(nTimes);
917
918 return(nTimes);
919 }
920 /**
921 * Get the time column starting at aTime. Return it in rTimes
922 * rTimes is preallocated by the caller.
923 */
924 void Storage::
getTimeColumnWithStartTime(Array<double> & rTimes,double aStartTime) const925 getTimeColumnWithStartTime(Array<double>& rTimes,double aStartTime) const
926 {
927 if(_storage.getSize()<=0) return;
928
929 int startIndex = findIndex(aStartTime);
930
931 double *timesVec=0;
932 int size = getTimeColumn(timesVec);
933
934 for(int i=startIndex; i<size; i++)
935 rTimes.append(timesVec[i]);
936 delete[] timesVec;
937 }
938 //-----------------------------------------------------------------------------
939 // DATA
940 //-----------------------------------------------------------------------------
941 //_____________________________________________________________________________
942 /**
943 * Get a data value of a specified state at a specified time index.
944 *
945 * @param aTimeIndex Index that identifies the time (row) at which to get the
946 * data value: 0 <= aTimeIndex < _storage.getSize().
947 * @param aStateIndex Index of the state (column) for which to get the value.
948 * @param rValue Value of the state.
949 * @return 1 on success, 0 on failure.
950 */
951 int Storage::
getData(int aTimeIndex,int aStateIndex,double & rValue) const952 getData(int aTimeIndex,int aStateIndex,double &rValue) const
953 {
954 if(aTimeIndex<0) return(0);
955 if(aTimeIndex>=_storage.getSize()) return(0);
956
957 // ASSIGNMENT
958 StateVector *vec = getStateVector(aTimeIndex);
959 if(vec==NULL) return(0);
960 return( vec->getDataValue(aStateIndex,rValue) );
961 }
962 //_____________________________________________________________________________
963 /**
964 * Helper function for getData
965 */
966 int Storage::
getData(int aTimeIndex,int aStateIndex,int aN,double ** rData) const967 getData(int aTimeIndex,int aStateIndex,int aN,double **rData) const
968 {
969 if(aN<=0) return(0);
970 if(aStateIndex<0) return(0);
971 if(aTimeIndex<0) return(0);
972 if(aTimeIndex>=_storage.getSize()) return(0);
973
974 // GET STATEVECTOR
975 StateVector *vec = getStateVector(aTimeIndex);
976 if(vec==NULL) return(0);
977 if(vec->getSize()<=0) return(0);
978
979 // NUMBER OF STATES TO GET
980 int size = vec->getSize();
981 if(aStateIndex>=size) return(0);
982 int n = aStateIndex + aN;
983 if(n>size) n = size;
984 int N = n - aStateIndex;
985
986 // ALLOCATE MEMORY
987 if(*rData==NULL) *rData = new double[N];
988
989 // ASSIGN DATA
990 int i,j;
991 Array<double> &data = vec->getData();
992 double *pData = *rData;
993 for(i=0,j=aStateIndex;j<n;i++,j++) pData[i] = data[j];
994
995 return(N);
996 }
997 //_____________________________________________________________________________
998 /**
999 * At a specified time index, get a number of state values starting at
1000 * a specified state. The method simply gets part of a row of data
1001 * from adjacent columns in the storage object.
1002 *
1003 * @param aTimeIndex Index that identifies the time (row) at which to get the
1004 * data value: 0 <= aTimeIndex < _storage.getSize().
1005 * @param aStateIndex Index of the state (column) at which to start getting
1006 * the data.
1007 * @param aN Number of states (columns) to get.
1008 * @param rData Data values. rData should be able to hold at least N values.
1009 * @return Number of states that were gotten.
1010 */
1011 int Storage::
getData(int aTimeIndex,int aStateIndex,int aN,double * rData) const1012 getData(int aTimeIndex,int aStateIndex,int aN,double *rData) const
1013 {
1014 if(rData==NULL) return(0);
1015 else return getData(aTimeIndex,aStateIndex,aN,&rData);
1016 }
1017 //_____________________________________________________________________________
1018 /**
1019 * At a specified time index, get a number of state values starting at
1020 * a specified state. The method simply gets part of a row of data
1021 * from adjacent columns in the storage object.
1022 *
1023 * @param aTimeIndex Time index at which to get the states.
1024 * @param aStateIndex Index of the state (column) at which to start getting
1025 * the data.
1026 * @param aN Number of states to get.
1027 * @param rData Pointer to an array where the returned data will be set. The
1028 * size of *rData is assumed to be at least aN. If rData comes in as NULL,
1029 * memory is allocated.
1030 * @return Number of states.
1031 */
1032 int Storage::
getData(int aTimeIndex,int aN,double ** rData) const1033 getData(int aTimeIndex,int aN,double **rData) const
1034 {
1035 return getData(aTimeIndex,0,aN,rData);
1036 }
1037 //_____________________________________________________________________________
1038 /**
1039 * Get the first aN states at a specified time index.
1040 *
1041 * @param aTimeIndex Time index at which to get the states.
1042 * @param aN Number of states to get.
1043 * @param rData Array where the returned data will be set. The
1044 * size of rData is assumed to be at least aN.
1045 * @return Number of states that were set.
1046 */
1047 int Storage::
getData(int aTimeIndex,int aN,double * rData) const1048 getData(int aTimeIndex,int aN,double *rData) const
1049 {
1050 if(rData==NULL) return(0);
1051 else return getData(aTimeIndex,0,aN,&rData);
1052 }
1053 //_____________________________________________________________________________
1054 /**
1055 * Get the first aN states at a specified time index.
1056 *
1057 * @param aTimeIndex Time index at which to get the states.
1058 * @param aN Number of states to get.
1059 * @param rData Array where the returned data will be set. The
1060 * size of rData is assumed to be at least aN.
1061 * @return Number of states that were set.
1062 */
1063 int Storage::
getData(int aTimeIndex,int aN,Array<double> & rData) const1064 getData(int aTimeIndex,int aN,Array<double> &rData) const
1065 {
1066 if(0 == rData.size()) return(0);
1067 else return getData(aTimeIndex,0,aN,&rData[0]);
1068 }
1069 //_____________________________________________________________________________
1070 /**
1071 * Get the first aN states at a specified time index.
1072 *
1073 * @param aTimeIndex Time index at which to get the states.
1074 * @param aN Number of states to get.
1075 * @param rData Array where the returned data will be set. The
1076 * size of rData is assumed to be at least aN.
1077 * @return Number of states that were set.
1078 */
1079 int Storage::
getData(int aTimeIndex,int aN,SimTK::Vector & v) const1080 getData(int aTimeIndex,int aN,SimTK::Vector& v) const
1081 {
1082 Array<double> rData;
1083 rData.setSize(aN);
1084 int r = getData(aTimeIndex,0,aN,&rData[0]);
1085 for (int i=0; i<aN; ++i)
1086 v[i] = rData[i];
1087 return r;
1088 }
1089 //_____________________________________________________________________________
1090 /**
1091 * Get the first aN states at a specified time.
1092 * The values of the states are determined by linear interpolation.
1093 *
1094 * @param aT Time at which to get the states.
1095 * @param aN Number of states to get.
1096 * @param rData Pointer to an array where the returned data will be set. The
1097 * size of *rData is assumed to be at least aN. If rData comes in as NULL,
1098 * memory is allocated.
1099 * @return Number of states that were set.
1100 */
1101 int Storage::
getDataAtTime(double aT,int aN,double ** rData) const1102 getDataAtTime(double aT,int aN,double **rData) const
1103 {
1104
1105 // FIND THE CORRECT INTERVAL FOR aT
1106 int i = findIndex(_lastI,aT);
1107 if((i<0)||(_storage.getSize()<=0)) {
1108 *rData = NULL;
1109 return(0);
1110 }
1111
1112 // CHECK FOR i AT END POINTS
1113 int i1=i,i2=i+1;
1114
1115 if(i2==_storage.getSize()) {
1116 i1--; if(i1<0) i1=0;
1117 i2--; if(i2<0) i2=0;
1118 }
1119
1120 // STATES AT FIRST INDEX
1121 int n1 = getStateVector(i1)->getSize();
1122 double t1 = getStateVector(i1)->getTime();
1123 Array<double> &y1 = getStateVector(i1)->getData();
1124
1125 // STATES AT NEXT INDEX
1126 int n2 = getStateVector(i2)->getSize();
1127 double t2 = getStateVector(i2)->getTime();
1128 Array<double> &y2 = getStateVector(i2)->getData();
1129
1130 // GET THE SMALLEST N TO PREVENT MEMORY OVER-RUNS
1131 int ns = (n1<n2) ? n1 : n2;
1132
1133 // ALLOCATE MEMORY?
1134 double *y;
1135 if(*rData==NULL) {
1136 y = new double[ns];
1137 } else {
1138 y = *rData;
1139 if(aN<ns) ns = aN;
1140 }
1141
1142 // ASSIGN VALUES
1143 double pct;
1144 double num = aT-t1;
1145 double den = t2-t1;
1146 if(den<SimTK::Eps) {
1147 pct = 0.0;
1148 } else {
1149 pct = num/den;
1150 }
1151
1152
1153 for(i=0;i<ns;i++) {
1154 if(pct==0.0) {
1155 y[i] = y1[i];
1156 } else {
1157 y[i] = y1[i] + pct*(y2[i]-y1[i]);
1158 }
1159 }
1160
1161 // ASSIGN FOR RETURN
1162 *rData = y;
1163
1164 return(ns);
1165 }
1166 //_____________________________________________________________________________
1167 /**
1168 * Get the first aN states at a specified time.
1169 * The values of the states are determined by linear interpolation.
1170 *
1171 * @param aT Time at which to get the states.
1172 * @param aN Number of states to get.
1173 * @param rData Array where the returned data will be set. The
1174 * size of rData is assumed to be at least aN.
1175 * @return Number of states that were set.
1176 */
1177 int Storage::
getDataAtTime(double aT,int aN,double * rData) const1178 getDataAtTime(double aT,int aN,double *rData) const
1179 {
1180 if(rData==NULL) return(0);
1181 else return getDataAtTime(aT,aN,&rData);
1182 }
1183 int Storage::
getDataAtTime(double aT,int aN,Array<double> & rData) const1184 getDataAtTime(double aT,int aN,Array<double> &rData) const
1185 {
1186 double *data=&rData[0];
1187 return getDataAtTime(aT,aN,&data);
1188 }
1189 int Storage::
getDataAtTime(double aT,int aN,SimTK::Vector & v) const1190 getDataAtTime(double aT,int aN,SimTK::Vector& v) const
1191 {
1192 Array<double> rData;
1193 rData.setSize(aN);
1194 int r = getDataAtTime(aT,aN,rData);
1195 for (int i=0; i<aN; ++i)
1196 v[i] = rData[i];
1197 return r;
1198 }
1199 //_____________________________________________________________________________
1200 /**
1201 * Get the data corresponding to a specified state. This call is equivalent
1202 * to getting a column of data from the storage file.
1203 *
1204 * @param aStateIndex Index of the state (column) for which to get the data.
1205 * @param rData Array containing the desired data. If rData is sent in as
1206 * NULL, memory is allocated. However, if rData is sent in as a non-NULL, it
1207 * is assumed that rData points to a memory block that is large enough to
1208 * hold getSize() doubles.
1209 * @return Number of values set in rData. The number of values set may be
1210 * less than getSize() because not all stored state vectors are
1211 * required to have the same number of states.
1212 */
1213 int Storage::
getDataColumn(int aStateIndex,double * & rData) const1214 getDataColumn(int aStateIndex,double *&rData) const
1215 {
1216 int n = _storage.getSize();
1217 if(n<=0) return(0);
1218
1219 // ALLOCATION
1220 if(rData==NULL) {
1221 rData = new double[n];
1222 }
1223
1224 // ASSIGNMENT
1225 int i,nData;
1226 for(i=nData=0;i<n;i++) {
1227 StateVector *vec = getStateVector(i);
1228 if(vec==NULL) continue;
1229 if(vec->getDataValue(aStateIndex,rData[nData])) nData++;
1230 }
1231
1232 return(nData);
1233 }
1234 int Storage::
getDataColumn(int aStateIndex,Array<double> & rData) const1235 getDataColumn(int aStateIndex,Array<double> &rData) const
1236 {
1237 int n = _storage.getSize();
1238 if(n<=0) return(0);
1239
1240 rData.setSize(n);
1241
1242 // ASSIGNMENT
1243 int i,nData;
1244 for(i=nData=0;i<n;i++) {
1245 StateVector *vec = getStateVector(i);
1246 if(vec==NULL) continue;
1247 if(vec->getDataValue(aStateIndex,rData[nData])) nData++;
1248 }
1249
1250 rData.setSize(nData);
1251
1252 return(nData);
1253 }
1254
1255 /**
1256 * Get the data column starting at aTime. Return it in rData
1257 * rData is preallocated by the caller.
1258 */
1259 void Storage::
getDataColumn(const std::string & columnName,Array<double> & rData,double aStartTime)1260 getDataColumn(const std::string& columnName, Array<double>& rData, double aStartTime)
1261 {
1262 if(_storage.getSize()<=0) return;
1263
1264 int startIndex = findIndex(aStartTime);
1265 int colIndex = getStateIndex(columnName);
1266 double *dataVec=0;
1267 getDataColumn(colIndex, dataVec);
1268 for(int i=startIndex; i<_storage.getSize(); i++)
1269 rData.append(dataVec[i]);
1270 delete[] dataVec;
1271 }
1272
1273 //_____________________________________________________________________________
1274 /**
1275 * Set the data corresponding to a specified state. This call is equivalent
1276 * to setting a column of data from the storage file.
1277 *
1278 * @param aStateIndex Index of the state (column) for which to get the data.
1279 * @param aData Array containing the new data.
1280 * @return Number of values set in rData. The number of values set may be
1281 * less than getSize() because not all stored state vectors are
1282 * required to have the same number of states.
1283 */
1284 void Storage::
setDataColumn(int aStateIndex,const Array<double> & aData)1285 setDataColumn(int aStateIndex,const Array<double> &aData)
1286 {
1287 int n = _storage.getSize();
1288 if(n!=aData.getSize()) {
1289 cout<<"Storage.setDataColumn: ERR- sizes don't match." << endl;
1290 return;
1291 }
1292
1293 // ASSIGNMENT
1294 for(int i=0;i<n;i++) {
1295 StateVector *vec = getStateVector(i);
1296 if(vec==NULL) continue;
1297 vec->setDataValue(aStateIndex,aData[i]);
1298 }
1299 }
1300 /**
1301 * set values in the column specified by columnName to newValue
1302 */
setDataColumnToFixedValue(const std::string & columnName,double newValue)1303 void Storage::setDataColumnToFixedValue(const std::string& columnName, double newValue) {
1304 int n = _storage.getSize();
1305 int aStateIndex = getStateIndex(columnName);
1306 if(aStateIndex==-1) {
1307 cout<<"Storage.setDataColumnToFixedValue: ERR- column not found." << endl;
1308 return;
1309 }
1310
1311 // ASSIGNMENT
1312 for(int i=0;i<n;i++) {
1313 StateVector *vec = getStateVector(i);
1314 if(vec==NULL) continue;
1315 vec->setDataValue(aStateIndex,newValue);
1316 }
1317
1318 }
1319
1320 //_____________________________________________________________________________
1321 /**
1322 * Get the data corresponding to a state specified by name. This call is equivalent
1323 * to getting a column of data from the storage file.
1324 *
1325 * @param aColumnName name in header of column for which to get the data.
1326 * @param rData Array containing the desired data. If rData is sent in as
1327 * NULL, memory is allocated. However, if rData is sent in as a non-NULL, it
1328 * is assumed that rData points to a memory block that is large enough to
1329 * hold getSize() doubles.
1330 * @return Number of values set in rData. The number of values set may be
1331 * less than getSize() because not all stored state vectors are
1332 * required to have the same number of states.
1333 */
1334 int Storage::
getDataColumn(const std::string & aColumnName,double * & rData) const1335 getDataColumn(const std::string& aColumnName,double *&rData) const
1336 {
1337 if (aColumnName.c_str()[0]=='#'){
1338 int columnNumber=-1;
1339 sscanf(aColumnName.c_str(), "#%d", &columnNumber);
1340 return getDataColumn(columnNumber-1, rData);
1341 }
1342 else
1343 return getDataColumn(getStateIndex(aColumnName), rData);
1344 }
1345
1346 /** It is desirable to access the block as a single entity provided an identifier that is common
1347 to all components (such as prefix in the column label).
1348 @param identifier string identifying a single block of data
1349 @param rData Array<Array<double>> of data belonging to the identifier */
getDataForIdentifier(const std::string & identifier,Array<Array<double>> & rData,double startTime) const1350 void Storage::getDataForIdentifier(const std::string& identifier, Array<Array<double> >& rData, double startTime) const
1351 {
1352
1353 Array<int> found = getColumnIndicesForIdentifier(identifier);
1354
1355 if(found.getSize() == 0){
1356 cout << "WARNING: Storage "+getName()+" could not locate data for identifier "+identifier+"." << endl;
1357 return;
1358 }
1359 /* a row of "data" can be shorter than number of columns if time is the first column, since
1360 that is not considered a state by storage. Need to fix this! -aseth */
1361 int nd = getLastStateVector()->getSize();
1362 int off = _columnLabels.getSize()-nd;
1363
1364
1365 for(int i=0; i<found.getSize(); ++i){
1366 Array<double> data{};
1367 getDataColumn(found[i]-off, data);
1368 rData.append(data);
1369 }
1370 }
1371
1372 OpenSim::Array<int>
getColumnIndicesForIdentifier(const std::string & identifier) const1373 Storage::getColumnIndicesForIdentifier(const std::string& identifier) const
1374 {
1375 Array<int> found;
1376 const size_t lid = identifier.length();
1377
1378 if (lid < 1) // Identifier is empty; return empty Array.
1379 return found;
1380
1381 for (int i = 0; i < _columnLabels.getSize(); ++i) {
1382 if (_columnLabels[i].compare(0,lid, identifier) == 0)
1383 found.append(i);
1384 }
1385 return found;
1386 }
1387
exportToTable() const1388 TimeSeriesTable Storage::exportToTable() const {
1389 TimeSeriesTable table{};
1390
1391 table.addTableMetaData("header", getName());
1392 table.addTableMetaData("inDegrees", std::string{_inDegrees ? "yes" : "no"});
1393 table.addTableMetaData("nRows", std::to_string(_storage.getSize()));
1394 table.addTableMetaData("nColumns", std::to_string(_columnLabels.getSize()));
1395 if(!getDescription().empty())
1396 table.addTableMetaData("description", getDescription());
1397
1398 // Exclude the first column label. It is 'time'. Time is a separate column
1399 // in TimeSeriesTable and column label is optional.
1400 table.setColumnLabels(_columnLabels.get() + 1,
1401 _columnLabels.get() + _columnLabels.getSize());
1402
1403 for(int i = 0; i < _storage.getSize(); ++i) {
1404 const auto& row = getStateVector(i)->getData();
1405 const auto time = getStateVector(i)->getTime();
1406 // Exclude the first column. It is 'time'. Time is a separate column in
1407 // TimeSeriesTable.
1408 table.appendRow(time, row.get(), row.get() + row.getSize());
1409 }
1410
1411 return table;
1412 }
1413
1414
1415 //=============================================================================
1416 // RESET
1417 //=============================================================================
1418 //_____________________________________________________________________________
1419 /**
1420 * Reset the storage to start storing at a specified index. All
1421 * statevectors at and following the specified index are discarded.
1422 *
1423 * If aIndex is less than or equal to zero, the storage object is
1424 * emptied (i.e., its size is set to 0).
1425 *
1426 * If aIndex is larger than the current size of the storage object, no
1427 * action is taken.
1428 *
1429 * @param aIndex Index at which to start storing new statevectors.
1430 * @return Index at which the next appended statevector will be stored.
1431 */
1432 int Storage::
reset(int aIndex)1433 reset(int aIndex)
1434 {
1435 if(aIndex>=_storage.getSize()) return(_storage.getSize());
1436 if(aIndex<0) aIndex = 0;
1437 _storage.setSize(aIndex);
1438
1439 return(_storage.getSize());
1440 }
1441 //_____________________________________________________________________________
1442 /**
1443 * Reset the storage to start storing after a specified time. If no valid
1444 * statevector exists at that specified time, the storage is set to occur after
1445 * the first valid statevector that immediately precedes the specified
1446 * time.
1447 *
1448 * @param aT Time after which to start storing states. If aT doesn't exist
1449 * exactly in the storage, the time is rounded down to the first valid time.
1450 * @return Index at which the next appended statevector will be stored.
1451 */
1452 int Storage::
reset(double aTime)1453 reset(double aTime)
1454 {
1455 // 1 is added so that the states at or just prior to aT are kept.
1456 int index = findIndex(aTime) + 1;
1457
1458 return( reset(index) );
1459 }
1460
1461
1462 //_____________________________________________________________________________
1463 /**
1464 * Crop the storage object to the specified start and final time
1465 */
1466 void Storage::
crop(const double newStartTime,const double newFinalTime)1467 crop(const double newStartTime, const double newFinalTime)
1468 {
1469 int startindex = findIndex(newStartTime);
1470 int finalindex = findIndex(newFinalTime);
1471 // Since underlying Array is packed we'll just move what we need up then
1472 // delete remaining rows in reverse order.
1473 int numRowsToKeep=finalindex-startindex+1;
1474 if (numRowsToKeep <=0){
1475 cout<<"Storage.crop: WARNING: No rows will be left." << endl;
1476 numRowsToKeep=0;
1477 }
1478 if (startindex!=0){
1479 for(int i=0; i<finalindex-startindex+1; i++)
1480 _storage[i]=_storage[startindex+i];
1481 }
1482 _storage.setSize(numRowsToKeep);
1483 }
1484
1485 //=============================================================================
1486 // STORAGE
1487 //=============================================================================
1488 //_____________________________________________________________________________
1489 /**
1490 * Append an StateVector.
1491 *
1492 * @param aStateVector Statevector to be appended.
1493 * @return Size of the storage after the append.
1494 */
1495 int Storage::
append(const StateVector & aStateVector,bool aCheckForDuplicateTime)1496 append(const StateVector &aStateVector,bool aCheckForDuplicateTime)
1497 {
1498 // TODO: use some tolerance when checking for duplicate time?
1499 if(aCheckForDuplicateTime && _storage.getSize() && _storage.getLast().getTime()==aStateVector.getTime())
1500 _storage.updLast() = aStateVector;
1501 else
1502 _storage.append(aStateVector);
1503
1504 if (_fp!=0){
1505 aStateVector.print(_fp);
1506 fflush(_fp);
1507 }
1508 return(_storage.getSize());
1509 }
1510 //_____________________________________________________________________________
1511 /**
1512 * Append copies of all state vectors in an Storage object.
1513 *
1514 * This method overrides Array::append(Array).
1515 * Currently, there is no difference. The override is done for completeness.
1516 *
1517 * @param aStorage Storage to be appended.
1518 * @return The index of the first empty storage element.
1519 */
1520 int Storage::
append(const Array<StateVector> & aStorage)1521 append(const Array<StateVector> &aStorage)
1522 {
1523 for(int i=0; i<aStorage.getSize(); i++)
1524 _storage.append(aStorage[i]);
1525 return(_storage.getSize());
1526 }
1527 //_____________________________________________________________________________
1528 /**
1529 * Append an array of data that occurred at a specified time.
1530 *
1531 * @param aT Time stamp of the data.
1532 * @param aN Length of the array.
1533 * @param aY Array.
1534 * @return Index of the first empty storage element.
1535 */
1536 int Storage::
append(double aT,int aN,const double * aY,bool aCheckForDuplicateTime)1537 append(double aT,int aN,const double *aY,bool aCheckForDuplicateTime)
1538 {
1539 if(aY==NULL) return(_storage.getSize());
1540 if(aN<0) return(_storage.getSize());
1541
1542 // APPEND
1543 StateVector vec(aT, SimTK::Vector_<double>(aN, aY));
1544 append(vec,aCheckForDuplicateTime);
1545 // TODO: use some tolerance when checking for duplicate time?
1546 /*
1547 if(aCheckForDuplicateTime && _storage.getSize() && _storage.getLast().getTime()==vec.getTime())
1548 _storage.getLast() = vec;
1549 else
1550 _storage.append(vec);
1551 */
1552 return(_storage.getSize());
1553 }
1554 //_____________________________________________________________________________
1555 /**
1556 * Append an array of data that occurred at a specified time.
1557 *
1558 * @param aT Time stamp of the data.
1559 * @param aY Vector.
1560 * @return Index of the first empty storage element.
1561 */
1562 int Storage::
append(double aT,const SimTK::Vector & aY,bool aCheckForDuplicateTime)1563 append(double aT,const SimTK::Vector& aY,bool aCheckForDuplicateTime)
1564 {
1565 // APPEND
1566 return( append ( aT, aY.size(), &aY[0], aCheckForDuplicateTime ));
1567 }
1568 //_____________________________________________________________________________
1569 /**
1570 * Append an array of data that occurred at a specified time.
1571 *
1572 * @param aT Time stamp of the data.
1573 * @param aY Array<double>.
1574 * @return Index of the first empty storage element.
1575 */
1576 int Storage::
append(double aT,const Array<double> & aY,bool aCheckForDuplicateTime)1577 append(double aT,const Array<double>& aY,bool aCheckForDuplicateTime)
1578 {
1579 // APPEND
1580 return( append ( aT, aY.getSize(), &aY[0], aCheckForDuplicateTime ));
1581 }
1582
1583 //_____________________________________________________________________________
1584 //_____________________________________________________________________________
1585 /**
1586 * Store a simulation vector.
1587 *
1588 * This method differs from append in that, if the integration step of
1589 * a simulation is not a multiple of the step interval of this
1590 * Storage class, the state is not appended.
1591 * Note that if the step storage interval is 0, storage is turned off.
1592 *
1593 * The first empty storage location is returned.
1594 */
1595 int Storage::
store(int aStep,double aT,int aN,const double * aY)1596 store(int aStep,double aT,int aN,const double *aY)
1597 {
1598 if(_stepInterval==0) return(_storage.getSize());
1599 if((aStep%_stepInterval) == 0) {
1600 append(aT,aN,aY);
1601 }
1602
1603 return(_storage.getSize());
1604 }
1605
1606
1607 //=============================================================================
1608 // OPERATIONS
1609 //=============================================================================
1610 //-----------------------------------------------------------------------------
1611 // TIME
1612 //-----------------------------------------------------------------------------
1613 //_____________________________________________________________________________
1614 /**
1615 * Shift the times of all state vectors.
1616 *
1617 * @param aValue Value by which to shift the times.
1618 */
1619 void Storage::
shiftTime(double aValue)1620 shiftTime(double aValue)
1621 {
1622 for(int i=0;i<_storage.getSize();i++) {
1623 _storage[i].shiftTime(aValue);
1624 }
1625 }
1626 //_____________________________________________________________________________
1627 /**
1628 * Scale the times of all state vectors.
1629 *
1630 * @param aValue Value by which to scale the times.
1631 */
1632 void Storage::
scaleTime(double aValue)1633 scaleTime(double aValue)
1634 {
1635 for(int i=0;i<_storage.getSize();i++) {
1636 _storage[i].scaleTime(aValue);
1637 }
1638 }
1639
1640 //-----------------------------------------------------------------------------
1641 // ADD
1642 //-----------------------------------------------------------------------------
1643 //_____________________________________________________________________________
1644 /**
1645 * Add a value to all state vectors in this storage instance.
1646 *
1647 * @param aValue Value to add to the state vectors.
1648 * @see StateVector::add(double)
1649 */
1650 void Storage::
add(double aValue)1651 add(double aValue)
1652 {
1653 for(int i=0;i<_storage.getSize();i++) {
1654 _storage[i].add(aValue);
1655 }
1656 }
1657 //_____________________________________________________________________________
1658 /**
1659 * Add a value to all entries in one column of the storage.
1660 *
1661 * @param aN Index of the column that the value is to be added to.
1662 * @param aValue Value to add to the column.
1663 * @see StateVector::add(int,double)
1664 */
1665 void Storage::
add(int aN,double aValue)1666 add(int aN, double aValue)
1667 {
1668 for(int i=0;i<_storage.getSize();i++) {
1669 _storage[i].add(aN,aValue);
1670 }
1671 }
1672 //_____________________________________________________________________________
1673 /**
1674 * Add an array to all state vectors in this storage instance.
1675 *
1676 * Only the first aN states of each state vector are altered.
1677 *
1678 * @param values Array of values to add to the state vectors.
1679 * @see StateVector::add(int,double[])
1680 */
add(const SimTK::Vector_<double> & values)1681 void Storage::add(const SimTK::Vector_<double>& values) {
1682 for(int i = 0; i < _storage.getSize(); ++i) {
1683 _storage[i].add(values);
1684 }
1685 }
1686 //_____________________________________________________________________________
1687 /**
1688 * Add a state vector to the all state vectors in this storage instance.
1689 *
1690 * @param aStateVector State vector to add to the state vectors.
1691 * @see StateVector::add(int,double[])
1692 */
1693 void Storage::
add(StateVector * aStateVector)1694 add(StateVector *aStateVector)
1695 {
1696 for(int i=0;i<_storage.getSize();i++) {
1697 _storage[i].add(aStateVector);
1698 }
1699 }
1700 //_____________________________________________________________________________
1701 /**
1702 * Add a storage instance to this storage instance.
1703 *
1704 * Linear interpolation or extrapolation is used to get the values of the
1705 * states that correspond in time to the states held in this storage
1706 * instance.
1707 *
1708 * @param aStorage Storage to add to this storage.
1709 s */
1710 void Storage::
add(Storage * aStorage)1711 add(Storage *aStorage)
1712 {
1713 if(aStorage==NULL) return;
1714
1715 int n,N=0,nN;
1716 double t,*Y=NULL;
1717 for(int i=0;i<_storage.getSize();i++) {
1718
1719 // GET INFO ON THIS STORAGE INSTANCE
1720 n = getStateVector(i)->getSize();
1721 t = getStateVector(i)->getTime();
1722
1723 // GET DATA FROM ARGUMENT
1724 N = aStorage->getDataAtTime(t,N,&Y);
1725
1726 // SET SIZE TO SMALLER
1727 nN = (n<N) ? n : N;
1728
1729 // ADD
1730 _storage[i].add(SimTK::Vector_<double>(nN, Y));
1731 }
1732
1733 // CLEANUP
1734 if(Y!=NULL) delete[] Y;
1735 }
1736
1737 //-----------------------------------------------------------------------------
1738 // SUBTRACT
1739 //-----------------------------------------------------------------------------
1740 //_____________________________________________________________________________
1741 /**
1742 * Subtract a value from all state vectors in this storage instance.
1743 *
1744 * @param aValue Value to subtract from the state vectors.
1745 * @see StateVector::subtract(double)
1746 */
1747 void Storage::
subtract(double aValue)1748 subtract(double aValue)
1749 {
1750 for(int i=0;i<_storage.getSize();i++) {
1751 _storage[i].subtract(aValue);
1752 }
1753 }
1754 //_____________________________________________________________________________
1755 /**
1756 * Subtract an array from all state vectors in this storage instance.
1757 *
1758 * Only the first aN states of each state vector are altered.
1759 *
1760 * @param values Array of values to subtract from the state vectors.
1761 * @see StateVector::subtract(int,double[])
1762 */
subtract(const SimTK::Vector_<double> & values)1763 void Storage::subtract(const SimTK::Vector_<double>& values) {
1764 for(int i = 0; i < _storage.getSize(); ++i) {
1765 _storage[i].subtract(values);
1766 }
1767 }
1768 //_____________________________________________________________________________
1769 /**
1770 * Subtract a state vector from all state vectors in this storage instance.
1771 *
1772 * @param aStateVector State vector to subtract from the state vectors.
1773 * @see StateVector::subtract(int,double[])
1774 */
1775 void Storage::
subtract(StateVector * aStateVector)1776 subtract(StateVector *aStateVector)
1777 {
1778 for(int i=0;i<_storage.getSize();i++) {
1779 _storage[i].subtract(aStateVector);
1780 }
1781 }
1782 //_____________________________________________________________________________
1783 /**
1784 * Subtract a storage instance to this storage instance.
1785 *
1786 * Linear interpolation or extrapolation is used to get the values of the
1787 * states that correspond in time to the states held in this storage
1788 * instance.
1789 *
1790 * @param aStorage Storage to subtract from this storage.
1791 s */
1792 void Storage::
subtract(Storage * aStorage)1793 subtract(Storage *aStorage)
1794 {
1795 if(aStorage==NULL) return;
1796
1797 int n,N=0,nN;
1798 double t,*Y=NULL;
1799 for(int i=0;i<_storage.getSize();i++) {
1800
1801 // GET INFO ON THIS STORAGE INSTANCE
1802 n = getStateVector(i)->getSize();
1803 t = getStateVector(i)->getTime();
1804
1805 // GET DATA FROM ARGUMENT
1806 N = aStorage->getDataAtTime(t,N,&Y);
1807
1808 // SET SIZE TO SMALLER
1809 nN = (n<N) ? n : N;
1810
1811 // SUBTRACT
1812 _storage[i].subtract(SimTK::Vector_<double>(nN, Y));
1813 }
1814
1815 // CLEANUP
1816 if(Y!=NULL) delete[] Y;
1817 }
1818
1819 //-----------------------------------------------------------------------------
1820 // MULTIPLY
1821 //-----------------------------------------------------------------------------
1822 //_____________________________________________________________________________
1823 /**
1824 * Multiply all state vectors in this storage instance by a value.
1825 *
1826 * @param aValue Value by which to multiply the state vectors.
1827 * @see StateVector::multiply(double)
1828 */
1829 void Storage::
multiply(double aValue)1830 multiply(double aValue)
1831 {
1832 for(int i=0;i<_storage.getSize();i++) {
1833 _storage[i].multiply(aValue);
1834 }
1835 }
1836 //_____________________________________________________________________________
1837 /**
1838 * Multiply all state vectors in this storage instance by an array.
1839 *
1840 * Only the first aN states of each state vector are altered.
1841 *
1842 * @param values Array of values the states are to be multiplied by.
1843 * @see StateVector::multiply(int,double[])
1844 */
multiply(const SimTK::Vector_<double> & values)1845 void Storage::multiply(const SimTK::Vector_<double>& values) {
1846 for(int i = 0; i < _storage.getSize(); ++i) {
1847 _storage[i].multiply(values);
1848 }
1849 }
1850
1851 //_____________________________________________________________________________
1852 /**
1853 * Multiply all state vectors in this storage instance by a state vector.
1854 *
1855 * @param aStateVector State vector by which to multiply the state vectors.
1856 * @see StateVector::multiply(StateVector)
1857 */
1858 void Storage::
multiply(StateVector * aStateVector)1859 multiply(StateVector *aStateVector)
1860 {
1861 for(int i=0;i<_storage.getSize();i++) {
1862 _storage[i].multiply(aStateVector);
1863 }
1864 }
1865 //_____________________________________________________________________________
1866 /**
1867 * Multiply this storage instance by a storage instance.
1868 *
1869 * Linear interpolation or extrapolation is used to get the values of the
1870 * states that correspond in time to the states held in this storage
1871 * instance.
1872 *
1873 * @param aStorage Storage instance by which to multiply.
1874 s */
1875 void Storage::
multiply(Storage * aStorage)1876 multiply(Storage *aStorage)
1877 {
1878 if(aStorage==NULL) return;
1879
1880 int n,N=0,nN;
1881 double t,*Y=NULL;
1882 for(int i=0;i<_storage.getSize();i++) {
1883
1884 // GET INFO ON THIS STORAGE INSTANCE
1885 n = getStateVector(i)->getSize();
1886 t = getStateVector(i)->getTime();
1887
1888 // GET DATA FROM ARGUMENT
1889 N = aStorage->getDataAtTime(t,N,&Y);
1890
1891 // SET SIZE TO SMALLER
1892 nN = (n<N) ? n : N;
1893
1894 // MULTIPLY
1895 _storage[i].multiply(SimTK::Vector_<double>(nN, Y));
1896 }
1897
1898 // CLEANUP
1899 if(Y!=NULL) delete[] Y;
1900 }
1901 //_____________________________________________________________________________
1902 /**
1903 * Multiply entries at column aIndex by a value.
1904 *
1905 * @param aIndex is the index of the column to multiply
1906 * @param aValue Value by which to multiply the column.
1907 */
1908 void Storage::
multiplyColumn(int aIndex,double aValue)1909 multiplyColumn(int aIndex, double aValue)
1910 {
1911 double newValue;
1912 for(int i=0;i<_storage.getSize();i++) {
1913 _storage[i].getDataValue(aIndex, newValue);
1914 newValue *= aValue;
1915 _storage[i].setDataValue(aIndex, newValue);
1916 }
1917 }
1918
1919 //-----------------------------------------------------------------------------
1920 // DIVIDE
1921 //-----------------------------------------------------------------------------
1922 //_____________________________________________________________________________
1923 /**
1924 * Divide all state vectors in this storage instance by a value.
1925 *
1926 * @param aValue Value by which to divide the state vectors.
1927 */
1928 void Storage::
divide(double aValue)1929 divide(double aValue)
1930 {
1931 for(int i=0;i<_storage.getSize();i++) {
1932 _storage[i].divide(aValue);
1933 }
1934 }
1935 //_____________________________________________________________________________
1936 /**
1937 * Divide all state vectors in this storage instance by an array.
1938 *
1939 * Only the first aN states of each state vector are altered.
1940 *
1941 * @param values Array of values the states are to be divided by.
1942 */
divide(const SimTK::Vector_<double> & values)1943 void Storage::divide(const SimTK::Vector_<double>& values) {
1944 for(int i = 0; i < _storage.getSize(); ++i) {
1945 _storage[i].divide(values);
1946 }
1947 }
1948 //_____________________________________________________________________________
1949 /**
1950 * Divide all state vectors in this storage instance by a state vector.
1951 *
1952 * @param aStateVector State vector by which to divide the state vectors.
1953 */
1954 void Storage::
divide(StateVector * aStateVector)1955 divide(StateVector *aStateVector)
1956 {
1957 for(int i=0;i<_storage.getSize();i++) {
1958 _storage[i].divide(aStateVector);
1959 }
1960 }
1961 //_____________________________________________________________________________
1962 /**
1963 * Divide this storage instance by a storage instance.
1964 *
1965 * Linear interpolation or extrapolation is used to get the values of the
1966 * states that correspond in time to the states held in this storage
1967 * instance.
1968 *
1969 * @param aStorage Storage instance by which to divide.
1970 s */
1971 void Storage::
divide(Storage * aStorage)1972 divide(Storage *aStorage)
1973 {
1974 if(aStorage==NULL) return;
1975
1976 int i;
1977 int n,N=0,nN;
1978 double t,*Y=NULL;
1979 for(i=0;i<_storage.getSize();i++) {
1980
1981 // GET INFO ON THIS STORAGE INSTANCE
1982 n = getStateVector(i)->getSize();
1983 t = getStateVector(i)->getTime();
1984
1985 // GET DATA FROM ARGUMENT
1986 N = aStorage->getDataAtTime(t,N,&Y);
1987
1988 // SET SIZE TO SMALLER
1989 nN = (n<N) ? n : N;
1990
1991 // DIVIDE
1992 _storage[i].divide(SimTK::Vector_<double>(nN, Y));
1993 }
1994
1995 // CLEANUP
1996 if(Y!=NULL) delete[] Y;
1997 }
1998
1999 //=============================================================================
2000 // COMPUTATIONS
2001 //=============================================================================
2002 //_____________________________________________________________________________
2003 /**
2004 * Compute the average value of the first aN states stored for all state
2005 * vectors stored in this storage instance.
2006 *
2007 * This method uses computeArea() to compute the area (integral) and then
2008 * simply divides by the time interval (tf-ti).
2009 *
2010 * It is assumed that there is enough memory at aAve to hold aN states.
2011 * If aN exceeds the number of states held in storage, aN is disregarded.
2012 *
2013 * The number of valid states in aAve is returned.
2014 */
2015 int Storage::
computeAverage(int aN,double * aAve) const2016 computeAverage(int aN,double *aAve) const
2017 {
2018 int n = computeArea(aN,aAve);
2019 if(n==0) return(0);
2020
2021 // DIVIDE BY THE TIME RANGE
2022 double ti = getFirstTime();
2023 double tf = getLastTime();
2024 if(tf<=ti) {
2025 cout << "Storage.computeAverage: ERROR- time interval invalid." << endl
2026 << "\tfirstTime=" << ti << " lastTime=" << tf << endl;
2027 return(0);
2028 }
2029 double dt_recip = 1.0 / (tf-ti);
2030 for(int i=0;i<n;i++) {
2031 aAve[i] *= dt_recip;
2032 }
2033
2034 return(n);
2035 }
2036 //_____________________________________________________________________________
2037 /**
2038 * Compute the average value of the first aN states stored between the
2039 * times aTI and aTF.
2040 *
2041 * This method uses computeArea() to compute the area (integral) of each
2042 * state on the interval [aTI,aTF] and then simply divides by the (aTF-aTI).
2043 *
2044 * It is assumed that there is enough memory at aAve to hold aN states.
2045 * If aN exceeds the number of states held in storage, aN is disregarded.
2046 *
2047 * The number of valid states in aAve is returned.
2048 *
2049 * Note that if aTI and aTF do not fall exactly on the time stamp
2050 * of a stored state, the states are linearly interpolated to provide
2051 * an estimate of the state at aTI or at aTF.
2052 */
2053 int Storage::
computeAverage(double aTI,double aTF,int aN,double * aAve) const2054 computeAverage(double aTI,double aTF,int aN,double *aAve) const
2055 {
2056 int n = computeArea(aTI,aTF,aN,aAve);
2057 if(n==0) return(0);
2058
2059 // DIVIDE BY THE TIME RANGE
2060 double dt_recip = 1.0 / (aTF-aTI);
2061 for(int i=0;i<n;i++) {
2062 aAve[i] *= dt_recip;
2063 }
2064
2065 return(n);
2066 }
2067
2068 //_____________________________________________________________________________
2069 /**
2070 * Helper function for integrate/computeArea
2071 */
2072 int Storage::
integrate(int aI1,int aI2,int aN,double * rArea,Storage * rStorage) const2073 integrate(int aI1,int aI2,int aN,double *rArea,Storage *rStorage) const
2074 {
2075 // CHECK THAT THERE ARE STATES STORED
2076 if(_storage.getSize()<=0) {
2077 cout << "Storage.integrate: ERROR- no stored states." << endl;
2078 return(0);
2079 }
2080
2081 // CHECK INDICES
2082 if(aI1>=aI2) {
2083 cout << "Storage.integrate: ERROR- aI1 >= aI2." << endl;
2084 return(0);
2085 }
2086
2087 // GET THE SMALLEST NUMBER OF STATES
2088 int n = getSmallestNumberOfStates();
2089 if(n>aN) n = aN;
2090 if(n<=0) {
2091 cout << "Storage.computeArea: ERROR- no stored states" << endl;
2092 return(0);
2093 }
2094
2095 // SET THE INDICES
2096 if(aI1<0) aI1 = 0;
2097 if(aI2<0) aI2 = _storage.getSize()-1;
2098
2099 // WORKING MEMORY
2100 double ti,tf;
2101 double *yi=NULL,*yf=NULL;
2102
2103 bool functionAllocatedArea = false;
2104 if(!rArea) {
2105 rArea = new double [n];
2106 functionAllocatedArea = true;
2107 }
2108
2109 // INITIALIZE AREA
2110 for(int i=0;i<n;i++) rArea[i]=0.0;
2111
2112 // RECORD FIRST STATE
2113 if(rStorage) {
2114 ti = getStateVector(aI1)->getTime();
2115 rStorage->append(ti,n,rArea);
2116 }
2117
2118 // INTEGRATE
2119 for(int I=aI1;I<aI2;I++) {
2120
2121 // INITIAL
2122 ti = getStateVector(I)->getTime();
2123 yi = getStateVector(I)->getData().get();
2124
2125 // FINAL
2126 tf = getStateVector(I+1)->getTime();
2127 yf = getStateVector(I+1)->getData().get();
2128
2129 // AREA
2130 for(int i=0;i<n;i++) {
2131 rArea[i] += 0.5*(yf[i]+yi[i])*(tf-ti);
2132 }
2133
2134 // APPEND
2135 if(rStorage) rStorage->append(tf,n,rArea);
2136 }
2137
2138 // CLEANUP
2139 if(functionAllocatedArea) delete[] rArea;
2140
2141 return(n);
2142 }
2143 //_____________________________________________________________________________
2144 /**
2145 * Helper function for integrate/computeArea
2146 */
2147 int Storage::
integrate(double aTI,double aTF,int aN,double * rArea,Storage * rStorage) const2148 integrate(double aTI,double aTF,int aN,double *rArea,Storage *rStorage) const
2149 {
2150 // CHECK THAT THERE ARE STATES STORED
2151 if(_storage.getSize()<=0) {
2152 cout << "Storage.integrate: ERROR- no stored states." << endl;
2153 return(0);
2154 }
2155
2156 // CHECK INITIAL AND FINAL TIMES
2157 if(aTI>=aTF) {
2158 cout << "Storage.integrate: ERROR- bad time range." << endl
2159 << "\tInitial time (" << aTI << ") is not smaller than final time (" << aTF << ")" << endl;
2160 return(0);
2161 }
2162
2163 // CHECK TIME RANGE
2164 double fstT = getFirstTime();
2165 double lstT = getLastTime();
2166 if((aTI<fstT)||(aTI>lstT)||(aTF<fstT)||(aTF>lstT)) {
2167 cout << "Storage.integrate: ERROR- bad time range." << endl
2168 << "\tThe specified range (" << aTI << " to " << aTF << ") is not covered by" << endl
2169 << "\ttime range of the stored states (" << fstT << " to " << lstT << ")." << endl;
2170 return(0);
2171 }
2172
2173 // CHECK FOR VALID OUTPUT ARRAYS
2174 if(aN<=0) return(0);
2175
2176 // GET THE SMALLEST NUMBER OF STATES
2177 int n = getSmallestNumberOfStates();
2178 if(n>aN) n = aN;
2179 if(n<=0) {
2180 cout << "Storage.integrate: ERROR- no stored states" << endl;
2181 return(0);
2182 }
2183
2184 // WORKING MEMORY
2185 double ti,tf;
2186 double *yI = new double[n];
2187 double *yF = new double[n];
2188
2189 bool functionAllocatedArea = false;
2190 if(!rArea) {
2191 rArea = new double [n];
2192 functionAllocatedArea = true;
2193 }
2194
2195 // INITIALIZE AREA
2196 for(int i=0;i<n;i++) rArea[i]=0.0;
2197
2198 // RECORD FIRST STATE
2199 if(rStorage) rStorage->append(aTI,n,rArea);
2200
2201 // GET RELEVANT STATE INDICES
2202 int II = findIndex(aTI)+1;
2203 int FF = findIndex(aTF);
2204
2205 // SAME INTERVAL
2206 if(II>FF) {
2207 getDataAtTime(aTI,n,&yI);
2208 getDataAtTime(aTF,n,&yF);
2209 for(int i=0;i<n;i++) {
2210 rArea[i] += 0.5*(yF[i]+yI[i])*(aTF-aTI);
2211 }
2212 if(rStorage) rStorage->append(aTF,n,rArea);
2213
2214 // SPANS MULTIPLE INTERVALS
2215 } else {
2216 double *yi=NULL,*yf=NULL;
2217
2218 // FIRST SLICE
2219 getDataAtTime(aTI,n,&yI);
2220 tf = getStateVector(II)->getTime();
2221 yf = getStateVector(II)->getData().get();
2222 for(int i=0;i<n;i++) {
2223 rArea[i] += 0.5*(yf[i]+yI[i])*(tf-aTI);
2224 }
2225 if(rStorage) rStorage->append(tf,n,rArea);
2226
2227 // INTERVALS
2228 for(int I=II;I<FF;I++) {
2229 ti = getStateVector(I)->getTime();
2230 yi = getStateVector(I)->getData().get();
2231 tf = getStateVector(I+1)->getTime();
2232 yf = getStateVector(I+1)->getData().get();
2233 for(int i=0;i<n;i++) {
2234 rArea[i] += 0.5*(yf[i]+yi[i])*(tf-ti);
2235 }
2236 if(rStorage) rStorage->append(tf,n,rArea);
2237 }
2238
2239 // LAST SLICE
2240 ti = getStateVector(FF)->getTime();
2241 yi = getStateVector(FF)->getData().get();
2242 getDataAtTime(aTF,n,&yF);
2243 for(int i=0;i<n;i++) {
2244 rArea[i] += 0.5*(yF[i]+yi[i])*(aTF-ti);
2245 }
2246 if(rStorage) rStorage->append(aTF,n,rArea);
2247 }
2248
2249 // CLEANUP
2250 delete[] yI;
2251 delete[] yF;
2252 if(functionAllocatedArea) delete[] rArea;
2253
2254 return(n);
2255 }
2256 //_____________________________________________________________________________
2257 /**
2258 * Compute the area of the first aN states stored for all state vectors
2259 * stored in this storage instance.
2260 *
2261 * It is assumed that there is enough memory at aArea to hold aN states.
2262 * If aN exceeds the number of states held in storage, aN is disregarded.
2263 *
2264 * The number of valid states in aArea is returned.
2265 */
2266 int Storage::
computeArea(int aN,double * aArea) const2267 computeArea(int aN,double *aArea) const
2268 {
2269 // CHECK FOR VALID OUTPUT ARRAYS
2270 if(aN<=0) return(0);
2271 else if(aArea==NULL) return(0);
2272 else return integrate(0,_storage.getSize()-1,aN,aArea,NULL);
2273 }
2274 //_____________________________________________________________________________
2275 /**
2276 * Compute the area of the first aN states stored between the
2277 * times aTI and aTF.
2278 *
2279 * It is assumed that there is enough memory at aArea to hold aN states.
2280 * If aN exceeds the number of states held in storage, aN is disregarded.
2281 *
2282 * The number of valid states in aArea is returned.
2283 *
2284 * Note that if aTI and aTF do not fall exactly on the time stamp
2285 * of a stored state, the states are linearly interpolated to provide
2286 * an estimate of the state at aTI or at aTF.
2287 */
2288 int Storage::
computeArea(double aTI,double aTF,int aN,double * aArea) const2289 computeArea(double aTI,double aTF,int aN,double *aArea) const
2290 {
2291 // CHECK FOR VALID OUTPUT ARRAYS
2292 if(aN<=0) return(0);
2293 else if(aArea==NULL) return(0);
2294 else return integrate(aTI,aTF,aN,aArea,NULL);
2295 }
2296 //_____________________________________________________________________________
2297 /**
2298 * Integrate the state vectors between aI1 and aI2.
2299 *
2300 * The integration results are returned in an Storage instance that is a
2301 * copy of this instance except the name has been appended with "_integrated"
2302 * and, of course, the state vectors have been integrated. The caller is
2303 * responsible for deleting the returned storage instance.
2304 *
2305 * If aI1 is negative, integrations starts at the first StateVector
2306 * held by this Storage instance.
2307 * If aI2 is negative, integration starts at the last StateVector held by
2308 * this Storage instance.
2309 *
2310 * Note that aI1 and aI2 have negative default values, so that this method
2311 * may be called without arguments to integrate all StateVectors held by
2312 * this Storage instance.
2313 *
2314 * @param aI1 Index of state vector at which to start integration.
2315 * @param aI2 Index of state vector at which to stop integration.
2316 * @return Storage instance of integrated results. NULL is returned if an
2317 * error is encountered.
2318 */
2319 Storage* Storage::
integrate(int aI1,int aI2) const2320 integrate(int aI1,int aI2) const
2321 {
2322 // CREATE COPY
2323 Storage *integStore = new Storage(*this,false);
2324 integStore->setName(getName()+"_integrated");
2325
2326 int n = getSmallestNumberOfStates();
2327 int result = integrate(aI1,aI2,n,NULL,integStore);
2328 if(result<=0) {
2329 delete integStore;
2330 return NULL;
2331 } else return integStore;
2332 }
2333 //_____________________________________________________________________________
2334 /**
2335 * Integrate the state vectors between times aTI and aTF.
2336 *
2337 * The integration results are returned in an Storage instance that is a
2338 * copy of this instance except the name has been appended with "_integrated"
2339 * and, of course, the state vectors have been integrated. The caller is
2340 * responsible for deleting the returned storage instance.
2341 *
2342 * Note that if aTI and aTF do not fall exactly on the time stamp
2343 * of a stored state, the states are linearly interpolated or extrapolated
2344 * to provide an estimate of the state at aTI or at aTF.
2345 *
2346 * @param aTI Time at which to start the integration.
2347 * @param aTF Time at which to stop the integration.
2348 * @return Storage instance of integrated results. NULL is returned if an
2349 * error is encountered.
2350 */
2351 Storage* Storage::
integrate(double aTI,double aTF) const2352 integrate(double aTI,double aTF) const
2353 {
2354 // CREATE COPY
2355 Storage *integStore = new Storage(*this,false);
2356 integStore->setName(getName()+"_integrated");
2357
2358 int n = getSmallestNumberOfStates();
2359 int result = integrate(aTI,aTF,n,NULL,integStore);
2360 if(result<=0) {
2361 delete integStore;
2362 return NULL;
2363 } else return integStore;
2364 }
2365
2366 //_____________________________________________________________________________
2367 /**
2368 * Pad each of the columns in a statevector by a specified amount.
2369 * Data is both prepended and appended by reflecting and negating.
2370 *
2371 * @param aPadSize Number of data points to prepend and append.
2372 */
2373 void Storage::
pad(int aPadSize)2374 pad(int aPadSize)
2375 {
2376 if (aPadSize==0) return; //Nothing to do
2377 // PAD THE TIME COLUMN
2378 Array<double> paddedTime;
2379 int size = getTimeColumn(paddedTime);
2380 Signal::Pad(aPadSize,paddedTime);
2381 int newSize = paddedTime.getSize();
2382
2383 // PAD EACH COLUMN
2384 int nc = getSmallestNumberOfStates();
2385 Array<double> paddedSignal(0.0,size);
2386 StateVector *vecs = new StateVector[newSize];
2387 for(int j=0;j<newSize;j++) {
2388 vecs[j].getData().setSize(nc);
2389 vecs[j].setTime(paddedTime[j]);
2390 }
2391 for(int i=0;i<nc;i++) {
2392 getDataColumn(i,paddedSignal);
2393 Signal::Pad(aPadSize,paddedSignal);
2394 for(int j=0;j<newSize;j++)
2395 vecs[j].setDataValue(i,paddedSignal[j]);
2396 }
2397
2398 // APPEND THE STATEVECTORS
2399 _storage.setSize(0);
2400 for(int i=0;i<newSize;i++) _storage.append(vecs[i]);
2401
2402 // CLEANUP
2403 delete[] vecs;
2404 }
2405
2406 void Storage::
smoothSpline(int aOrder,double aCutoffFrequency)2407 smoothSpline(int aOrder,double aCutoffFrequency)
2408 {
2409 int size = getSize();
2410 double dtmin = getMinTimeStep();
2411 double avgDt = (_storage[size-1].getTime() - _storage[0].getTime()) / (size-1);
2412
2413 if(dtmin<SimTK::Eps) {
2414 cout<<"Storage.SmoothSpline: storage cannot be resampled."<<endl;
2415 return;
2416 }
2417
2418 // RESAMPLE if the sampling interval is not uniform
2419 if ((avgDt - dtmin) > SimTK::Eps) {
2420 dtmin = resample(dtmin, aOrder);
2421 size = getSize();
2422 }
2423
2424 if(size<(2*aOrder)) {
2425 cout<<"Storage.SmoothSpline: too few data points to filter."<<endl;
2426 return;
2427 }
2428
2429 // LOOP OVER COLUMNS
2430 double *times=NULL;
2431 int nc = getSmallestNumberOfStates();
2432 double *signal=NULL;
2433 Array<double> filt(0.0,size);
2434 getTimeColumn(times,0);
2435 for(int i=0;i<nc;i++) {
2436 getDataColumn(i,signal);
2437 Signal::SmoothSpline(aOrder,dtmin,aCutoffFrequency,size,times,signal,&filt[0]);
2438 setDataColumn(i,filt);
2439 }
2440
2441 // CLEANUP
2442 delete[] times;
2443 delete[] signal;
2444 }
2445
2446 void Storage::
lowpassIIR(double aCutoffFrequency)2447 lowpassIIR(double aCutoffFrequency)
2448 {
2449 int size = getSize();
2450 double dtmin = getMinTimeStep();
2451 double avgDt = (_storage[size-1].getTime() - _storage[0].getTime()) / (size-1);
2452
2453 if(dtmin<SimTK::Eps) {
2454 cout<<"Storage.lowpassIIR: storage cannot be resampled."<<endl;
2455 return;
2456 }
2457
2458 // RESAMPLE if the sampling interval is not uniform
2459 if ((avgDt - dtmin) > SimTK::Eps) {
2460 dtmin = resample(dtmin, 5);
2461 size = getSize();
2462 }
2463
2464 if(size<(4)) {
2465 cout<<"Storage.lowpassIIR: too few data points to filter."<<endl;
2466 return;
2467 }
2468
2469 // LOOP OVER COLUMNS
2470 int nc = getSmallestNumberOfStates();
2471 double *signal=NULL;
2472 Array<double> filt(0.0,size);
2473 for(int i=0;i<nc;i++) {
2474 getDataColumn(i,signal);
2475 Signal::LowpassIIR(dtmin,aCutoffFrequency,size,signal,&filt[0]);
2476 setDataColumn(i,filt);
2477 }
2478
2479 // CLEANUP
2480 delete[] signal;
2481 }
2482
2483 void Storage::
lowpassFIR(int aOrder,double aCutoffFrequency)2484 lowpassFIR(int aOrder,double aCutoffFrequency)
2485 {
2486 int size = getSize();
2487 double dtmin = getMinTimeStep();
2488 double avgDt = (_storage[size-1].getTime() - _storage[0].getTime()) / (size-1);
2489
2490 if (dtmin<SimTK::Eps) {
2491 cout<<"Storage.lowpassFIR: storage cannot be resampled."<<endl;
2492 return;
2493 }
2494
2495 // RESAMPLE if the sampling interval is not uniform
2496 if ((avgDt - dtmin) > SimTK::Eps) {
2497 dtmin = resample(dtmin, 5);
2498 size = getSize();
2499 }
2500
2501 if(size<(2*aOrder)) {
2502 cout<<"Storage.lowpassFIR: too few data points to filter."<<endl;
2503 return;
2504 }
2505
2506 // LOOP OVER COLUMNS
2507 int nc = getSmallestNumberOfStates();
2508 double *signal=NULL;
2509 Array<double> filt(0.0,size);
2510 for(int i=0;i<nc;i++) {
2511 getDataColumn(i,signal);
2512 Signal::LowpassFIR(aOrder,dtmin,aCutoffFrequency,size,signal,&filt[0]);
2513 setDataColumn(i,filt);
2514 }
2515
2516 // CLEANUP
2517 delete[] signal;
2518 }
2519
2520
2521 //=============================================================================
2522 // UTILITY
2523 //=============================================================================
2524 //_____________________________________________________________________________
2525 /**
2526 * Find the index of the storage element that occurred immediately before
2527 * or at time aT ( aT <= getTime(index) ).
2528 *
2529 * This method can be much more efficient than findIndex(aT) if a good guess
2530 * is made for aI.
2531 * If aI corresponds to a state which occurred later than aT, an exhaustive
2532 * search is performed by calling findIndex(aT).
2533 *
2534 * @param aI Index at which to start searching.
2535 * @param aT Time.
2536 * @return Index preceding or at time aT. If aT is less than the earliest
2537 * time, 0 is returned.
2538 */
2539 int Storage::
findIndex(int aI,double aT) const2540 findIndex(int aI,double aT) const
2541 {
2542 // MAKE SURE aI IS VALID
2543 if(_storage.getSize()<=0) return(-1);
2544 if((aI>=_storage.getSize())||(aI<0)) aI=0;
2545 if(getStateVector(aI)->getTime()>aT) aI=0;
2546
2547 // SEARCH
2548 //cout << "Storage.findIndex: starting at " << aI << endl;
2549 int i;
2550 for(i=aI;i<_storage.getSize();i++) {
2551 if(aT<getStateVector(i)->getTime()) break;
2552 }
2553 _lastI = i-1;
2554 if(_lastI<0) _lastI=0;
2555 return(_lastI);
2556 }
2557 //_____________________________________________________________________________
2558 /**
2559 * Find the index of the storage element that occurred immediately before
2560 * or at a specified time ( getTime(index) <= aT ).
2561 *
2562 * This method is not very efficient because it always starts its search
2563 * with the first stored state.
2564 *
2565 * @param aT Time.
2566 * @return Index preceding or at time aT. If aT is less than the earliest
2567 * time, 0 is returned.
2568 */
2569 int Storage::
findIndex(double aT) const2570 findIndex(double aT) const
2571 {
2572 if(_storage.getSize()<=0) return(-1);
2573 int i;
2574 for(i=0;i<_storage.getSize();i++) {
2575 if(aT<getStateVector(i)->getTime()) break;
2576 }
2577 _lastI = i-1;
2578 if(_lastI<0) _lastI=0;
2579 return(_lastI);
2580 }
2581 //_____________________________________________________________________________
2582 /**
2583 * Find the range of frames that is between start time and end time
2584 * (inclusive). Return the indices of the bounding frames.
2585 */
findFrameRange(double aStartTime,double aEndTime,int & oStartFrame,int & oEndFrame) const2586 void Storage::findFrameRange(double aStartTime, double aEndTime, int& oStartFrame, int& oEndFrame) const
2587 {
2588 SimTK_ASSERT_ALWAYS(aStartTime <= aEndTime, "Start time must be <= end time");
2589
2590 oStartFrame = findIndex(0, aStartTime);
2591 oEndFrame = findIndex(getSize()-1, aEndTime);
2592 }
2593 //_____________________________________________________________________________
2594 /**
2595 * Resample Storage columns to specified rate. This's done by fitting splines
2596 * to Storage columns and resampling
2597 *
2598 * @param aDT Time interval between adjacent statevectors.
2599 * @return Actual sampling time step (may be clamped)
2600 */
2601 double Storage::
resample(double aDT,int aDegree)2602 resample(double aDT, int aDegree)
2603 {
2604 int numDataRows = _storage.getSize();
2605
2606 if(numDataRows<=1) return aDT;
2607
2608 // Limit aDT based on expected number of samples
2609 int maxSamples = MAX_RESAMPLE_SIZE;
2610 if((getLastTime()-getFirstTime())/aDT > maxSamples) {
2611 double newDT = (getLastTime()-getFirstTime())/maxSamples;
2612 cout<<"Storage.resample: WARNING: resampling at time step "<<newDT<<" (but minimum time step is "<<aDT<<")"<<endl;
2613 aDT = newDT;
2614 }
2615
2616 GCVSplineSet *splineSet = new GCVSplineSet(aDegree,this);
2617
2618 Array<std::string> saveLabels = getColumnLabels();
2619 // Free up memory used by Storage
2620 _storage.setSize(0);
2621 // For every column, collect data and fit spline to originalTimes, dataColumn.
2622 Storage *newStorage = splineSet->constructStorage(0,aDT);
2623 newStorage->setInDegrees(isInDegrees());
2624 copyData(*newStorage);
2625
2626 setColumnLabels(saveLabels);
2627
2628 delete newStorage;
2629 delete splineSet;
2630
2631 return aDT;
2632 }
2633 //_____________________________________________________________________________
2634 /**
2635 * Resample using linear interpolation
2636 */
2637 double Storage::
resampleLinear(double aDT)2638 resampleLinear(double aDT)
2639 {
2640 int numDataRows = _storage.getSize();
2641
2642 if(numDataRows<=1) return aDT;
2643
2644 // Limit aDT based on expected number of samples
2645 int maxSamples = MAX_RESAMPLE_SIZE;
2646 if((getLastTime()-getFirstTime())/aDT > maxSamples) {
2647 double newDT = (getLastTime()-getFirstTime())/maxSamples;
2648 cout<<"Storage.resampleLinear: WARNING: resampling at time step "<<newDT<<" (but minimum time step is "<<aDT<<")"<<endl;
2649 aDT = newDT;
2650 }
2651
2652 Array<std::string> saveLabels = getColumnLabels();
2653
2654 // HOW MANY TIME STEPS?
2655 double ti = getFirstTime();
2656 double tf = getLastTime();
2657 int nr = IO::ComputeNumberOfSteps(ti,tf,aDT);
2658
2659 Storage *newStorage = new Storage(nr);
2660
2661 // LOOP THROUGH THE DATA
2662 int ny=0;
2663 double *y=NULL;
2664 StateVector vec;
2665 for(int i=0; i<nr; i++) {
2666 double t = ti+aDT*(double)i;
2667 // INTERPOLATE THE STATES
2668 ny = getDataAtTime(t,ny,&y);
2669 newStorage->append(t,ny,y);
2670 }
2671
2672 copyData(*newStorage);
2673
2674 delete newStorage;
2675 delete[] y;
2676
2677 return aDT;
2678 }
2679
2680 //_____________________________________________________________________________
2681 /**
2682 * interpolateAt passed in list of time values. Tries to check if there is a data
2683 * row at the specified times to avoid introducing duplicates.
2684 */
interpolateAt(const Array<double> & targetTimes)2685 void Storage::interpolateAt(const Array<double> &targetTimes)
2686 {
2687 for(int i=0; i<targetTimes.getSize();i++){
2688 double t = targetTimes[i];
2689 // get index for t
2690 int tIndex = findIndex(t);
2691 // If within small number from t then pass
2692 double actualTime=0.0;
2693 if (tIndex < getSize()-1){
2694 getTime(tIndex+1, actualTime);
2695 if (fabs(actualTime - t)<1e-6)
2696 continue;
2697 }
2698 // or could be the following one too
2699 getTime(tIndex, actualTime);
2700 if (fabs(actualTime - t)<1e-6)
2701 continue;
2702 // create a StateVector and add it
2703 double *y=NULL;
2704 int ny=0;
2705 StateVector vec;
2706 // INTERPOLATE THE STATES
2707 ny = getDataAtTime(t,ny,&y);
2708 vec.setStates(t, SimTK::Vector_<double>(ny, y));
2709
2710 _storage.insert(tIndex+1, vec);
2711 }
2712 }
2713 //=============================================================================
2714 // IO
2715 //=============================================================================
2716 //_____________________________________________________________________________
2717 /**
2718 * Set name of output file to be written into.
2719 * This has the side effect of opening the file for writing. The header will not have the correct
2720 * number of rows but this may not be an issue for version 2 of the Storage class
2721 */
2722 void Storage::
setOutputFileName(const std::string & aFileName)2723 setOutputFileName(const std::string& aFileName)
2724 {
2725 assert(_fileName=="");
2726 _fileName = aFileName;
2727
2728 // OPEN THE FILE
2729 _fp = IO::OpenFile(aFileName,"w");
2730 if(_fp==NULL) throw(Exception("Could not open file "+aFileName));
2731 // WRITE THE HEADER
2732 writeHeader(_fp);
2733 writeDescription(_fp);
2734 // WRITE THE COLUMN LABELS
2735 writeColumnLabels(_fp);
2736 }
2737 //_____________________________________________________________________________
2738 /**
2739 * Print the contents of this storage instance to a file.
2740 *
2741 * The argument aMode specifies whether the file is opened for writing, "w",
2742 * or appending, "a". If a bad value for aMode is sent in, the file is opened
2743 * for writing.
2744 *
2745 * The total number of characters written is returned. If an error occurred,
2746 * a negative number is returned.
2747 *
2748 * @param aFileName Name of file to which to save.
2749 * @param aMode Writing mode: "w" means write and "a" means append. The
2750 * default is "w".
2751 * @param aComment string to be written to the file header (preceded by # per SIMM)
2752 * @return true on success
2753 */
2754 bool Storage::
print(const string & aFileName,const string & aMode,const string & aComment) const2755 print(const string &aFileName,const string &aMode, const string& aComment) const
2756 {
2757 // OPEN THE FILE
2758 FILE *fp = IO::OpenFile(aFileName,aMode);
2759 if(fp==NULL) return(false);
2760
2761 // WRITE THE HEADER
2762 int n=0,nTotal=0;
2763 n = writeHeader(fp);
2764 if(n<0) {
2765 cout << "Storage.print(const string&,const string&): failed to" << endl
2766 << " write header to file " << aFileName << endl;
2767 return(false);
2768 }
2769
2770 // WRITE SIMM HEADER
2771 if(_writeSIMMHeader) {
2772 n = writeSIMMHeader(fp, -1, aComment.c_str());
2773 if(n<0) {
2774 cout << "Storage.print(const string&,const string&): failed to" << endl
2775 << " write SIMM header to file " << aFileName << endl;
2776 return(false);
2777 }
2778 }
2779
2780 // WRITE THE DESCRIPTION
2781 n = writeDescription(fp);
2782 if(n<0) {
2783 cout << "Storage.print(const string&,const string&): failed to" << endl
2784 << " write description to file " << aFileName << endl;
2785 return(false);
2786 }
2787
2788 // WRITE THE COLUMN LABELS
2789 n = writeColumnLabels(fp);
2790 if(n<0) {
2791 cout << "Storage.print(const string&,const string&): failed to" << endl
2792 << " write column labels to file " << aFileName << endl;
2793 return(false);
2794 }
2795
2796 //printf("Storage.cpp:print storage=%x n=%d ",&_storage, _storage.getSize());
2797 //std::cout << aFileName << endl;
2798
2799 // VECTORS
2800 for(int i=0;i<_storage.getSize();i++) {
2801 n = getStateVector(i)->print(fp);
2802 if(n<0) {
2803 cout << "Storage.print(const string&,const string&): error printing to " << aFileName;
2804 return(false);
2805 }
2806 nTotal += n;
2807 }
2808
2809 // CLOSE
2810 fclose(fp);
2811
2812 return(nTotal!=0);
2813 }
2814 //_____________________________________________________________________________
2815 /**
2816 * Print the contents of this storage instance to a file named by the argument
2817 * aFileaName using uniform time spacing.
2818 *
2819 * The argument aMode specifies whether the file is opened for writing, "w",
2820 * or appending, "a". If a bad value for aMode is sent in, the file is opened
2821 * for writing.
2822 *
2823 * The argument aDT specifies the time spacing.
2824 *
2825 * The total number of characters written is returned. If an error occurred,
2826 * a negative number is returned.
2827 */
2828 int Storage::
print(const string & aFileName,double aDT,const string & aMode) const2829 print(const string &aFileName,double aDT,const string &aMode) const
2830 {
2831 // CHECK FOR VALID DT
2832 if(aDT<=0) return(0);
2833
2834 if (_fp!= NULL) fclose(_fp);
2835 // OPEN THE FILE
2836 FILE *fp = IO::OpenFile(aFileName,aMode);
2837 if(fp==NULL) return(-1);
2838
2839 // HOW MANY TIME STEPS?
2840 double ti = getFirstTime();
2841 double tf = getLastTime();
2842 int nr = IO::ComputeNumberOfSteps(ti,tf,aDT);
2843 //printf("Storage.cpp:print ti=%f tf=%f dt=%f nr=%d ", ti,tf,aDT,nr );
2844 //std::cout << aFileName << endl;
2845
2846 // WRITE THE HEADER
2847 int n,nTotal=0;
2848 n = writeHeader(fp,aDT);
2849 if(n<0) {
2850 cout << "Storage.print(const string&,const string&,double): failed to" << endl
2851 << " write header of file " << aFileName << endl;
2852 return(n);
2853 }
2854
2855 // WRITE SIMM HEADER
2856 if(_writeSIMMHeader) {
2857 n = writeSIMMHeader(fp,aDT);
2858 if(n<0) {
2859 cout << "Storage.print(const string&,const string&): failed to" << endl
2860 << " write SIMM header to file " << aFileName << endl;
2861 return(n);
2862 }
2863 }
2864
2865 // WRITE THE DESCRIPTION
2866 n = writeDescription(fp);
2867 if(n<0) {
2868 cout << "Storage.print(const string&,const string&): failed to" << endl
2869 << " write description to file " << aFileName << endl;
2870 return(n);
2871 }
2872
2873 // WRITE THE COLUMN LABELS
2874 n = writeColumnLabels(fp);
2875 if(n<0) {
2876 cout << "Storage.print(const string&,const string&): failed to" << endl
2877 << " write column labels to file " << aFileName << endl;
2878 return(n);
2879 }
2880
2881 // LOOP THROUGH THE DATA
2882 int i,ny=0;
2883 double t,*y=NULL;
2884 StateVector vec;
2885 for(t=ti,i=0;i<nr;i++,t=ti+aDT*(double)i) {
2886
2887 // INTERPOLATE THE STATES
2888 ny = getDataAtTime(t,ny,&y);
2889 vec.setStates(t, SimTK::Vector_<double>(ny, y));
2890
2891 // PRINT
2892 n = vec.print(fp);
2893 if(n<0) {
2894 cout << "Storage.print(const string&,const string&): error printing to " << aFileName;
2895 return(n);
2896 }
2897 nTotal += n;
2898 }
2899
2900 // CLEANUP
2901 fclose(fp);
2902 if(y!=NULL) { delete[] y; y=NULL; }
2903
2904 return(nTotal);
2905 }
2906
2907 void Storage::
printResult(const Storage * aStorage,const std::string & aName,const std::string & aDir,double aDT,const std::string & aExtension)2908 printResult(const Storage *aStorage,const std::string &aName,
2909 const std::string &aDir,double aDT,const std::string &aExtension)
2910 {
2911 if(!aStorage) return;
2912 std::string path = (aDir=="") ? "." : aDir;
2913 std::string name = (aName.rfind(aExtension)==string::npos)? (path + "/" + aName + aExtension) : (path + "/" + aName);
2914 if(aDT<=0.0) aStorage->print(name);
2915 else aStorage->print(name,aDT);
2916 }
2917
2918 //_____________________________________________________________________________
2919 /**
2920 * Write the header.
2921 */
2922 int Storage::
writeHeader(FILE * rFP,double aDT) const2923 writeHeader(FILE *rFP,double aDT) const
2924 {
2925 if(rFP==NULL) return(-1);
2926
2927 // COMPUTE ATTRIBUTES
2928 int nr,nc;
2929 if(aDT<=0) {
2930 nr = _storage.getSize();
2931 } else {
2932 double ti = getFirstTime();
2933 double tf = getLastTime();
2934 nr = IO::ComputeNumberOfSteps(ti,tf,aDT);
2935 }
2936 nc = getSmallestNumberOfStates()+1;
2937
2938 // ATTRIBUTES
2939 fprintf(rFP,"%s\n",getName().c_str());
2940 fprintf(rFP,"version=%d\n",LatestVersion);
2941 fprintf(rFP,"nRows=%d\n",nr);
2942 fprintf(rFP,"nColumns=%d\n",nc);
2943 fprintf(rFP,"inDegrees=%s\n",(_inDegrees?"yes":"no"));
2944
2945 return(0);
2946 }
2947 //_____________________________________________________________________________
2948 /**
2949 * Write a header appropriate for SIMM motion files.
2950 *
2951 * @return SIMM header.
2952 */
2953 int Storage::
writeSIMMHeader(FILE * rFP,double aDT,const char * aComment) const2954 writeSIMMHeader(FILE *rFP,double aDT, const char *aComment) const
2955 {
2956 if(rFP==NULL) return(-1);
2957
2958 // COMMENT
2959 // NOTE: avoid writing empty comment because SIMM seems to screw up parsing
2960 // of a line with only a #
2961 if (aComment && aComment[0])
2962 fprintf(rFP,"\n# %s\n", aComment);
2963 else
2964 fprintf(rFP,"\n# SIMM Motion File Header:\n");
2965
2966 // NAME
2967 fprintf(rFP,"name %s\n",getName().c_str());
2968
2969 // COLUMNS
2970 fprintf(rFP,"datacolumns %d\n",getSmallestNumberOfStates()+1);
2971
2972 // ROWS
2973 int nRows;
2974 if(aDT<=0) {
2975 nRows = _storage.getSize();
2976 } else {
2977 nRows = IO::ComputeNumberOfSteps(getFirstTime(),getLastTime(),aDT);
2978 }
2979 fprintf(rFP,"datarows %d\n",nRows);
2980
2981 // OTHER DATA
2982 fprintf(rFP,"otherdata 1\n");
2983
2984 // RANGE
2985 fprintf(rFP,"range %lf %lf\n",getFirstTime(),getLastTime());
2986
2987 // Other data from the map
2988 MapKeysToValues::const_iterator iter;
2989
2990 for(iter = _keyValueMap.begin(); iter != _keyValueMap.end(); iter++){
2991 fprintf(rFP,"%s %s\n",iter->first.c_str(), iter->second.c_str());
2992 }
2993 return(0);
2994 }
2995 //_____________________________________________________________________________
2996 /**
2997 * Write the description.
2998 */
2999 int Storage::
writeDescription(FILE * rFP) const3000 writeDescription(FILE *rFP) const
3001 {
3002 if(rFP==NULL) return(-1);
3003
3004 // DESCRIPTION
3005 string descrip = getDescription();
3006 size_t len = descrip.size();
3007 if((len>0)&&(descrip[len-1]!='\n')) {
3008 fprintf(rFP,"%s\n",descrip.c_str());
3009 } else {
3010 fprintf(rFP,"%s",descrip.c_str());
3011 }
3012
3013 // DESCRIPTION TOKEN
3014 fprintf(rFP,"%s\n",_headerToken.c_str());
3015
3016 return(0);
3017 }
3018 //_____________________________________________________________________________
3019 /**
3020 * Write the column labels.
3021 *
3022 * @param rFP File pointer.
3023 */
3024 int Storage::
writeColumnLabels(FILE * rFP) const3025 writeColumnLabels(FILE *rFP) const
3026 {
3027 if(rFP==NULL) return(-1);
3028
3029 if(_columnLabels.getSize()) {
3030 fprintf(rFP,"%s",_columnLabels[0].c_str());
3031 for(int i=1;i<_columnLabels.getSize();i++) fprintf(rFP,"\t%s",_columnLabels[i].c_str());
3032 fprintf(rFP,"\n");
3033 } else {
3034 // Write default column labels
3035 fprintf(rFP,"time");
3036 int n=getSmallestNumberOfStates();
3037 for(int i=0;i<n;i++) fprintf(rFP,"\tstate_%d",i);
3038 fprintf(rFP,"\n");
3039 }
3040
3041 return(0);
3042 }
addToRdStorage(Storage & rStorage,double aStartTime,double aEndTime)3043 void Storage::addToRdStorage(Storage& rStorage, double aStartTime, double aEndTime)
3044 {
3045 bool addedData = false;
3046 double time, stateTime;
3047
3048 /* Loop through the rows in rStorage from aStartTime to aEndTime,
3049 * looking for a match (by time) in the rows of Storage.
3050 * If you find a match, add the columns in the Storage
3051 * to the end of the state vector in the rStorage. If you
3052 * don't find one, it's a fatal error so throw an exception.
3053 * Don't add a column if its name is 'unassigned'.
3054 */
3055 int i, j, startIndex, endIndex;
3056 rStorage.findFrameRange(aStartTime, aEndTime, startIndex, endIndex);
3057 int numColumns=getColumnLabels().getSize();
3058 for (i = startIndex; i <= endIndex; i++)
3059 {
3060 rStorage.getTime(i, stateTime);
3061 for (j = 0; j < getSize(); j++) {
3062 /* Assume that the first column is 'time'. */
3063 time = getStateVector(j)->getTime();
3064 // The following tolerance is a hack. Previously, it used 0.0001
3065 // which caused values to be duplicated in cases where time
3066 // steps were within the tolerance. This method should only be
3067 // used to concatenate data columns from the same simulation
3068 // or analysis results.
3069 if (EQUAL_WITHIN_TOLERANCE(time, stateTime, SimTK::SignificantReal)) {
3070 Array<double>& states = rStorage.getStateVector(i)->getData();
3071 // Start at 1 to avoid duplicate time column
3072 for (int k = 1; k < numColumns; k++)
3073 {
3074 if (_columnLabels[k] != "Unassigned")
3075 {
3076 states.append(getStateVector(j)->getData().get(k-1));
3077 addedData = true;
3078 }
3079 }
3080 break;
3081 }
3082 }
3083 if (j == getSize()) {
3084 stringstream errorMessage;
3085 errorMessage << "Error: no data found at time " << stateTime
3086 << " in " << _fileName;
3087 throw (Exception(errorMessage.str()));
3088 }
3089 }
3090
3091 /* Add the coordinate names to the Storage (if at least
3092 * one row of data was added to the object).
3093 */
3094 if (addedData)
3095 {
3096 const Array<std::string>& oldColumnLabels =rStorage.getColumnLabels();
3097 Array<std::string> newColumnLabels(oldColumnLabels);
3098 for (int i = 1; i < _columnLabels.getSize(); i++) // Start at 1 to avoid duplicate time label
3099 {
3100 if (!(_columnLabels[i] == "Unassigned"))
3101 newColumnLabels.append( _columnLabels[i]);
3102 }
3103 rStorage.setColumnLabels(newColumnLabels);
3104 }
3105 }
3106 //_____________________________________________________________________________
3107 /**
3108 * Processing of special reserved words used by SIMM and corresponding values
3109 * The keys and their corresponding values are maintained in _keyValueMap
3110 */
3111 // Add a new Pair
3112 void Storage::
addKeyValuePair(const std::string & aKey,const std::string & aValue)3113 addKeyValuePair(const std::string& aKey, const std::string& aValue)
3114 {
3115 if (_keyValueMap.find(aKey)!=_keyValueMap.end()){
3116 // Should we warn here? or append in case of comment?
3117 }
3118 _keyValueMap[aKey]=aValue;
3119 //cout << "key, value " << aKey << ", " << aValue << endl;
3120
3121 }
3122 // Lookup the value for a key
3123 void Storage::
getValueForKey(const std::string & aKey,std::string & rValue) const3124 getValueForKey(const std::string& aKey, std::string& rValue) const
3125 {
3126 MapKeysToValues::const_iterator iter =_keyValueMap.find(aKey);
3127 if (iter!=_keyValueMap.end()){
3128 rValue=iter->second;
3129 }
3130 else
3131 rValue="";
3132
3133 }
3134 // Check if the key exists
hasKey(const std::string & aKey) const3135 bool Storage::hasKey(const std::string& aKey) const
3136 {
3137 return (_keyValueMap.find(aKey)!=_keyValueMap.end());
3138 }
3139
3140 //_____________________________________________________________________________
3141 /**
3142 * Check that a Token belongs to a list of reserved keys
3143 *
3144 * @returns true on success (meaningful values of rNumRows, rNumColumns)
3145 */
isSimmReservedToken(const std::string & aToken)3146 bool Storage::isSimmReservedToken(const std::string& aToken)
3147 {
3148 for(int i=0; i<numSimmReservedKeys; i++){
3149 if (simmReservedKeys[i]==aToken)
3150 return true;
3151 }
3152 return false;
3153 }
3154 //_____________________________________________________________________________
3155 /**
3156 * parse headers of OpenSim::Storage file or SIMM motion file into a Storage object
3157 * and populate rNumRows, rNumColumns
3158 * a Map between some keywords and their values for SIMM compatibility
3159 *
3160 * @returns true on success (meaningful values of rNumRows, rNumColumns)
3161 */
parseHeaders(std::ifstream & aStream,int & rNumRows,int & rNumColumns)3162 bool Storage::parseHeaders(std::ifstream& aStream, int& rNumRows, int& rNumColumns)
3163 {
3164 bool done=false;
3165 bool firstLine=true;
3166 _fileVersion = 0;
3167 // Parse until the end of header
3168 while(!done){
3169 // NAME
3170 string line = IO::ReadLine(aStream);
3171 // Always Strip leading and trailing spaces and tabs
3172 IO::TrimLeadingWhitespace(line);
3173 IO::TrimTrailingWhitespace(line);
3174 if(line.empty() && !aStream.good()) {
3175 cout << "Storage: ERROR- no more lines in storage file." << endl;
3176 return false;
3177 }
3178 if (line.length()==0)
3179 continue;
3180
3181 // Here we have a line. Should be one of:
3182 // name
3183 // nRows, nr, datarows
3184 // nColumns, nc, datacolumns
3185 // nd, nDescrip
3186 // # Comment
3187 size_t delim = line.find_first_of(" \t=");
3188 string key = line.substr(0, delim);
3189 size_t restidx = line.find_first_not_of(" \t=", delim);
3190 string rest = (restidx==string::npos) ? "" : line.substr(restidx);
3191
3192 if (key== "name"){
3193 setName(rest);
3194 }
3195 else if (key== "nr" || key== "nRows" || key== "datarows"){
3196 rNumRows = atoi(rest.c_str());
3197 }
3198 else if (key== "nc" || key== "nColumns" || key== "datacolumns"){
3199 rNumColumns = atoi(rest.c_str());
3200 }
3201 else if (isSimmReservedToken(key)) {
3202 _keyValueMap[key]= rest;
3203 }
3204 else if (key== "units") {
3205 _units = Units(rest);
3206 }
3207 else if (key=="version") {
3208 _fileVersion = atoi(rest.c_str());
3209 }
3210 else if (key=="inDegrees") {
3211 string lower = IO::Lowercase(rest);
3212 bool inDegrees = (lower=="yes" || lower=="y");
3213 setInDegrees(inDegrees);
3214 }
3215 else if (key=="Angles" && _fileVersion==0){
3216 if (line == "Angles are in degrees.")
3217 setInDegrees(true);
3218 else if (line == "Angles are in radians.")
3219 setInDegrees(false);
3220 }
3221 else if(key== DEFAULT_HEADER_TOKEN){
3222 break;
3223 }
3224 else if (firstLine){ // Storage file have their names without "name prefix on first line"
3225 setName(line);
3226 }
3227 firstLine=false;
3228 }
3229
3230 if (_fileVersion < 1) {
3231 cout << "Old version storage/motion file encountered" << endl;
3232 }
3233
3234 if(rNumColumns==0 || rNumRows==0) {
3235 return false;
3236 }
3237
3238 return true;
3239 }
3240 //_____________________________________________________________________________
3241 /**
3242 * This function exchanges the time column (including the label) with the column
3243 * at the passed in aColumnIndex. The index is zero based relative to the Data
3244 */
3245 void Storage::
exchangeTimeColumnWith(int aColumnIndex)3246 exchangeTimeColumnWith(int aColumnIndex)
3247 {
3248 StateVector* vec;
3249 for(int i=0; i< _storage.getSize(); i++){
3250 vec = getStateVector(i);
3251 double swap = vec->getData().get(aColumnIndex);
3252 double time=vec->getTime();
3253 vec->setDataValue(aColumnIndex, time);
3254 vec->setTime(swap);
3255 }
3256 // Now column labels
3257 string swap = _columnLabels.get(0);
3258 _columnLabels.set(aColumnIndex+1, swap);
3259 _columnLabels.set(0, "time");
3260
3261 }
3262 //_____________________________________________________________________________
3263 /**
3264 * If that was a SIMM motion file post-process it to account for
3265 * lack of time column, assumption of uniform time
3266 * other kinds of processing can be added here to account for calc_derivatives, ...
3267 *
3268 * This is all untested since all motion files we deal with so far do not exhibit this behavior.
3269 */
postProcessSIMMMotion()3270 void Storage::postProcessSIMMMotion()
3271 {
3272 Array<std::string> currentLabels = getColumnLabels();
3273 // If time is not first column check if it exists somewhere else and exchange
3274 if (!(currentLabels.get(0)=="time")){
3275 int timeColumnIndx = currentLabels.findIndex("time");
3276 if (timeColumnIndx!=-1){ // Exchange column timeColumnIndx with time
3277 exchangeTimeColumnWith(timeColumnIndx);
3278 }
3279 else {
3280 // There was no time column altogether. make one based on
3281 // range (if specified) and number of entries
3282 MapKeysToValues::iterator iter;
3283 iter = _keyValueMap.find("range"); // Should we check "Range", "RANGE" too?
3284 if (iter !=_keyValueMap.end()){
3285 string rangeValue = iter->second;
3286 double start, end;
3287 sscanf(rangeValue.c_str(), "%lf %lf", &start, &end);
3288 if (_storage.getSize()<2){ // Something wrong throw exception unless start==end
3289 if (start !=end){
3290 stringstream errorMessage;
3291 errorMessage << "Error: Motion file has inconsistent headers";
3292 throw (Exception(errorMessage.str()));
3293 }
3294 else if (_storage.getSize()==1){
3295 // Prepend a Time column
3296 StateVector vec = _storage.get(0);
3297 vec.getData().append(0.0);
3298 _columnLabels.append("time");
3299 exchangeTimeColumnWith(_columnLabels.findIndex("time"));
3300 }
3301 else
3302 throw (Exception("File has no data"));
3303 }
3304 else { // time column from range, size
3305 double timeStep = (end - start)/(_storage.getSize()-1);
3306 _columnLabels.append("time");
3307 for(int i=0; i<_storage.getSize(); i++){
3308 Array<double>& data=_storage.updElt(i).getData();
3309 data.append(i*timeStep);
3310 }
3311 int timeColumnIndex=_columnLabels.findIndex("time");
3312 exchangeTimeColumnWith(timeColumnIndex-1);
3313 }
3314 }
3315 else { // No time specified altogether
3316 throw (Exception("Storage::postProcessSIMMMotion no time column found."));
3317 }
3318 }
3319 }
3320 }
3321 /**
3322 * Compare column named "aColumnName" in two storage objects
3323 * If endTime is not specified the comparison goes to the end of the file
3324 * @returns the difference or SimTK::Infinity if times do not match up.
3325 *
3326 * NOTE: This assumes same time sampling between both Storages.
3327 */
compareColumn(Storage & aOtherStorage,const std::string & aColumnName,double startTime,double endTime)3328 double Storage::compareColumn(Storage& aOtherStorage, const std::string& aColumnName, double startTime, double endTime)
3329 {
3330 //Subtract one since, the data does not include the time column anymore.
3331 int thisColumnIndex=_columnLabels.findIndex(aColumnName)-1;
3332 int otherColumnIndex = aOtherStorage._columnLabels.findIndex(aColumnName)-1;
3333
3334 double theDiff = SimTK::NaN;
3335
3336 if ((thisColumnIndex==-2)||(otherColumnIndex==-2))// not found is now -2 since we subtracted 1 already
3337 return theDiff;
3338
3339 // Now we have two columnNumbers. get the data and compare
3340 Array<double> thisData, otherData;
3341 Array<double> thisTime, otherTime;
3342 getDataColumn(thisColumnIndex, thisData);
3343 getTimeColumn(thisTime);
3344
3345 aOtherStorage.getDataColumn(otherColumnIndex, otherData);
3346 aOtherStorage.getTimeColumn(otherTime);
3347
3348 // make sure times match (we probably should make this more flexible to interpolate missing values
3349 // but that's not needed for now.
3350 theDiff = -SimTK::Infinity;
3351 int startIndex = findIndex(startTime);
3352 int startIndexOther = aOtherStorage.findIndex(startTime);
3353 int endIndex = (endTime==-1.0)?getSize():findIndex(endTime);
3354 int endIndexOther = (endTime==-1.0)?(aOtherStorage.getSize()):aOtherStorage.findIndex(endTime);
3355 // Make sure we have same number of rows
3356 if ((endIndex -startIndex)!= (endIndexOther -startIndexOther)) return (theDiff);
3357
3358 for(int i=startIndex; i< endIndex; i++){
3359 if (abs(thisTime[i]-otherTime[startIndexOther+i-startIndex]) > 1E-3)
3360 return SimTK::Infinity;
3361 theDiff = std::max(theDiff, fabs(thisData[i]-otherData[startIndexOther+i-startIndex]));
3362 }
3363 return theDiff;
3364 }
3365 /**
3366 * Compare column named "aColumnName" in two storage objects
3367 * If endTime is not specified the comparison goes to the end of the file
3368 * @returns the root mean square, using a spline to calculate values where the times do not match up.
3369 */
compareColumnRMS(const Storage & aOtherStorage,const std::string & aColumnName,double startTime,double endTime) const3370 double Storage::compareColumnRMS(const Storage& aOtherStorage, const std::string& aColumnName, double startTime, double endTime) const
3371 {
3372 int thisColumnIndex = getStateIndex(aColumnName);
3373 int otherColumnIndex = aOtherStorage.getStateIndex(aColumnName);
3374
3375 if ((thisColumnIndex < 0) || (otherColumnIndex < 0))
3376 return SimTK::NaN;
3377
3378 // Now we have two columnNumbers. get the data and compare
3379 Array<double> thisData, otherData;
3380 Array<double> thisTime, otherTime;
3381 getDataColumn(thisColumnIndex, thisData);
3382 getTimeColumn(thisTime);
3383 aOtherStorage.getDataColumn(otherColumnIndex, otherData);
3384 aOtherStorage.getTimeColumn(otherTime);
3385
3386 // get start and end indices
3387 if (SimTK::isNaN(startTime))
3388 startTime = max(thisTime[0], otherTime[0]);
3389 int startIndex = findIndex(startTime);
3390 if (thisTime[startIndex] < startTime)
3391 ++startIndex;
3392 if (SimTK::isNaN(endTime))
3393 endTime = min(thisTime.getLast(), otherTime.getLast());
3394 int endIndex = findIndex(endTime);
3395
3396 // create spline in case time values do not match up
3397 GCVSpline spline(3, otherTime.getSize(), &otherTime[0], &otherData[0]);
3398
3399 double rms = 0.;
3400
3401 for(int i = startIndex; i <= endIndex; i++) {
3402 SimTK::Vector inputTime(1, thisTime[i]);
3403 double diff = thisData[i] - spline.calcValue(inputTime);
3404 rms += diff * diff;
3405 }
3406
3407 rms = sqrt(rms/(endIndex - startIndex));
3408
3409 return rms;
3410 }
3411 /**
3412 * Compare this storage object with a standard storage object. Find RMS
3413 * errors for columns occurring in both storage objects, and record the
3414 * values and column names in the comparisons and columnsUsed Arrays.
3415 */
compareWithStandard(const Storage & standard,std::vector<string> & columnsUsed,std::vector<double> & comparisons) const3416 void Storage::compareWithStandard(const Storage& standard, std::vector<string>& columnsUsed, std::vector<double>& comparisons) const
3417 {
3418 int maxColumns = _columnLabels.getSize();
3419
3420 for (int i = 1; i < maxColumns; ++i) {
3421 double comparison = compareColumnRMS(standard, _columnLabels[i]);
3422 if (!SimTK::isNaN(comparison)) {
3423 comparisons.push_back(comparison);
3424 columnsUsed.push_back(_columnLabels[i]);
3425 }
3426 }
3427 }
3428
makeStorageLabelsUnique()3429 bool Storage::makeStorageLabelsUnique() {
3430 Array<std::string> lbls = getColumnLabels();
3431 std::string offending = "";
3432 bool changedLabels = false;
3433 for(int i = 0; i < lbls.getSize(); i++){
3434 bool isUnique = (lbls.findIndex(lbls[i]) == i);
3435 if (!isUnique) { // Make new names
3436 offending = lbls[i];
3437 bool exist = true;
3438 std::string newName = offending;
3439 changedLabels = true;
3440 int c = 1;
3441 while (exist) {
3442 char cString[20];
3443 sprintf(cString,"%d", c);
3444 newName = std::string(cString) + "_" + offending;
3445 exist = (lbls.findIndex(newName) != -1);
3446 c++;
3447 }
3448 lbls[i] = newName;
3449 }
3450 }
3451 if (changedLabels) setColumnLabels(lbls);
3452 const bool labelsWereUnique = (!changedLabels);
3453 return labelsWereUnique;
3454 }
3455
storageLabelsAreUnique() const3456 bool Storage::storageLabelsAreUnique() const {
3457 const auto& lbls = getColumnLabels();
3458 for(int i = 0; i < lbls.getSize(); i++) {
3459 const bool isUnique = (lbls.findIndex(lbls[i]) == i);
3460 if (!isUnique) return false;
3461 }
3462 return true;
3463 }
3464