Java tutorial
/* * Copyright 2015 Octavian Hasna * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ro.hasna.ts.math.representation; import org.apache.commons.math3.exception.NumberIsTooSmallException; import org.apache.commons.math3.util.FastMath; import org.apache.commons.math3.util.Precision; import ro.hasna.ts.math.exception.ArrayLengthIsTooSmallException; import ro.hasna.ts.math.type.MeanLastPair; import ro.hasna.ts.math.util.TimeSeriesPrecision; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; /** * Implements the Adaptive Piecewise Constant Approximation (APCA) algorithm. * <p> * Reference: * Chakrabarti K., Keogh E., Mehrotra S., Pazzani M. (2002) * <i>Locally Adaptive Dimensionality Reduction for Indexing Large Time Series Databases</i> * </p> * * @since 1.0 */ public class AdaptivePiecewiseConstantApproximation implements GenericTransformer<double[], MeanLastPair[]> { private static final long serialVersionUID = 5071554004881637993L; private final int segments; private final boolean approximateError; /** * Creates a new instance of this class with approximation enabled. * * @param segments the number of segments * @throws NumberIsTooSmallException if segments lower than 1 */ public AdaptivePiecewiseConstantApproximation(int segments) { this(segments, true); } /** * Creates a new instance of this class with a flag for using approximation. * * @param segments the number of segments * @param approximateError compute the error of unified segments using approximation * @throws NumberIsTooSmallException if segments lower than 1 */ public AdaptivePiecewiseConstantApproximation(int segments, boolean approximateError) { if (segments < 1) { throw new NumberIsTooSmallException(segments, 1, true); } this.segments = segments; this.approximateError = approximateError; } @Override public MeanLastPair[] transform(double[] values) { int length = values.length; if (length < 2 * segments) { throw new ArrayLengthIsTooSmallException(length, 2 * segments, true); } int numberOfSegments = length / 2; // create segments with two values Segment first = createSegments(values, length); if (numberOfSegments > segments) { // compute error by unifying current segment with the next segment TreeSet<Segment> set = createSegmentsSet(values, first); // unify concurrent segments with minimum error while (numberOfSegments > segments) { Segment minSegment = set.pollFirst(); minSegment.mean = getUnifiedMean(minSegment, minSegment.next); minSegment.error = getUnifiedError(minSegment, minSegment.next, values, minSegment.mean); minSegment.end = minSegment.next.end; deleteSubsequentSegment(minSegment, set); if (minSegment.next != null) { double mean = getUnifiedMean(minSegment, minSegment.next); minSegment.errorWithNext = getUnifiedError(minSegment, minSegment.next, values, mean); set.add(minSegment); } if (minSegment.prev != null) { set.remove(minSegment.prev); double mean = getUnifiedMean(minSegment.prev, minSegment); minSegment.prev.errorWithNext = getUnifiedError(minSegment.prev, minSegment, values, mean); set.add(minSegment.prev); } numberOfSegments--; } } return getMeanLastPairs(first, numberOfSegments); } private Segment createSegments(double[] values, int length) { Segment first = null, last = null; for (int i = 0; i < length - 1; i += 2) { double mean = (values[i] + values[i + 1]) / 2; Segment segment = new Segment(i, i + 2, mean, 2 * FastMath.abs(values[i] - mean)); if (first == null) { first = segment; last = first; } else { last.next = segment; segment.prev = last; last = last.next; } } return first; } private TreeSet<Segment> createSegmentsSet(double[] values, Segment first) { TreeSet<Segment> map = new TreeSet<>(new Comparator<Segment>() { @Override public int compare(Segment s1, Segment s2) { return Precision.compareTo(s1.errorWithNext, s2.errorWithNext, TimeSeriesPrecision.EPSILON); } }); Segment current = first; while (current.next != null) { double mean = getUnifiedMean(current, current.next); current.errorWithNext = getUnifiedError(current, current.next, values, mean); map.add(current); current = current.next; } return map; } private void deleteSubsequentSegment(Segment segment, Set<Segment> set) { Segment toBeDeleted = segment.next; segment.next = toBeDeleted.next; if (toBeDeleted.next != null) { toBeDeleted.next.prev = segment; } set.remove(toBeDeleted); } private MeanLastPair[] getMeanLastPairs(Segment segments, int numberOfSegments) { MeanLastPair[] result = new MeanLastPair[numberOfSegments]; int i = 0; while (segments != null) { result[i] = new MeanLastPair(segments.mean, segments.end); segments = segments.next; i++; } return result; } private double getUnifiedApproximatedError(Segment first, Segment second, double mean) { return first.error + second.error + 2 * FastMath.abs(first.mean - mean) * (first.end - first.start); } private double getUnifiedError(Segment first, Segment second, double[] values, double mean) { if (Precision.equals(mean, first.mean, TimeSeriesPrecision.EPSILON)) { return first.error + second.error; } if (approximateError) { return getUnifiedApproximatedError(first, second, mean); } double error = 0.0; for (int i = first.start; i < second.end; i++) { error += FastMath.abs(values[i] - mean); } return error; } private double getUnifiedMean(Segment first, Segment second) { return (first.mean * (first.end - first.start) + second.mean * (second.end - second.start)) / (second.end - first.start); } private static class Segment { int start; //inclusive int end; //exclusive double mean; double error; double errorWithNext; Segment next; Segment prev; Segment(int start, int end, double mean, double error) { this.start = start; this.end = end; this.mean = mean; this.error = error; this.errorWithNext = Double.POSITIVE_INFINITY; } } }