All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.citec.tcs.alignment.AbstractStrictDTWAlgorithm Maven / Gradle / Ivy

/* 
 * TCS Alignment Toolbox
 * 
 * Copyright (C) 2013-2015
 * Benjamin Paaßen, Georg Zentgraf
 * AG Theoretical Computer Science
 * Centre of Excellence Cognitive Interaction Technology (CITEC)
 * University of Bielefeld
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */
package de.citec.tcs.alignment;

import de.citec.tcs.alignment.comparators.Comparator;
import de.citec.tcs.alignment.sequence.Node;
import de.citec.tcs.alignment.sequence.Sequence;
import de.citec.tcs.alignment.sequence.Value;
import java.util.ArrayList;

/**
 * This serves as an abstract super class for strict Dynamic Time Warping
 * implementations. For soft ones please refer to the
 * DynamicTimeWarpingAlgorithm class. Note that this algorithm calculates a
 * distance, while the soft DynamicTimeWarping algorithm calculates a
 * similarity.
 *
 * This implements the forward part of DTW, meaning the calculation of the
 * dynamic programming matrix. The backtracing has to be implemented by
 * subclasses (if it is required at all).
 *
 * @author Benjamin Paassen - [email protected]
 */
public abstract class AbstractStrictDTWAlgorithm implements AlignmentAlgorithm {

	private final Class resultClass;
	private final AlignmentSpecification specification;
	private double[][] lastDTWMatrix;
	private double weightThreshold = 0;

	public AbstractStrictDTWAlgorithm(Class resultClass, AlignmentSpecification specification) {
		this.resultClass = resultClass;
		this.specification = specification;
	}

	/**
	 * {@inheritDoc }
	 */
	@Override
	public Class getResultClass() {
		return resultClass;
	}

	/**
	 * {@inheritDoc }
	 */
	@Override
	public AlignmentSpecification getSpecification() {
		return specification;
	}

	/**
	 * This returns the dynamic programming matrix that was calculated
	 * in the last call of calculateAlignment.
	 *
	 * @return the dynamic programming matrix that was calculated
	 * in the last call of calculateAlignment.
	 */
	public double[][] getLastDTWMatrix() {
		return lastDTWMatrix;
	}

	/**
	 * Set a weight threshold (between 0 and 1) that determines which keywords
	 * should be ignored during calculation because their weight is negligible.
	 *
	 * The default value is 0.
	 *
	 * @param weightThreshold a weight threshold (between 0 and 1)
	 */
	public void setWeightThreshold(double weightThreshold) {
		if (weightThreshold < 0 || weightThreshold > 1) {
			throw new RuntimeException("A weight threshold has to be between 0 and 1!");
		}
		this.weightThreshold = weightThreshold;
	}

	/**
	 *
	 * @return The current weight threshold (0 per default).
	 */
	public double getWeightThreshold() {
		return weightThreshold;
	}

