/* * RapidMiner * * Copyright (C) 2001-2008 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * 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 http://www.gnu.org/licenses/. */ package com.rapidminer.tools.math; import java.util.LinkedList; import java.util.List; /** * <p> * Generates the amplitude and index point of the highest peaks in the series. * Find peaks by a divide and conquer algorithm. In each series it tries to find * the maximum and then find other peaks left and right from it. Makes sure that * values of the current peak are excluded. * </p> * * @author Ingo Mierswa * @version $Id: BinaryPeakFinder.java,v 1.3 2008/05/09 19:23:02 ingomierswa Exp $ */ public class BinaryPeakFinder implements PeakFinder { /** An area of the series which still should be investigated. */ private static class Area { private int start; private int end; private Area(int start, int end) { this.start = start; this.end = end; } public String toString() { return "start: " + start + ", end: " + end; } } /** * Defines the sloppyness of the algorithm, i.e. how many wrong values are * possible until detecting a new peak. */ // private int sloppyValues; /** * Defines how big the variance of a value can be until he counts as wrong * value. */ // private double toleranceOfVariance; /** The min width of a peak. */ // private int minWidth = 5; /** The average of the current series. */ private double average; public BinaryPeakFinder() {} // int sloppyValues, int minWidth, double toleranceOfVariance) { // this.sloppyValues = sloppyValues; // this.toleranceOfVariance = toleranceOfVariance; // this.minWidth = minWidth; // } public List<Peak> getPeaks(Peak[] series) { // calculate average this.average = 0.0; for (int i = 0; i < series.length; i++) { this.average += series[i].getMagnitude(); } this.average /= series.length; LinkedList<Area> areaStack = new LinkedList<Area>(); Area startArea = new Area(0, series.length); areaStack.add(startArea); List<Peak> result = new LinkedList<Peak>(); while (areaStack.size() != 0) { Area current = areaStack.removeLast(); // adds the maximum value of the current area to the peaks // collection int maxIndex = findMaximum(series, current.start, current.end); // adds up to two new areas to the area stack int newRightEnd = getLeftEndOfPeak(series, maxIndex, current.start); if (newRightEnd > current.start) areaStack.add(new Area(current.start, newRightEnd)); int newLeftEnd = getRightEndOfPeak(series, maxIndex, current.end); if (newLeftEnd < current.end) areaStack.add(new Area(newLeftEnd, current.end)); // if ((newLeftEnd - newRightEnd) > minWidth) { // Peak peak = new Peak(maxIndex, // series[maxIndex].getMagnitude(series.length)); result.add(series[maxIndex]); // } } return result; } /** * Traverses the series from max to left until startIndex is reached or * while the current value is below average or while the values are still * decreasing. */ private int getLeftEndOfPeak(Peak[] series, int max, int startIndex) { int left = max; long sloppyCount = 2 + Math.round(Math.sqrt((double) max / (double) series.length) * 5.0d); double lastValue; boolean ok = false; do { ok = false; lastValue = series[left].getMagnitude(); for (int i = 0; i < sloppyCount + 1; i++) { left--; if (left <= startIndex) { break; } else { double tolerance = Math.sqrt((double) left / (double) series.length) + 1.2; if ((series[left].getMagnitude() < 2 * average) || (series[left].getMagnitude() < tolerance * lastValue)) { ok = true; break; } } } } while (ok); if (left <= startIndex) return startIndex; else return left; } /** * Traverses the series from max to right until endIndex is reached or while * the current value is below average or while the values are still * decreasing. */ private int getRightEndOfPeak(Peak[] series, int max, int endIndex) { int right = max; long sloppyCount = 2 + Math.round(Math.sqrt((double) max / (double) series.length) * 5.0d); double lastValue; boolean ok = false; do { ok = false; lastValue = series[right].getMagnitude(); for (int i = 0; i < sloppyCount + 1; i++) { right++; if (right >= endIndex) { break; } else { double tolerance = Math.sqrt((double) right / (double) series.length) + 1.2; if ((series[right].getMagnitude() < 2 * average) || (series[right].getMagnitude() < tolerance * lastValue)) { ok = true; break; } } } } while (ok); if (right >= endIndex) return endIndex; else return right; } /** * Finds the index point of the maximum of the series between startIndex and * endIndex. */ private int findMaximum(Peak[] series, int startIndex, int endIndex) { int maxIndex = startIndex; double maxValue = Double.NEGATIVE_INFINITY; for (int i = startIndex; i < endIndex; i++) { if (series[i].getMagnitude() > maxValue) { maxValue = series[i].getMagnitude(); maxIndex = i; } } return maxIndex; } }