PSquarePercentile.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* https://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.
*/
/*
* This is not the original file distributed by the Apache Software Foundation
* It has been modified by the Hipparchus project
*/
package org.hipparchus.stat.descriptive.rank;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.hipparchus.analysis.UnivariateFunction;
import org.hipparchus.analysis.interpolation.LinearInterpolator;
import org.hipparchus.analysis.interpolation.NevilleInterpolator;
import org.hipparchus.analysis.interpolation.UnivariateInterpolator;
import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.exception.MathIllegalArgumentException;
import org.hipparchus.stat.descriptive.AbstractStorelessUnivariateStatistic;
import org.hipparchus.stat.descriptive.StorelessUnivariateStatistic;
import org.hipparchus.util.MathArrays;
import org.hipparchus.util.MathUtils;
import org.hipparchus.util.Precision;
/**
* A {@link StorelessUnivariateStatistic} estimating percentiles using the
* <a href=http://www.cs.wustl.edu/~jain/papers/ftp/psqr.pdf>P<SUP>2</SUP></a>
* Algorithm as explained by <a href=http://www.cse.wustl.edu/~jain/>Raj
* Jain</a> and Imrich Chlamtac in
* <a href=http://www.cse.wustl.edu/~jain/papers/psqr.htm>P<SUP>2</SUP> Algorithm
* for Dynamic Calculation of Quantiles and Histogram Without Storing
* Observations</a>.
* <p>
* Note: This implementation is not synchronized and produces an approximate
* result. For small samples, where data can be stored and processed in memory,
* {@link Percentile} should be used.
*/
public class PSquarePercentile extends AbstractStorelessUnivariateStatistic
implements StorelessUnivariateStatistic, Serializable {
/** The maximum array size used for psquare algorithm */
private static final int PSQUARE_CONSTANT = 5;
/**
* A Default quantile needed in case if user prefers to use default no
* argument constructor.
*/
private static final double DEFAULT_QUANTILE_DESIRED = 50d;
/** Serial ID */
private static final long serialVersionUID = 20150412L;
/** A decimal formatter for print convenience */
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("00.00");
/**
* Initial list of 5 numbers corresponding to 5 markers. <b>NOTE:</b>watch
* out for the add methods that are overloaded
*/
private final List<Double> initialFive = new FixedCapacityList<>(PSQUARE_CONSTANT);
/**
* The quantile needed should be in range of 0-1. The constructor
* {@link #PSquarePercentile(double)} ensures that passed in percentile is
* divided by 100.
*/
private final double quantile;
/**
* lastObservation is the last observation value/input sample. No need to
* serialize.
*/
private transient double lastObservation;
/**
* Markers is the marker collection object which comes to effect
* only after 5 values are inserted
*/
private PSquareMarkers markers;
/**
* Computed p value (i,e percentile value of data set hither to received)
*/
private double pValue = Double.NaN;
/**
* Counter to count the values/observations accepted into this data set
*/
private long countOfObservations;
/**
* Constructs a PSquarePercentile with the specific percentile value.
* @param p the percentile
* @throws MathIllegalArgumentException if p is not greater than 0 and less
* than or equal to 100
*/
public PSquarePercentile(final double p) {
if (p > 100 || p < 0) {
throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE,
p, 0, 100);
}
this.quantile = p / 100d;// always set it within (0,1]
}
/**
* Default constructor that assumes a {@link #DEFAULT_QUANTILE_DESIRED
* default quantile} needed.
*/
PSquarePercentile() {
this(DEFAULT_QUANTILE_DESIRED);
}
/**
* Copy constructor, creates a new {@code PSquarePercentile} identical
* to the {@code original}.
*
* @param original the {@code PSquarePercentile} instance to copy
* @throws org.hipparchus.exception.NullArgumentException if original is null
*/
public PSquarePercentile(PSquarePercentile original) {
super();
this.quantile = original.quantile;
if (original.markers != null) {
this.markers = original.markers.copySelf();
}
this.countOfObservations = original.countOfObservations;
this.pValue = original.pValue;
this.initialFive.addAll(original.initialFive);
}
/** {@inheritDoc} */
@Override
public int hashCode() {
double result = getResult();
result = Double.isNaN(result) ? 37 : result;
final double markersHash = markers == null ? 0 : markers.hashCode();
final double[] toHash = {result, quantile, markersHash, countOfObservations};
return Arrays.hashCode(toHash);
}
/**
* Returns true iff {@code o} is a {@code PSquarePercentile} returning the
* same values as this for {@code getResult()} and {@code getN()} and also
* having equal markers
*
* @param o object to compare
* @return true if {@code o} is a {@code PSquarePercentile} with
* equivalent internal state
*/
@Override
public boolean equals(Object o) {
boolean result = false;
if (this == o) {
result = true;
} else if (o instanceof PSquarePercentile) {
PSquarePercentile that = (PSquarePercentile) o;
boolean isNotNull = markers != null && that.markers != null;
boolean isNull = markers == null && that.markers == null;
result = isNotNull ? markers.equals(that.markers) : isNull;
// markers as in the case of first
// five observations
result = result && getN() == that.getN();
}
return result;
}
/**
* {@inheritDoc}The internal state updated due to the new value in this
* context is basically of the marker positions and computation of the
* approximate quantile.
*
* @param observation the observation currently being added.
*/
@Override
public void increment(final double observation) {
// Increment counter
countOfObservations++;
// Store last observation
this.lastObservation = observation;
// 0. Use Brute force for <5
if (markers == null) {
if (initialFive.add(observation)) {
Collections.sort(initialFive);
pValue =
initialFive
.get((int) (quantile * (initialFive.size() - 1)));
return;
}
// 1. Initialize once after 5th observation
markers = newMarkers(initialFive, quantile);
}
// 2. process a Data Point and return pValue
pValue = markers.processDataPoint(observation);
}
/**
* Returns a string containing the last observation, the current estimate
* of the quantile and all markers.
*
* @return string representation of state data
*/
@Override
public String toString() {
synchronized (this) {
synchronized (DECIMAL_FORMAT) {
if (markers == null) {
return String.format("obs=%s pValue=%s",
DECIMAL_FORMAT.format(lastObservation),
DECIMAL_FORMAT.format(pValue));
} else {
return String.format("obs=%s markers=%s",
DECIMAL_FORMAT.format(lastObservation), markers.toString());
}
}
}
}
/** {@inheritDoc} */
@Override
public long getN() {
return countOfObservations;
}
/** {@inheritDoc} */
@Override
public PSquarePercentile copy() {
return new PSquarePercentile(this);
}
/**
* Returns the quantile estimated by this statistic in the range [0.0-1.0]
*
* @return quantile estimated by {@link #getResult()}
*/
public double quantile() {
return quantile;
}
/**
* {@inheritDoc}. This basically clears all the markers, the
* initialFive list and sets countOfObservations to 0.
*/
@Override
public void clear() {
markers = null;
initialFive.clear();
countOfObservations = 0L;
pValue = Double.NaN;
}
/**
* {@inheritDoc}
*/
@Override
public double getResult() {
if (Double.compare(quantile, 1d) == 0) {
pValue = maximum();
} else if (Double.compare(quantile, 0d) == 0) {
pValue = minimum();
}
return pValue;
}
/** Get quantile estimated by this statistic.
* @return the quantile estimated by this statistic
*/
public double getQuantile() {
return quantile;
}
/** Get maximum in the data set added to this statistic.
* @return maximum in the data set added to this statistic
*/
private double maximum() {
double val = Double.NaN;
if (markers != null) {
val = markers.height(PSQUARE_CONSTANT);
} else if (!initialFive.isEmpty()) {
val = initialFive.get(initialFive.size() - 1);
}
return val;
}
/** Get minimum in the data set added to this statistic.
* @return minimum in the data set added to this statistic
*/
private double minimum() {
double val = Double.NaN;
if (markers != null) {
val = markers.height(1);
} else if (!initialFive.isEmpty()) {
val = initialFive.get(0);
}
return val;
}
/**
* Markers is an encapsulation of the five markers/buckets as indicated in
* the original works.
*/
private static class Markers implements PSquareMarkers, Serializable {
/**
* Serial version id
*/
private static final long serialVersionUID = 1L;
/** Low marker index */
private static final int LOW = 2;
/** High marker index */
private static final int HIGH = 4;
/**
* Array of 5+1 Markers (The first marker is dummy just so we
* can match the rest of indexes [1-5] indicated in the original works
* which follows unit based index)
*/
private final Marker[] markerArray;
/**
* Constructor
*
* @param theMarkerArray marker array to be used, a reference to the array will be stored
*/
private Markers(final Marker[] theMarkerArray) { // NOPMD - storing a reference to the array is intentional and documented here
MathUtils.checkNotNull(theMarkerArray);
markerArray = theMarkerArray;
for (int i = 1; i < PSQUARE_CONSTANT; i++) {
markerArray[i].previous(markerArray[i - 1])
.next(markerArray[i + 1]).index(i);
}
markerArray[0].previous(markerArray[0])
.next(markerArray[1])
.index(0);
markerArray[5].previous(markerArray[4])
.next(markerArray[5])
.index(5);
}
/**
* Constructor
*
* @param initialFive elements required to build Marker
* @param p quantile required to be computed
*/
private Markers(final List<Double> initialFive, final double p) {
this(createMarkerArray(initialFive, p));
}
/**
* Creates a marker array using initial five elements and a quantile
*
* @param initialFive list of initial five elements
* @param p the pth quantile
* @return Marker array
*/
private static Marker[] createMarkerArray(
final List<Double> initialFive, final double p) {
final int countObserved =
initialFive == null ? -1 : initialFive.size();
if (countObserved < PSQUARE_CONSTANT) {
throw new MathIllegalArgumentException(
LocalizedCoreFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
countObserved, PSQUARE_CONSTANT);
}
Collections.sort(initialFive);
return new Marker[] {
new Marker(),// Null Marker
new Marker(initialFive.get(0), 1, 0, 1),
new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2),
new Marker(initialFive.get(2), 1 + 4 * p, p, 3),
new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4),
new Marker(initialFive.get(4), 5, 1, 5) };
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Arrays.deepHashCode(markerArray);
}
/**
* {@inheritDoc}.This equals method basically checks for marker array to
* be deep equals.
*
* @param o is the other object
* @return true if the object compares with this object are equivalent
*/
@Override
public boolean equals(Object o) {
boolean result = false;
if (this == o) {
result = true;
} else if (o instanceof Markers) {
Markers that = (Markers) o;
result = Arrays.deepEquals(markerArray, that.markerArray);
}
return result;
}
/**
* Process a data point
*
* @param inputDataPoint is the data point passed
* @return computed percentile
*/
@Override
public double processDataPoint(final double inputDataPoint) {
// 1. Find cell and update minima and maxima
final int kthCell = findCellAndUpdateMinMax(inputDataPoint);
// 2. Increment positions
incrementPositions(1, kthCell + 1, 5);
// 2a. Update desired position with increments
updateDesiredPositions();
// 3. Adjust heights of m[2-4] if necessary
adjustHeightsOfMarkers();
// 4. Return percentile
return getPercentileValue();
}
/**
* Returns the percentile computed thus far.
*
* @return height of mid point marker
*/
@Override
public double getPercentileValue() {
return height(3);
}
/**
* Finds the cell where the input observation / value fits.
*
* @param observation the input value to be checked for
* @return kth cell (of the markers ranging from 1-5) where observed
* sample fits
*/
private int findCellAndUpdateMinMax(final double observation) {
if (observation < height(1)) {
markerArray[1].markerHeight = observation;
return 1;
} else if (observation < height(2)) {
return 1;
} else if (observation < height(3)) {
return 2;
} else if (observation < height(4)) {
return 3;
} else if (observation <= height(5)) {
return 4;
} else {
markerArray[5].markerHeight = observation;
return 4;
}
}
/**
* Adjust marker heights by setting quantile estimates to middle markers.
*/
private void adjustHeightsOfMarkers() {
for (int i = LOW; i <= HIGH; i++) {
estimate(i);
}
}
/**
* {@inheritDoc}
*/
@Override
public double estimate(final int index) {
MathUtils.checkRangeInclusive(index, LOW, HIGH);
return markerArray[index].estimate();
}
/**
* Increment positions by d. Refer to algorithm paper for the
* definition of d.
*
* @param d The increment value for the position
* @param startIndex start index of the marker array
* @param endIndex end index of the marker array
*/
private void incrementPositions(final int d, final int startIndex,
final int endIndex) {
for (int i = startIndex; i <= endIndex; i++) {
markerArray[i].incrementPosition(d);
}
}
/**
* Desired positions incremented by bucket width. The bucket width is
* basically the desired increments.
*/
private void updateDesiredPositions() {
for (int i = 1; i < markerArray.length; i++) {
markerArray[i].updateDesiredPosition();
}
}
/**
* Sets previous and next markers after default read is done.
*
* @param anInputStream the input stream to be deserialized
* @throws ClassNotFoundException thrown when a desired class not found
* @throws IOException thrown due to any io errors
*/
private void readObject(ObjectInputStream anInputStream)
throws ClassNotFoundException, IOException {
// always perform the default de-serialization first
anInputStream.defaultReadObject();
// Build links
for (int i = 1; i < PSQUARE_CONSTANT; i++) {
markerArray[i].previous(markerArray[i - 1]).next(markerArray[i + 1]).index(i);
}
markerArray[0].previous(markerArray[0]).next(markerArray[1]).index(0);
markerArray[5].previous(markerArray[4]).next(markerArray[5]).index(5);
}
/**
* Return marker height given index
*
* @param markerIndex index of marker within (1,6)
* @return marker height
*/
@Override
public double height(final int markerIndex) {
MathUtils.checkRangeInclusive(markerIndex, 1, markerArray.length - 1);
return markerArray[markerIndex].markerHeight;
}
/** {@inheritDoc} */
@Override
public Markers copySelf() {
return new Markers(new Marker[] {
new Marker(),
markerArray[1].copySelf(),
markerArray[2].copySelf(),
markerArray[3].copySelf(),
markerArray[4].copySelf(),
markerArray[5].copySelf()
});
}
/**
* Returns string representation of the Marker array.
*
* @return Markers as a string
*/
@Override
public String toString() {
return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]",
markerArray[1].toString(), markerArray[2].toString(),
markerArray[3].toString(), markerArray[4].toString(),
markerArray[5].toString());
}
}
/**
* The class modeling the attributes of the marker of the P-square algorithm
*/
private static class Marker implements Serializable {
/**
* Serial Version ID
*/
private static final long serialVersionUID = -3575879478288538431L;
/**
* The marker index which is just a serial number for the marker in the
* marker array of 5+1.
*/
private int index;
/**
* The integral marker position. Refer to the variable n in the original
* works.
*/
private double intMarkerPosition;
/**
* Desired marker position. Refer to the variable n' in the original
* works.
*/
private double desiredMarkerPosition;
/**
* Marker height or the quantile. Refer to the variable q in the
* original works.
*/
private double markerHeight;
/**
* Desired marker increment. Refer to the variable dn' in the original
* works.
*/
private double desiredMarkerIncrement;
/**
* Next and previous markers for easy linked navigation in loops. this
* is not serialized as they can be rebuilt during deserialization.
*/
private transient Marker next;
/**
* The previous marker links
*/
private transient Marker previous;
/**
* Nonlinear interpolator
*/
private final UnivariateInterpolator nonLinear = new NevilleInterpolator();
/**
* Linear interpolator which is not serializable
*/
private transient UnivariateInterpolator linear = new LinearInterpolator();
/**
* Default constructor
*/
private Marker() {
this.next = this.previous = this;
}
/**
* Constructor of the marker with parameters
*
* @param heightOfMarker represent the quantile value
* @param makerPositionDesired represent the desired marker position
* @param markerPositionIncrement represent increments for position
* @param markerPositionNumber represent the position number of marker
*/
private Marker(double heightOfMarker, double makerPositionDesired,
double markerPositionIncrement, double markerPositionNumber) {
this();
this.markerHeight = heightOfMarker;
this.desiredMarkerPosition = makerPositionDesired;
this.desiredMarkerIncrement = markerPositionIncrement;
this.intMarkerPosition = markerPositionNumber;
}
/**
* Sets the previous marker.
*
* @param previousMarker the previous marker to the current marker in
* the array of markers
* @return this instance
*/
private Marker previous(final Marker previousMarker) {
MathUtils.checkNotNull(previousMarker);
this.previous = previousMarker;
return this;
}
/**
* Sets the next marker.
*
* @param nextMarker the next marker to the current marker in the array
* of markers
* @return this instance
*/
private Marker next(final Marker nextMarker) {
MathUtils.checkNotNull(nextMarker);
this.next = nextMarker;
return this;
}
/**
* Sets the index of the marker.
*
* @param indexOfMarker the array index of the marker in marker array
* @return this instance
*/
private Marker index(final int indexOfMarker) {
this.index = indexOfMarker;
return this;
}
/**
* Update desired Position with increment.
*/
private void updateDesiredPosition() {
desiredMarkerPosition += desiredMarkerIncrement;
}
/**
* Increment Position by d.
*
* @param d a delta value to increment
*/
private void incrementPosition(final int d) {
intMarkerPosition += d;
}
/**
* Difference between desired and actual position
*
* @return difference between desired and actual position
*/
private double difference() {
return desiredMarkerPosition - intMarkerPosition;
}
/**
* Estimate the quantile for the current marker.
*
* @return estimated quantile
*/
private double estimate() {
final double di = difference();
final boolean isNextHigher =
next.intMarkerPosition - intMarkerPosition > 1;
final boolean isPreviousLower =
previous.intMarkerPosition - intMarkerPosition < -1;
if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) {
final int d = di >= 0 ? 1 : -1;
final double[] xval = { previous.intMarkerPosition, intMarkerPosition, next.intMarkerPosition };
final double[] yval = { previous.markerHeight, markerHeight, next.markerHeight };
final double xD = intMarkerPosition + d;
UnivariateFunction univariateFunction =
nonLinear.interpolate(xval, yval);
markerHeight = univariateFunction.value(xD);
// If parabolic estimate is bad then turn linear
if (isEstimateBad(yval, markerHeight)) {
int delta = xD - xval[1] > 0 ? 1 : -1;
final double[] xBad = { xval[1], xval[1 + delta] };
final double[] yBad = { yval[1], yval[1 + delta] };
MathArrays.sortInPlace(xBad, yBad);// since d can be +/- 1
univariateFunction = linear.interpolate(xBad, yBad);
markerHeight = univariateFunction.value(xD);
}
incrementPosition(d);
}
return markerHeight;
}
/**
* Check if parabolic/nonlinear estimate is bad by checking if the
* ordinate found is beyond the y[0] and y[2].
*
* @param y the array to get the bounds
* @param yD the estimate
* @return true if yD is a bad estimate
*/
private boolean isEstimateBad(final double[] y, final double yD) {
return yD <= y[0] || yD >= y[2];
}
/**
* {@inheritDoc}<i>This equals method checks for marker attributes and
* as well checks if navigation pointers (next and previous) are the same
* between this and passed in object</i>
*
* @param o Other object
* @return true if this equals passed in other object o
*/
@Override
public boolean equals(Object o) {
boolean result = false;
if (this == o) {
result = true;
} else if (o instanceof Marker) {
Marker that = (Marker) o;
result = Double.compare(markerHeight, that.markerHeight) == 0;
result =
result &&
Double.compare(intMarkerPosition,
that.intMarkerPosition) == 0;
result =
result &&
Double.compare(desiredMarkerPosition,
that.desiredMarkerPosition) == 0;
result =
result &&
Double.compare(desiredMarkerIncrement,
that.desiredMarkerIncrement) == 0;
result = result && next.index == that.next.index;
result = result && previous.index == that.previous.index;
}
return result;
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition,
desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index});
}
/**
* Read Object to deserialize.
*
* @param anInstream Stream Object data
* @throws IOException thrown for IO Errors
* @throws ClassNotFoundException thrown for class not being found
*/
private void readObject(ObjectInputStream anInstream)
throws ClassNotFoundException, IOException {
anInstream.defaultReadObject();
previous=next=this;
linear = new LinearInterpolator();
}
/** Copy this instance.
* @return copy of the instance
*/
public Marker copySelf() {
return new Marker(markerHeight, desiredMarkerPosition, desiredMarkerIncrement, intMarkerPosition);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return String.format(
"index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d",
(double) index, Precision.round(intMarkerPosition, 0),
Precision.round(desiredMarkerPosition, 2),
Precision.round(markerHeight, 2),
Precision.round(desiredMarkerIncrement, 2), previous.index,
next.index);
}
}
/**
* A simple fixed capacity list that has an upper bound to growth.
* Once its capacity is reached, {@code add} is a no-op, returning
* {@code false}.
*
* @param <E> type of the elements
*/
private static class FixedCapacityList<E> extends ArrayList<E> implements Serializable {
/**
* Serialization Version Id
*/
private static final long serialVersionUID = 2283952083075725479L;
/**
* Capacity of the list
*/
private final int capacity;
/**
* This constructor constructs the list with given capacity and as well
* as stores the capacity
*
* @param fixedCapacity the capacity to be fixed for this list
*/
FixedCapacityList(final int fixedCapacity) {
super(fixedCapacity);
this.capacity = fixedCapacity;
}
/**
* {@inheritDoc} In addition it checks if the {@link #size()} returns a
* size that is within capacity and if true it adds; otherwise the list
* contents are unchanged and {@code false} is returned.
*
* @return true if addition is successful and false otherwise
*/
@Override
public boolean add(final E e) {
return size() < capacity && super.add(e);
}
/**
* {@inheritDoc} In addition it checks if the sum of Collection size and
* this instance's {@link #size()} returns a value that is within
* capacity and if true it adds the collection; otherwise the list
* contents are unchanged and {@code false} is returned.
*
* @return true if addition is successful and false otherwise
*/
@Override
public boolean addAll(Collection<? extends E> collection) {
boolean isCollectionLess =
collection != null &&
collection.size() + size() <= capacity;
return isCollectionLess && super.addAll(collection);
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object other) {
return super.equals(other) && capacity == ((FixedCapacityList<?>) other).capacity;
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return super.hashCode() + capacity;
}
}
/**
* A creation method to build Markers
*
* @param initialFive list of initial five elements
* @param p the quantile desired
* @return an instance of PSquareMarkers
*/
public static PSquareMarkers newMarkers(final List<Double> initialFive, final double p) {
return new Markers(initialFive, p);
}
/**
* An interface that encapsulates abstractions of the
* P-square algorithm markers as is explained in the original works. This
* interface is exposed with protected access to help in testability.
*/
protected interface PSquareMarkers {
/**
* Returns Percentile value computed thus far.
*
* @return percentile
*/
double getPercentileValue();
/**
* A deep copy function to clone the current instance.
*
* @return deep copy of this instance
*/
PSquareMarkers copySelf();
/**
* Returns the marker height (or percentile) of a given marker index.
*
* @param markerIndex is the index of marker in the marker array
* @return percentile value of the marker index passed
* @throws MathIllegalArgumentException in case the index is not within [1-5]
*/
double height(int markerIndex);
/**
* Process a data point by moving the marker heights based on estimator.
*
* @param inputDataPoint is the data point passed
* @return computed percentile
*/
double processDataPoint(double inputDataPoint);
/**
* An Estimate of the percentile value of a given Marker
*
* @param index the marker's index in the array of markers
* @return percentile estimate
* @throws MathIllegalArgumentException in case if index is not within [1-5]
*/
double estimate(int index);
}
}