/*
* A simple DTW algorithm that computes the warped path with no constraints
*
*/
package weka.core.elastic_distance_measures;
import utilities.ClassifierTools;
import weka.core.DenseInstance;
import weka.core.EuclideanDistance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.neighboursearch.PerformanceStats;
/**
*
* @author Chris Rimmer
*/
public class BasicDTW extends EuclideanDistance{
protected double[][] distances;
protected boolean isEarlyAbandon=false; //This doesnt seem to work!
// private int distanceCount = 0;
/**
* BasicDTW Constructor
*
* Early Abandon Disabled
*/
public BasicDTW(){
super();
this.m_DontNormalize = true;
this.isEarlyAbandon = true;
}
/**
* BasicDTW Constructor that allows enabling of early abandon
*
* @param earlyAbandon boolean value setting if early abandon is enabled
*/
public BasicDTW(boolean earlyAbandon) {
super();
this.isEarlyAbandon = earlyAbandon;
this.m_DontNormalize = true;
}
public BasicDTW(Instances d) {
super(d);
this.m_DontNormalize = true;
}
/**
* Distance method
*
* @param first instance 1
* @param second instance 2
* @param cutOffValue used for early abandon
* @param stats
* @return distance between instances
*/
@Override
public double distance(Instance first, Instance second, double cutOffValue, PerformanceStats stats){
//Get the double arrays
return distance(first,second,cutOffValue);
}
@Override
public double distance(Instance first, Instance second) {
return distance(first, second, Double.POSITIVE_INFINITY);
}
/**
* Calculates the distance between two instances.
*
* @param first the first instance
* @param second the second instance
* @return the distance between the two given instances
*/
@Override
public double distance(Instance first, Instance second, double cutOffValue){
//remove class index from first instance if there is one
int firtClassIndex = first.classIndex();
double[] arr1;
if(firtClassIndex > 0){
arr1 = new double[first.numAttributes()-1];
for(int i = 0,j = 0; i < first.numAttributes(); i++){
if(i != firtClassIndex){
arr1[j]= first.value(i);
j++;
}
}
}else{
arr1 = first.toDoubleArray();
}
//remove class index from second instance if there is one
int secondClassIndex = second.classIndex();
double[] arr2;
if(secondClassIndex > 0){
arr2 = new double[second.numAttributes()-1];
for(int i = 0,j = 0; i < second.numAttributes(); i++){
if(i != secondClassIndex){
arr2[j]= second.value(i);
j++;
}
}
}else{
arr2 = second.toDoubleArray();
}
return distance(arr1,arr2,cutOffValue);
}
/**
* 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
*/
public double distance(double[] first, double[] second, double cutOffValue){
//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++){
this.distances[0][i] = this.distances[0][i-1]+((first[0]-second[i])*(first[0]-second[i]));
}
//first column
for(int i=1;i<first.length;i++){
this.distances[i][0] = this.distances[i-1][0]+((first[i]-second[0])*(first[i]-second[0]));
}
//warp rest
double minDistance;
for(int i = 1; i<first.length; i++){
boolean overflow = true;
for(int j = 1; j<second.length; j++){
//calculate distances
minDistance = Math.min(this.distances[i][j-1], Math.min(this.distances[i-1][j], this.distances[i-1][j-1]));
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;
}
}
//early abandon
if(overflow && this.isEarlyAbandon){
return Double.MAX_VALUE;
}
}
return Math.sqrt(this.distances[first.length-1][second.length-1]);
}
/**
* Generates a string of the minimum cost warp path
*
* Distances array must be populated through use of distance method
*
* @return Path
*/
public String printMinCostWarpPath(){
return findPath(this.distances.length-1, this.distances[0].length-1);
}
/**
* Recursive method that finds and prints the minimum warped path
*
* @param int i position in distances, should be max of series
* @param int j position in distances, should be max of series
*
* @return current position
*/
protected String findPath(int i, int j){
double prevDistance = this.distances[i][j];
int oldI = i;
int oldJ = j;
//final condition
if(i != 0 || j != 0){
//decrementing i and j
if(i > 0 && j > 0){
double min = Math.min(this.distances[i-1][j], Math.min(this.distances[i-1][j-1], this.distances[i][j-1]));
if(this.distances[i-1][j-1] == min){
i--;
j--;
}else if(this.distances[i-1][j] == min){
i--;
}else if(this.distances[i][j-1] == min){
j--;
}
}else if(j > 0){
j--;
}else if(i > 0){
i--;
}
//recursive step
return "("+oldI+","+oldJ+") = "+prevDistance+"\n" + findPath(i,j);
}else{
return "("+oldI+","+oldJ+") = "+prevDistance+"\n";
}
}
/**
* returns the Euclidean distances array
*
* @return double[][] distances
*/
public double[][] getDistanceArray(){
return this.distances;
}
/**
* This will print the diagonal route with no warping
*/
public void printDiagonalRoute(){
System.out.println("------------------ Diagonal Route ------------------");
for(int i = this.distances.length-1; i >= 0; i--){
System.out.print(this.distances[i][i]+" ");
}
System.out.println("\n------------------ End ------------------");
}
/**
* Prints the distances array as a table
*/
public void printDistances(){
System.out.println("------------------ Distances Table ------------------");
for(int i = 0; i<this.distances.length; i++){
System.out.print("Row ="+i+" = ");
for(int j = 0; j<this.distances[0].length; j++){
System.out.print(" "+ distances[i][j]);
}
System.out.print("\n");
}
System.out.println("------------------ End ------------------");
}
/**
* Check if early abandon enabled
*
* @return early abandon enabled
*/
public boolean isEarlyAbandon() {
return isEarlyAbandon;
}
/**
* Set early abandon
*
* @param isEarlyAbandon value for early abandon
*/
public void setIsEarlyAbandon(boolean isEarlyAbandon) {
this.isEarlyAbandon = isEarlyAbandon;
}
@Override
public String toString() {
return "BasicDTW{ " + "earlyAbandon=" + this.isEarlyAbandon + " }";
}
public static void main(String[] args){
//Test BasicDTW
Instances test = ClassifierTools.loadData("C:\\Users\\ajb\\Dropbox\\test\\Beef");
BasicDTW dtw=new BasicDTW(test);
EuclideanDistance ed=new EuclideanDistance(test);
ed.setDontNormalize(true);
System.out.println(" DATA \n"+test.toString());
System.out.println(" ED ="+ed.distance(test.instance(0),test.instance(1)));
System.out.println(" ED ="+ed.distance(test.instance(0),test.instance(1),2));
System.out.println(" DTW ="+dtw.distance(test.instance(0),test.instance(1)));
System.out.println(" DTW ="+dtw.distance(test.instance(0),test.instance(1),1));
//Test Early abandon
}
}