	/**
	 * {@inheritDoc }
	 */
	@Override
	public R calculateAlignment(Sequence a, Sequence b) {

		//check validity
		if (a.getNodes().isEmpty()) {
			throw new IllegalArgumentException("The first given sequence is empty!");
		}
		if (b.getNodes().isEmpty()) {
			throw new IllegalArgumentException("The second given sequence is emtpy!");
		}
		if (a.getNodeSpecification() != specification.getNodeSpecification()
				&& !a.getNodeSpecification().equals(specification.getNodeSpecification())) {
			throw new IllegalArgumentException(
					"The first input sequence has an unexpected node specification!");
		}
		if (a.getNodeSpecification() != b.getNodeSpecification()
				&& !a.getNodeSpecification().equals(b.getNodeSpecification())) {
			throw new IllegalArgumentException(
					"The node specifications of both input sequences to not match!");
		}

		//identify the subset of comparators that have an above threshold weighting.
		final ArrayList relevantIndices = new ArrayList();
		for (int k = 0; k < specification.size(); k++) {
			if (specification.getWeighting()[k] > weightThreshold) {
				relevantIndices.add(k);
			}
		}

		final Comparator[] comparators = new Comparator[relevantIndices.size()];
		final double[] weights = new double[relevantIndices.size()];
		final int[] originalIndices = new int[relevantIndices.size()];
		for (int k = 0; k < comparators.length; k++) {
			comparators[k] = specification.getComparator(relevantIndices.get(k));
			weights[k] = specification.getWeighting()[relevantIndices.get(k)];
			originalIndices[k] = specification.getOriginalIndex(relevantIndices.get(k));
		}

		final int m = a.getNodes().size();
		final int n = b.getNodes().size();

		double local = 0;
		Node aNode, bNode;

		final double[][] dtwMatrix = new double[m][n];
		/*
		 * initialize the alignment matrix. Note that I do not use the classic
		 * trick to initialize the first row and column with infinity. First
		 * this blows up the matrix by a linear summand, second it is an
		 * unnecessary workaround from my perspective, because you can exploit
		 * some nice properties of the second row and column (which I treat as
		 * the first one) to initialize it directly.
		 */
		{

			//initialize the first entry, which is just the comparison of the first
			//entries of both vectors. Note that the sequences are not allowed to be empty.
			final Value[] aValues = new Value[comparators.length];
			final Value[] bValues = new Value[comparators.length];
			aNode = a.getNodes().get(0);
			bNode = b.getNodes().get(0);
			for (int k = 0; k < comparators.length; k++) {
				aValues[k] = aNode.getValue(originalIndices[k]);
				bValues[k] = bNode.getValue(originalIndices[k]);
				local += weights[k] * comparators[k].compare(
						aValues[k], bValues[k]);
			}
			dtwMatrix[0][0] = local;

			//initialize first column. Here we can only elongate the first sequence
			//until the second one ends.
			for (int i = 1; i < m; i++) {
				local = 0;
				aNode = a.getNodes().get(i);
				for (int k = 0; k < comparators.length; k++) {
					local += weights[k] * comparators[k].compare(
							aNode.getValue(originalIndices[k]),
							bValues[k]);
				}
				dtwMatrix[i][0] = dtwMatrix[i - 1][0] + local;
			}
			//initialize the first row. Here we can only elongate the second sequence
			//until the first one ends.
			for (int j = 1; j < n; j++) {
				local = 0;
				bNode = b.getNodes().get(j);
				for (int k = 0; k < comparators.length; k++) {
					local += weights[k] * comparators[k].compare(
							aValues[k],
							bNode.getValue(originalIndices[k]));
				}
				dtwMatrix[0][j] = dtwMatrix[0][j - 1] + local;
			}
		}
		/*
		 * use the actual DTW algorithm
		 */
		//buffer the values of the first sequence.
		final Value[] aValues = new Value[comparators.length];
		for (int i = 1; i < m; i++) {
			aNode = a.getNodes().get(i);
			for (int k = 0; k < comparators.length; k++) {
				aValues[k] = aNode.getValue(originalIndices[k]);
			}
			for (int j = 1; j < n; j++) {
				//calculate the local cost.
				local = 0;
				bNode = b.getNodes().get(j);
				for (int k = 0; k < comparators.length; k++) {
					local += comparators[k].compare(
							aValues[k],
							bNode.getValue(originalIndices[k]));
				}
				//calculate the DTW matrix entry.
				dtwMatrix[i][j] = local + Math.min(
						dtwMatrix[i - 1][j - 1],
						Math.min(dtwMatrix[i - 1][j],
								dtwMatrix[i][j - 1])
				);
			}
		}
		lastDTWMatrix = dtwMatrix;
		return transformToResult(dtwMatrix, a, b);
	}

	/**
	 * This method has to be implemented by sub classes to transform
	 * a calculated dynamic programming matrix to a valid result of
	 * that implementation. This also has to implement the backtracing if
	 * necessary.
	 *
	 * @param dtwMatrix a dynamic programming matrix calculated with respect to
	 * both input sequences.
	 * @param a the first input sequence.
	 * @param b the second input sequence.
	 * @return a valid result for this algorithm implementation.
	 */
	public abstract R transformToResult(double[][] dtwMatrix,
			final Sequence a, final Sequence b);

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy