// // DTW.h // FastDTW-x // // Created by Melo Yao on 12/6/13. // Copyright (c) 2013 melo.yao. All rights reserved. // #ifndef __FastDTW_x__DTW__ #define __FastDTW_x__DTW__ #include "Foundation.h" #include "WarpPath.h" #include "TimeSeries.h" #include "ColMajorCell.h" #include "FDMath.h" #include "TimeWarpInfo.h" #include "SearchWindow.h" #include "PartialWindowMatrix.h" #include "MemoryResidentMatrix.h" #include #include FD_NS_START namespace STRI { using namespace std; template ValueType calcWarpCost(const WarpPath& path,const TimeSeries& tsI, const TimeSeries& tsJ, const DistanceFunction& distFn) { ValueType totalCost = 0.0; for (JInt p =0; p ValueType getWarpDistBetween(TimeSeries const& tsI, TimeSeries const& tsJ, DistanceFunction const& distFn) { // The space complexity is 2*tsJ.size(). Dynamic time warping is symmetric so switching the two time series // parameters does not effect the final warp cost but can reduce the space complexity by allowing tsJ to // be set as the shorter time series and only requiring 2 columns of size |tsJ| rather than 2 larger columns of // size |tsI|. if (tsI.size() < tsJ.size()) { return getWarpDistBetween(tsJ, tsI, distFn); } vector lastColumn(tsJ.size()); vector currColumn(tsJ.size()); JInt maxI = tsI.size() - 1; JInt maxJ = tsJ.size() - 1; // Calculate the values for the first column, from the bottom up. currColumn[0] = distFn.calcDistance(*tsI.getMeasurementVector(0), *tsJ.getMeasurementVector(0)); for (JInt j = 1; j* lastCol = &lastColumn; vector* currCol = &currColumn; for (JInt i = 1; i* temp = lastCol; lastCol = currCol; currCol = temp; // Calculate the value for the bottom row of the current column // (i,0) = LocalCost(i,0) + GlobalCost(i-1,0) (*currCol)[0] = (*lastCol)[0] + distFn.calcDistance(*tsI.getMeasurementVector(i), *tsJ.getMeasurementVector(0)); for (int j=1; j<=maxJ; j++) // j = rows { // (i,j) = LocalCost(i,j) + minGlobalCost{(i-1,j),(i-1,j-1),(i,j-1)} ValueType minGlobalCost = fd_min(lastCol->at(j), fd_min(lastCol->at(j-1), currCol->at(j-1))); (*currCol)[j] = minGlobalCost + distFn.calcDistance(*tsI.getMeasurementVector(i), *tsJ.getMeasurementVector(j)); } // end for loop } return currCol->at(maxJ); } template const TimeWarpInfo getWarpInfoBetween(TimeSeries const& tsI, TimeSeries const& tsJ, DistanceFunction const& distFn) { // COST MATRIX: // 5|_|_|_|_|_|_|E| E = min Global Cost // 4|_|_|_|_|_|_|_| S = Start point // 3|_|_|_|_|_|_|_| each cell = min global cost to get to that point // j 2|_|_|_|_|_|_|_| // 1|_|_|_|_|_|_|_| // 0|S|_|_|_|_|_|_| // 0 1 2 3 4 5 6 // i // access is M(i,j)... column-row vector > costMatrix; costMatrix.reserve(tsI.size()); JInt jSize = tsJ.size(); for (JInt i = 0; i(jSize)); } JInt maxI = tsI.size() - 1; JInt maxJ = tsJ.size() - 1; costMatrix[0][0] = distFn.calcDistance(*tsI.getMeasurementVector(0), *tsJ.getMeasurementVector(0)); for (int j=1; j<=maxJ; j++) costMatrix[0][j] = costMatrix[0][j-1] + distFn.calcDistance(*tsI.getMeasurementVector(0), *tsJ.getMeasurementVector(j)); for (int i=1; i<=maxI; i++) // i = columns { // Calculate the value for the bottom row of the current column // (i,0) = LocalCost(i,0) + GlobalCost(i-1,0) costMatrix[i][0] = costMatrix[i-1][0] + distFn.calcDistance(*tsI.getMeasurementVector(i), *tsJ.getMeasurementVector(0)); for (int j=1; j<=maxJ; j++) // j = rows { // (i,j) = LocalCost(i,j) + minGlobalCost{(i-1,j),(i-1,j-1),(i,j-1)} ValueType minGlobalCost = fd_min(costMatrix[i-1][j], fd_min(costMatrix[i-1][j-1], costMatrix[i][j-1])); costMatrix[i][j] = minGlobalCost + distFn.calcDistance(*tsI.getMeasurementVector(i), *tsJ.getMeasurementVector(j)); } } ValueType minimumCost = costMatrix[maxI][maxJ]; WarpPath minCostPath(maxI + maxJ - 1); JInt i = maxI; JInt j = maxJ; minCostPath.addFirst(i,j); while (i>0 || j>0) { ValueType diagCost; ValueType leftCost; ValueType downCost; if ((i>0) && (j>0)) { diagCost = costMatrix[i-1][j-1]; } else { diagCost = numeric_limits::max(); } if (i > 0) { leftCost = costMatrix[i-1][j]; } else { leftCost = numeric_limits::max(); } if (j > 0) { downCost = costMatrix[i][j-1]; } else { downCost = numeric_limits::max(); } // Determine which direction to move in. Prefer moving diagonally and // moving towards the i==j axis of the matrix if there are ties. if ((diagCost<=leftCost) && (diagCost<=downCost)) { i--; j--; } else if ((leftCost diagCost { j--; } else // leftCost==rightCost > diagCost { i--; } minCostPath.addFirst(i, j); } //Let Return Value Optimization do its job. return TimeWarpInfo(minimumCost, minCostPath); } template ValueType getWarpDistBetween(TimeSeries const& tsI,TimeSeries const& tsJ,SearchWindow const& window, DistanceFunction const& distFn) { // COST MATRIX: // 5|_|_|_|_|_|_|E| E = min Global Cost // 4|_|_|_|_|_|_|_| S = Start point // 3|_|_|_|_|_|_|_| each cell = min global cost to get to that point // j 2|_|_|_|_|_|_|_| // 1|_|_|_|_|_|_|_| // 0|S|_|_|_|_|_|_| // 0 1 2 3 4 5 6 // i // access is M(i,j)... column-row PartialWindowMatrix costMatrix(window); JInt maxI = tsI.size()-1; JInt maxJ = tsI.size() -1; // Get an iterator that traverses the window cells in the order that the cost matrix is filled. // (first to last row (1..maxI), bottom to top (1..MaxJ) SearchWindowIterator matrixIterator = window.iterator(); while (matrixIterator.hasNext()) { ColMajorCell currentCell = matrixIterator.next(); JInt i = currentCell.getCol(); JInt j = currentCell.getRow(); if (i == 0 && j==0) { // bottom left cell (first row AND first column) costMatrix.put(i,j,distFn.calcDistance(*tsI.getMeasurementVector(0),*tsJ.getMeasurementVector(0))); } else if (i == 0) // first column { costMatrix.put(i, j, distFn.calcDistance(*tsI.getMeasurementVector(0), *tsJ.getMeasurementVector(j)) + costMatrix.get(i, j-1)); } else if (j == 0) // first row { costMatrix.put(i, j, distFn.calcDistance(*tsI.getMeasurementVector(i), *tsJ.getMeasurementVector(0)) + costMatrix.get(i-1, j)); } else // not first column or first row { ValueType minGlobalCost = fd_min(costMatrix.get(i-1, j), fd_min(costMatrix.get(i-1, j-1), costMatrix.get(i, j-1))); costMatrix.put(i, j, minGlobalCost + distFn.calcDistance(*tsI.getMeasurementVector(i), *tsJ.getMeasurementVector(j))); } } return costMatrix.get(maxI,maxJ); } template TimeWarpInfo getWarpInfoBetween(TimeSeries const& tsI, TimeSeries const& tsJ,SearchWindow const& window, DistanceFunction const& distFn) { // COST MATRIX: // 5|_|_|_|_|_|_|E| E = min Global Cost // 4|_|_|_|_|_|_|_| S = Start point // 3|_|_|_|_|_|_|_| each cell = min global cost to get to that point // j 2|_|_|_|_|_|_|_| // 1|_|_|_|_|_|_|_| // 0|S|_|_|_|_|_|_| // 0 1 2 3 4 5 6 // i // access is M(i,j)... column-row MemoryResidentMatrix costMatrix(&window); JInt maxI = tsI.size()-1; JInt maxJ = tsJ.size()-1; // Get an iterator that traverses the window cells in the order that the cost matrix is filled. // (first to last row (1..maxI), bottom to top (1..MaxJ) SearchWindowIterator matrixIterator = window.iterator(); while (matrixIterator.hasNext()) { ColMajorCell currentCell = matrixIterator.next(); // current cell being filled JInt i = currentCell.getCol(); JInt j = currentCell.getRow(); if ( (i==0) && (j==0) ) // bottom left cell (first row AND first column) costMatrix.put(i, j, distFn.calcDistance(*tsI.getMeasurementVector(0), *tsJ.getMeasurementVector(0))); else if (i == 0) // first column { costMatrix.put(i, j, distFn.calcDistance(*tsI.getMeasurementVector(0), *tsJ.getMeasurementVector(j)) + costMatrix.get(i, j-1)); } else if (j == 0) // first row { costMatrix.put(i, j, distFn.calcDistance(*tsI.getMeasurementVector(i), *tsJ.getMeasurementVector(0)) + costMatrix.get(i-1, j)); } else // not first column or first row { ValueType minGlobalCost = fd_min(costMatrix.get(i-1, j), fd_min(costMatrix.get(i-1, j-1), costMatrix.get(i, j-1))); costMatrix.put(i, j, minGlobalCost + distFn.calcDistance(*tsI.getMeasurementVector(i), *tsJ.getMeasurementVector(j))); } } // Minimum Cost is at (maxI, maxJ) ValueType minimumCost = costMatrix.get(maxI, maxJ); WarpPath minCostPath(maxI+maxJ-1); JInt i = maxI; JInt j = maxJ; minCostPath.addFirst(i, j); while ((i>0) || (j>0)) { // Find the costs of moving in all three possible directions (left, // down, and diagonal (down and left at the same time). ValueType diagCost; ValueType leftCost; ValueType downCost; if ((i>0) && (j>0)) diagCost = costMatrix.get(i-1, j-1); else diagCost = numeric_limits::max(); if (i > 0) leftCost = costMatrix.get(i-1, j); else leftCost = numeric_limits::max(); if (j > 0) downCost = costMatrix.get(i, j-1); else downCost = numeric_limits::max(); // Determine which direction to move in. Prefer moving diagonally and // moving towards the i==j axis of the matrix if there are ties. if ((diagCost<=leftCost) && (diagCost<=downCost)) { i--; j--; } else if ((leftCost diagCost j--; else // leftCost==rightCost > diagCost i--; // Add the current step to the warp path. minCostPath.addFirst(i, j); } return TimeWarpInfo(minimumCost, minCostPath); } } FD_NS_END #endif /* defined(__FastDTW_x__DTW__) */