/* * Sakoe Chiba DTW distance metric */ package weka.core.elastic_distance_measures; import weka.core.Instances; /** * * @author Chris Rimmer */ public class SakoeChibaDTW extends BasicDTW { // private int bandSize; private double bandPercent; /** * Creates new Sakoe Chiba Distance metric * * @param bandPercent warping window width as a percentage * @throws IllegalArgumentException bandSize must be > 0 */ public SakoeChibaDTW(double bandPercent) throws IllegalArgumentException { super(); setup(bandPercent); } /** * Creates new Sakoe Chiba Distance metric * * @param bandPercent warping window width * @param earlyAbandon set early abandon * @throws IllegalArgumentException bandSize must be > 0 */ public SakoeChibaDTW(int bandPercent, boolean earlyAbandon) throws IllegalArgumentException { super(earlyAbandon); setup(bandPercent); } /** * sets up the distance metric * * @param bandSize * @throws IllegalArgumentException */ // private void setup(int bandSize) throws IllegalArgumentException { // if (bandSize < 1) { // throw new IllegalArgumentException("Band Size must be 1 or greater"); // } // // this.bandSize = bandSize; // } private void setup(double bandPercent) throws IllegalArgumentException { if (bandPercent <0 || bandPercent > 1) { throw new IllegalArgumentException("Band Size must be between 0 and 1"); } this.bandPercent = bandPercent; } public int calcaulteBandSize(int instanceLength){ if(this.bandPercent==0){ return 1; }else{ double width = instanceLength*this.bandPercent; return (int)Math.ceil(width); } } /** * 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) { int bandSize = this.calcaulteBandSize(first.length); //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 < second.length; i++) { if (i < bandSize) { 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++) { if (i < bandSize) { 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; } } //warp rest double minDistance; for (int i = 1; i < first.length; i++) { boolean overflow = true; for (int j = 1; j < second.length; j++) { //Checks if i and j are within the band window if (i < j + bandSize && j < i + bandSize) { 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 size of the warping window * * @param bandSize band width * @throws IllegalArgumentException */ public void setBandSize(int bandSize) throws IllegalArgumentException { setup(bandSize); } /** * Gets the current warping window width * * @return warping window width */ public double getBandPercentage() { return this.bandPercent; } @Override public String toString() { return "SakoeChibaDTW{ " + "bandSize=" + this.bandPercent + ", earlyAbandon=" + this.isEarlyAbandon + " }"; } }