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);
    }
}