package de.lmu.ifi.dbs.elki.distance.distancefunction.timeseries; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2014 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team 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 . */ import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.data.type.TypeUtil; import de.lmu.ifi.dbs.elki.data.type.VectorTypeInformation; import de.lmu.ifi.dbs.elki.distance.distancefunction.AbstractNumberVectorDistanceFunction; import de.lmu.ifi.dbs.elki.utilities.documentation.Reference; import de.lmu.ifi.dbs.elki.utilities.documentation.Title; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.CommonConstraints; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.DoubleParameter; /** * Longest Common Subsequence distance for numerical vectors. * * Adapted for Java, based on Matlab Code by Michalis Vlachos. Original * Copyright Notice: * * BEGIN COPYRIGHT NOTICE * * lcsMatching code -- (c) 2002 Michalis Vlachos * (http://www.cs.ucr.edu/~mvlachos) * * This code is provided as is, with no guarantees except that bugs are almost * surely present. Published reports of research using this code (or a modified * version) should cite the article that describes the algorithm: * *

* M. Vlachos, M. Hadjieleftheriou, D. Gunopulos, E. Keogh:
* Indexing Multi-Dimensional Time-Series with Support for Multiple Distance * Measures
* In Proc. of 9th SIGKDD, Washington, DC, 2003 *

* * Comments and bug reports are welcome. Email to mvlachos@cs.ucr.edu I would * also appreciate hearing about how you used this code, improvements that you * have made to it. * * You are free to modify, extend or distribute this code, as long as this * copyright notice is included whole and unchanged. * * END COPYRIGHT NOTICE * * * @author Thomas Bernecker */ @Title("Longest Common Subsequence distance function") @Reference(authors = "M. Vlachos, M. Hadjieleftheriou, D. Gunopulos, E. Keogh", // title = "Indexing Multi-Dimensional Time-Series with Support for Multiple Distance Measures", // booktitle = "Proceedings of the ninth ACM SIGKDD international conference on Knowledge discovery and data mining", // url = "http://dx.doi.org/10.1145/956750.956777") public class LCSSDistanceFunction extends AbstractNumberVectorDistanceFunction { /** * Keeps the currently set pDelta. */ private double pDelta; /** * Keeps the currently set pEpsilon. */ private double pEpsilon; /** * Constructor. * * @param pDelta pDelta * @param pEpsilon pEpsilon */ public LCSSDistanceFunction(double pDelta, double pEpsilon) { super(); this.pDelta = pDelta; this.pEpsilon = pEpsilon; } @Override public double distance(NumberVector v1, NumberVector v2) { final int dim1 = v1.getDimensionality(), dim2 = v2.getDimensionality(); if(dim1 > dim2) { return distance(v2, v1); } final int delta = (int) Math.ceil(dim2 * pDelta); // Compute value range, for scaling epsilon: final double epsilon = getRange(v1, dim1, v2, dim2) * pEpsilon; double[] curr = new double[dim2 + 1]; double[] next = new double[dim2 + 1]; for(int i = 0; i < dim1; i++) { final double ai = v1.doubleValue(i); for(int j = Math.max(0, i - delta); j <= Math.min(dim2 - 1, i + delta); j++) { final double bj = v2.doubleValue(j); if((bj + epsilon) >= ai && (bj - epsilon) <= ai) { // match next[j + 1] = curr[j] + 1; } else if(curr[j + 1] > next[j]) { // ins next[j + 1] = curr[j + 1]; } else { // del next[j + 1] = next[j]; } } // Swap double[] tmp = curr; curr = next; next = tmp; } // search for maximum in the last line double maxEntry = curr[1]; for(int i = 2; i < dim2 + 1; i++) { maxEntry = (curr[i] > maxEntry) ? curr[i] : maxEntry; } final double sim = maxEntry / Math.min(dim1, dim2); return 1. - sim; } public double getRange(NumberVector v1, final int dim1, NumberVector v2, final int dim2) { double min = v1.doubleValue(0), max = min; for(int i = 1; i < dim1; i++) { final double v = v1.doubleValue(i); min = (v < min) ? v : min; max = (v > max) ? v : max; } for(int i = 0; i < dim2; i++) { final double v = v2.doubleValue(i); min = (v < min) ? v : min; max = (v > max) ? v : max; } final double range = max - min; return range; } @Override public VectorTypeInformation getInputTypeRestriction() { return TypeUtil.NUMBER_VECTOR_VARIABLE_LENGTH; } @Override public boolean equals(Object obj) { if(obj == this) { return true; } if(obj == null) { return false; } if(!this.getClass().equals(obj.getClass())) { return false; } return (this.pDelta == ((LCSSDistanceFunction) obj).pDelta) && (this.pEpsilon == ((LCSSDistanceFunction) obj).pEpsilon); } /** * Parameterization class. * * @author Erich Schubert * * @apiviz.exclude */ public static class Parameterizer extends AbstractParameterizer { /** * PDELTA parameter */ public static final OptionID PDELTA_ID = new OptionID("lcss.pDelta", "the allowed deviation in x direction for LCSS alignment (positive double value, 0 <= pDelta <= 1)"); /** * PEPSILON parameter */ public static final OptionID PEPSILON_ID = new OptionID("lcss.pEpsilon", "the allowed deviation in y direction for LCSS alignment (positive double value, 0 <= pEpsilon <= 1)"); /** * Keeps the currently set pDelta. */ private double pDelta; /** * Keeps the currently set pEpsilon. */ private double pEpsilon; @Override protected void makeOptions(Parameterization config) { super.makeOptions(config); final DoubleParameter pDeltaP = new DoubleParameter(PDELTA_ID, 0.1)// .addConstraint(CommonConstraints.GREATER_EQUAL_ZERO_DOUBLE)// .addConstraint(CommonConstraints.LESS_EQUAL_ONE_DOUBLE); if(config.grab(pDeltaP)) { pDelta = pDeltaP.doubleValue(); } final DoubleParameter pEpsilonP = new DoubleParameter(PEPSILON_ID, 0.05)// .addConstraint(CommonConstraints.GREATER_EQUAL_ZERO_DOUBLE) // .addConstraint(CommonConstraints.LESS_EQUAL_ONE_DOUBLE); if(config.grab(pEpsilonP)) { pEpsilon = pEpsilonP.doubleValue(); } } @Override protected LCSSDistanceFunction makeInstance() { return new LCSSDistanceFunction(pDelta, pEpsilon); } } }