/* * Itakura Parallelogram DTW distance metric */ package weka.core.elastic_distance_measures; /** * * @author Chris Rimmer */ public class ItakuraParallelogramDTW extends BasicDTW { private int maxWidth; private double AB_A; private double AB_B; private double AB_C; private double AC_A; private double AC_B; private double AC_C; private double BD_A; private double BD_B; private double BD_C; private double CD_A; private double CD_B; private double CD_C; private boolean calcLines; private double halfWidth; /** * Create Itakura Parallelogram distance metric * * @param maxWidth maximum width of the warping window * @throws IllegalArgumentException */ public ItakuraParallelogramDTW(int maxWidth) throws IllegalArgumentException{ super(); setup(maxWidth); } /** * Create Itakura Parallelogram distance metric * * @param maxWidth maximum width of the warping window * @param earlyAbandon enable early abandon * @throws IllegalArgumentException */ public ItakuraParallelogramDTW(int maxWidth, boolean earlyAbandon) throws IllegalArgumentException{ super(earlyAbandon); setup(maxWidth); } /** * setup the distance metric * * @param maxWidth maximum width of the warping window * @throws IllegalArgumentException */ private void setup(int maxWidth) throws IllegalArgumentException{ if(maxWidth < 1){ throw new IllegalArgumentException("Max Width must be 1 or greater"); } this.maxWidth = maxWidth; this.calcLines = true; } /** * calculates the distance between two instances (been converted to arrays) * * @param first instance 1 as array * @param second instance 2 as array * @param cutOffValue used for early abandon * @return distance between instances */ @Override public double distance(double[] first,double[] second, double cutOffValue){ //calculate if(this.calcLines){ calculateSlopes(first); this.calcLines = false; } //double halfwidth = Math.floor(this.maxWidth/2); //create empty array this.distances = new double[first.length][second.length]; //first value this.distances[0][0] = (first[0]-second[0])*(first[0]-second[0]); //early abandon if first values is larger than cut off if(this.distances[0][0] > cutOffValue && this.isEarlyAbandon){ return Double.MAX_VALUE; } //top row for(int i = 1; i<first.length; i++){ double AC_D = AC_A*(i+1)+AC_B+AC_C; if(AC_D > 0){ this.distances[0][i] = this.distances[0][i-1]+((first[0]-second[i])*(first[0]-second[i])); }else{ this.distances[0][i] = Double.MAX_VALUE; } } //first column for(int i = 1; i<first.length; i++){ double AB_D = AB_A+AB_B*(i+1)+AB_C; if(AB_D < 0){ this.distances[i][0] = this.distances[i-1][0]+((first[i]-second[0])*(first[i]-second[0])); }else{ this.distances[i][0] = Double.MAX_VALUE; } } double minDistance; for(int i = 1; i<first.length; i++){ boolean overflow = true; for(int j = 1; j<second.length; j++){ double AB_D = AB_A*(j+1)+AB_B*(i+1)+AB_C; double AC_D = AC_A*(j+1)+AC_B*(i+1)+AC_C; double BD_D = BD_A*(j+1)+BD_B*(i+1)+BD_C; double CD_D = CD_A*(j+1)+CD_B*(i+1)+CD_C; double firstHalf = (first.length/2)+this.halfWidth; double secondHalf = (first.length/2)-this.halfWidth; boolean evenlyDivisible = this.maxWidth%2 == 0; if((i < firstHalf && j < firstHalf && ((!evenlyDivisible && AB_D <= 0 && AC_D >= 0) || (evenlyDivisible && AB_D < 0 && AC_D > 0))) || (i > secondHalf && j > secondHalf && ((!evenlyDivisible && BD_D <= 0 && CD_D >= 0) || (evenlyDivisible && BD_D < 0 && CD_D > 0))) || (i == first.length-1 && j == first.length-1)){ minDistance = Math.min(this.distances[i][j-1], Math.min(this.distances[i-1][j], this.distances[i-1][j-1])); //Assign distance if(minDistance > cutOffValue && this.isEarlyAbandon){ this.distances[i][j] = Double.MAX_VALUE; }else{ this.distances[i][j] = minDistance+((first[i]-second[j])*(first[i]-second[j])); overflow = false; } }else{ this.distances[i][j] = Double.MAX_VALUE; } } //early abandon if(overflow && this.isEarlyAbandon){ return Double.MAX_VALUE; } } return this.distances[first.length-1][second.length-1]; } /** * Sets the maximum width of the warping window * * @param maxWidth width of warping window * @throws IllegalArgumentException */ public void setMaxWidth(int maxWidth) throws IllegalArgumentException { setup(maxWidth); } /** * Gets the current size of the warping window * * @return maximum size of warping window */ public int getMaxWidth() { return maxWidth; } /** * pre processing to calculate lines of parallelogram * * @param first array of data */ private void calculateSlopes(double[] first){ double middle = first.length/2; this.halfWidth = Math.floor(this.maxWidth/2); double Ax = 1; double Ay = 1; double Bx = middle-this.halfWidth; double By = middle+this.halfWidth; double Cx = middle+this.halfWidth; double Cy = middle-this.halfWidth; double Dx = first.length; double Dy = first.length; this.AB_A = -(By-Ay); this.AB_B = Bx-Ax; this.AB_C = -(AB_A*Ax+AB_B*Ay); this.AC_A = -(Cy-Ay); this.AC_B = Cx-Ax; this.AC_C = -(AC_A*Ax+AC_B*Ay); this.BD_A = -(Dy-By); this.BD_B = Dx-Bx; this.BD_C = -(BD_A*Bx+BD_B*By); this.CD_A = -(Dy-Cy); this.CD_B = Dx-Cx; this.CD_C = -(CD_A*Cx+CD_B*Cy); } @Override public String toString() { return "ItakuraParallelogramDTW{ " + "maxWidth=" + this.maxWidth + ", earlyAbandon=" + this.isEarlyAbandon + " }"; } }