code: package dotnet54.measures.multivariate;
  
  import dotnet54.measures.Measure;
  import dotnet54.tscore.data.Dataset;
  
  import java.util.Random;
  
  /**
   * Some classes in this package may contain borrowed code from the timeseriesweka project (Bagnall, 2017), 
   * we might have modified (fixes, and improvements for efficiency) the original classes.
   * 
   */
  
  public class WDTW extends MultivariateMeasure {
  
  	public static final String gParam = "g";
  	protected static final double WEIGHT_MAX = 1;
  	protected double g; // "empirical constant that controls the curvature (slope) of the function
  	protected transient double[] weightVector; // initialised on first distance call
  
  	public WDTW(Measure measureName, boolean dependentDimensions, int[] dimensionsToUse, double g) {
  		super(measureName, dependentDimensions, dimensionsToUse);
  		this.g = g;
  	}
  
  	public double distance(double[][] series1, double[][] series2, double bsf, double g){
  		this.setG(g, series1[0].length);
  		return distance(series1, series2, bsf);
  	}
  
  	//fast WDTW implemented by Geoff Webb
  	@Override
  	public double distanceIndep(double[][] series1, double[][] series2, int dimension, double bsf) {
  		double[] vector1 = series1[dimension];
  		double[] vector2 = series2[dimension];
  		double[] prevRow = new double[vector2.length];
  		double[] curRow = new double[vector2.length];
  		double second0 = vector2[0];
  		double diff;
  		double prevVal = 0.0;
  		this.setG(g, curRow.length);
  
  		
  		// put the series1 row into prevRow to save swapping before moving to the series2 row
  		
  		{	double first0 = vector1[0];
  		
  			// series1 value
  			diff = first0 - second0;
  			prevVal = prevRow[0] = this.weightVector[0] * diff * diff;
  	
  			// top row
  			for (int j = 1; j < vector2.length; j++) {
  				diff = first0 - vector2[j];
  				prevVal = prevRow[j] = prevVal + this.weightVector[j] * diff * diff;
  			}
  		}
  		
  		double minDistance;
  		double firsti = vector1[1];
  		
  		// series2 row is a special case because path can't go through prevRow[j]
  		diff = firsti - second0;
  		prevVal = curRow[0] = prevRow[0] + this.weightVector[1] * diff * diff;
  		
  		for (int j = 1; j < vector2.length; j++) {
  			// calculate distances
  			minDistance = Math.min(prevVal, prevRow[j - 1]);
  			diff = firsti - vector2[j];
  			prevVal = curRow[j] = minDistance + this.weightVector[j-1] * diff * diff;
  		}
  
  		// warp rest
  		for (int i = 2; i < vector1.length; i++) {
  			// make the old current row into the current previous row and set current row to use the old prev row
  			double [] tmp = curRow;
  			curRow = prevRow;
  			prevRow = tmp;
  			firsti = vector1[i];
  			
  			diff = firsti - second0;
  			prevVal = curRow[0] = prevRow[0] + this.weightVector[i] * diff * diff;
  
  			for (int j = 1; j < vector2.length; j++) {
  				// calculate distances
  				minDistance = min(prevVal, prevRow[j], prevRow[j - 1]);
  				diff = firsti - vector2[j];
  				prevVal = curRow[j] = minDistance + this.weightVector[Math.abs(i - j)] * diff * diff;
  			}
  
  		}
  
  		return prevVal;
  	}
  
  	@Override
  	public double distanceDep(double[][] series1, double[][] series2, double bsf){
  		int length1 = series1[dimensionsToUse[0]].length;
  		int length2 = series2[dimensionsToUse[0]].length;
  
  		double[] prevRow = new double[length2];
  		double[] curRow = new double[length2];
  		double prevVal;
  		double minDistance;
  
  		this.setG(g, length2);
  
  		// put the series1 row into prevRow to save swapping before moving to the series2 row
  		prevVal = prevRow[0] = this.weightVector[0] * squaredEuclideanDistanceFromPointToPoint(series1, 0, series2, 0);
  
  		// top row
  		for (int j = 1; j < length2; j++) {
  			prevVal = prevRow[j] = prevVal + this.weightVector[j]
  					* squaredEuclideanDistanceFromPointToPoint(series1, 0, series2, j);
  		}
  
  		// series2 row is a special case because path can't go through prevRow[j]
  		prevVal = curRow[0] = prevRow[0] + this.weightVector[1]
  				* squaredEuclideanDistanceFromPointToPoint(series1, 1, series2, 0);
  
  		for (int j = 1; j < length2; j++) {
  			// calculate distances
  			minDistance = Math.min(prevVal, prevRow[j - 1]);
  			prevVal = curRow[j] = minDistance + this.weightVector[j-1]
  					* squaredEuclideanDistanceFromPointToPoint(series1, 1, series2, j);
  		}
  
  		// warp rest
  		for (int i = 2; i < length1; i++) {
  			// make the old current row into the current previous row and set current row to use the old prev row
  			double [] tmp = curRow;
  			curRow = prevRow;
  			prevRow = tmp;
  
  			prevVal = curRow[0] = prevRow[0] + this.weightVector[i]
  					* squaredEuclideanDistanceFromPointToPoint(series1, i, series2, 0);
  
  			for (int j = 1; j < length2; j++) {
  				// calculate distances
  				minDistance = min(prevVal, prevRow[j], prevRow[j - 1]);
  				prevVal = curRow[j] = minDistance + this.weightVector[Math.abs(i - j)]
  						* squaredEuclideanDistanceFromPointToPoint(series1, i, series2, j);
  			}
  
  		}
  
  //		double res = prevVal;
  		return prevVal;
  	}
  
  	private void initWeights(int seriesLength) {
  		this.weightVector = new double[seriesLength];
  		double halfLength = (double) seriesLength / 2;
  
  		for (int i = 0; i < seriesLength; i++) {
  			weightVector[i] = WEIGHT_MAX / (1 + Math.exp(-g * (i - halfLength)));
  		}
  	}
  
  	@Override
  	public void setRandomParams(Dataset trainData, Random rand) {
  		setG(getRandomG(trainData, rand), trainData.length());
  	}
  
  	@Override
  	public void initParamsByID(Dataset trainData, Random rand) {
  		initWeights(trainData.length());
  	}
  
  	@Override
  	public void setParamsByID(int paramID, int seriesLength, Random rand) {
  		setG(paramID / 100.0, seriesLength);
  	}
  
  	@Override
  	public void setRandomParamByID(Dataset trainData, Random rand) {
  		int paramID = rand.nextInt(100 );
  		setParamsByID(paramID, trainData.length(), rand);
  	}
  
  	public static double getRandomG(Dataset dataset, Random rand) {
  		return rand.nextDouble();
  	}
  
  	public void setG(double g, int seriesLength){
  		if(this.g!=g || this.weightVector==null){
  			this.g = g;
  			this.initWeights(seriesLength);
  		}
  	}
  
  	@Override
  	public String toString(){
  		return "WDTW[,g="+g+","
  				+super.toString()+",]";
  	}
  
  //	public synchronized double distance_dep(double[][] series1, double[][] series2, double bsf, double g, int[] dimensionsToUse) {
  //		int length1 = series1[0].length;
  //		int length2 = series2[0].length;
  //		this.setG(g, Math.max(length1, length2));
  //
  //		double[] prevRow = new double[length2];
  //		double[] currRow = new double[length2];
  //		double prevVal;
  //
  //		//value at matrix(0,0) -- top left value
  //		prevVal = prevRow[0] = this.weightVector[0] * squaredDistanceVector(series1, series2,
  //				0, 0, dimensionsToUse);
  //
  //		// values at matrix(0, j) -- top row
  //		for (int j = 1; j < series2.length; j++) {
  //			prevVal = prevRow[j] = prevVal + this.weightVector[j] * squaredDistanceVector(series1, series2,
  //					0, j, dimensionsToUse);
  //		}
  //
  //
  //		double minDistance;
  //		double series1i = series1[1];
  //
  //		thisDiff = firsti - second0;
  //		prevVal = curRow[0] = prevRow[0] + this.weightVector[1] + squaredDistanceVector(series1, series2,
  //				0, j, dimensionsToUse);
  //
  //		for (int j = 1; j < series2.length; j++) {
  //			// calculate distances
  //			minDistance = Math.min(prevVal, prevRow[j - 1]);
  //			prevVal = currRow[j] = minDistance + this.weightVector[j-1] * squaredDistanceVector(series1, series2,
  //					0, j, dimensionsToUse);
  //		}
  //
  //		// warp rest
  //		for (int i = 2; i < series1.length; i++) {
  //			// make the old current row into the current previous row and set current row to use the old prev row
  //			double [] tmp = currRow;
  //			currRow = prevRow;
  //			prevRow = tmp;
  //			series1i = series1[i];
  //
  //			thisDiff = series1i - second0;
  //			prevVal = currRow[0] = prevRow[0] + this.weightVector[i] * thisDiff * thisDiff;
  //
  //			for (int j = 1; j < series2.length; j++) {
  //				// calculate distances
  //				minDistance = min(prevVal, prevRow[j], prevRow[j - 1]);
  //				thisDiff = series1i - series2[j];
  //				prevVal = currRow[j] = minDistance + this.weightVector[Math.abs(i - j)] * thisDiff * thisDiff;
  //			}
  //
  //		}
  //
  //		double res = prevVal;
  //		return res;
  //	}
  
  
  
  //	public synchronized double distance(double[] first, double[] second, double bsf, double g) {
  //		this.setG(g, first.length);
  //
  //		double[][] distances = MemorySpaceProvider.getInstance(first.length).getDoubleMatrix();
  ////		double[][] distances = MemoryManager.getInstance().getDoubleMatrix(0);
  //
  //		// create empty array
  //		if (distances == null || distances.length < first.length || distances[0].length < second.length) {
  //			distances = new double[first.length][second.length];
  //		}
  //
  //		// first value
  //		distances[0][0] = this.weightVector[0] * (first[0] - second[0]) * (first[0] - second[0]);
  //
  //		// top row
  //		for (int i = 1; i < second.length; i++) {
  //			distances[0][i] = distances[0][i - 1] + this.weightVector[i] * (first[0] - second[i]) * (first[0] - second[i]); // edited
  //																	// by
  //																	// Jay
  //		}
  //
  //		// first column
  //		for (int i = 1; i < first.length; i++) {
  //			distances[i][0] = distances[i - 1][0] + this.weightVector[i] * (first[i] - second[0]) * (first[i] - second[0]); // edited
  //																	// by
  //																	// Jay
  //		}
  //
  //		// warp rest
  //		double minDistance;
  //		for (int i = 1; i < first.length; i++) {
  //
  //			for (int j = 1; j < second.length; j++) {
  //				// calculate distances
  //				minDistance = DistanceTools.Min3(distances[i][j - 1], distances[i - 1][j], distances[i - 1][j - 1]);
  //				distances[i][j] = minDistance + this.weightVector[Math.abs(i - j)] * (first[i] - second[j]) * (first[i] - second[j]); // edited
  //																		      // by
  //																		      // Jay
  //
  //				//
  //				// if(minDistance > cutOffValue &&
  //				// this.isEarlyAbandon){
  //				// this.distances[i][j] = Double.MAX_VALUE;
  //				// }else{
  //				// this.distances[i][j] =
  //				// minDistance+this.weightVector[Math.abs(i-j)]
  //				// *(first[i]-second[j])*(first[i]-second[j]);
  //				// //edited by Jay
  //				// overflow = false;
  //				// }
  //			}
  //
  //		}
  //
  //		double res = distances[first.length - 1][second.length - 1];
  //		MemorySpaceProvider.getInstance().returnDoubleMatrix(distances);
  //		return res;
  //	}
  
  }
  