PSquarePercentile.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.math4.legacy.stat.descriptive.rank;

  18. import java.text.DecimalFormat;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.List;

  24. import org.apache.commons.numbers.core.Precision;
  25. import org.apache.commons.numbers.arrays.SortInPlace;
  26. import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
  27. import org.apache.commons.math4.legacy.analysis.interpolation.LinearInterpolator;
  28. import org.apache.commons.math4.legacy.analysis.interpolation.NevilleInterpolator;
  29. import org.apache.commons.math4.legacy.analysis.interpolation.UnivariateInterpolator;
  30. import org.apache.commons.math4.legacy.exception.InsufficientDataException;
  31. import org.apache.commons.math4.legacy.exception.OutOfRangeException;
  32. import org.apache.commons.math4.legacy.exception.NullArgumentException;
  33. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  34. import org.apache.commons.math4.legacy.stat.descriptive.AbstractStorelessUnivariateStatistic;
  35. import org.apache.commons.math4.legacy.stat.descriptive.StorelessUnivariateStatistic;

  36. /**
  37.  * A {@link StorelessUnivariateStatistic} estimating percentiles using the
  38.  * <a href="http://www.cs.wustl.edu/~jain/papers/ftp/psqr.pdf">P<SUP>2</SUP></a>
  39.  * Algorithm as explained by <a href="http://www.cse.wustl.edu/~jain/">Raj
  40.  * Jain</a> and Imrich Chlamtac in
  41.  * <a href="http://www.cse.wustl.edu/~jain/papers/psqr.htm">P<SUP>2</SUP> Algorithm
  42.  * for Dynamic Calculation of Quantiles and Histogram Without Storing
  43.  * Observations</a>.
  44.  * <p>
  45.  * Note: This implementation is not synchronized and produces an approximate
  46.  * result. For small samples, where data can be stored and processed in memory,
  47.  * {@link Percentile} should be used.</p>
  48.  */
  49. public class PSquarePercentile extends AbstractStorelessUnivariateStatistic
  50.     implements StorelessUnivariateStatistic {

  51.     /**
  52.      * The maximum array size used for psquare algorithm.
  53.      */
  54.     private static final int PSQUARE_CONSTANT = 5;

  55.     /**
  56.      * A Default quantile needed in case if user prefers to use default no
  57.      * argument constructor.
  58.      */
  59.     private static final double DEFAULT_QUANTILE_DESIRED = 50d;

  60.     /**
  61.      * A decimal formatter for print convenience.
  62.      */
  63.     private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("00.00");

  64.     /**
  65.      * Initial list of 5 numbers corresponding to 5 markers. <b>NOTE:</b>watch
  66.      * out for the add methods that are overloaded.
  67.      */
  68.     private final List<Double> initialFive = new FixedCapacityList<>(PSQUARE_CONSTANT);

  69.     /**
  70.      * The quantile needed should be in range of 0-1. The constructor
  71.      * {@link #PSquarePercentile(double)} ensures that passed in percentile is
  72.      * divided by 100.
  73.      */
  74.     private final double quantile;

  75.     /**
  76.      * lastObservation is the last observation value/input sample. No need to
  77.      * serialize
  78.      */
  79.     private transient double lastObservation;

  80.     /**
  81.      * Markers is the marker collection object which comes to effect.
  82.      * only after 5 values are inserted
  83.      */
  84.     private PSquareMarkers markers;

  85.     /**
  86.      * Computed p value (i,e percentile value of data set hither to received).
  87.      */
  88.     private double pValue = Double.NaN;

  89.     /**
  90.      * Counter to count the values/observations accepted into this data set.
  91.      */
  92.     private long countOfObservations;

  93.     /**
  94.      * Constructs a PSquarePercentile with the specific percentile value.
  95.      * @param p the percentile
  96.      * @throws OutOfRangeException  if p is not greater than 0 and less
  97.      * than or equal to 100
  98.      */
  99.     public PSquarePercentile(final double p) {
  100.         if (p > 100 || p < 0) {
  101.             throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE, p, 0, 100);
  102.         }
  103.         this.quantile = p / 100d;// always set it within (0,1]
  104.     }

  105.     /**
  106.      * Default constructor that assumes a {@link #DEFAULT_QUANTILE_DESIRED
  107.      * default quantile} needed.
  108.      */
  109.     PSquarePercentile() {
  110.         this(DEFAULT_QUANTILE_DESIRED);
  111.     }

  112.     /**
  113.      * {@inheritDoc}
  114.      */
  115.     @Override
  116.     public int hashCode() {
  117.         double result = getResult();
  118.         result = Double.isNaN(result) ? 37 : result;
  119.         final double markersHash = markers == null ? 0 : markers.hashCode();
  120.         final double[] toHash = {result, quantile, markersHash, countOfObservations};
  121.         return Arrays.hashCode(toHash);
  122.     }

  123.     /**
  124.      * Returns true iff {@code o} is a {@code PSquarePercentile} returning the.
  125.      * same values as this for {@code getResult()} and {@code getN()} and also
  126.      * having equal markers
  127.      *
  128.      * @param o object to compare
  129.      * @return true if {@code o} is a {@code PSquarePercentile} with
  130.      * equivalent internal state
  131.      */
  132.     @Override
  133.     public boolean equals(Object o) {
  134.         boolean result = false;
  135.         if (this == o) {
  136.             result = true;
  137.         } else if (o instanceof PSquarePercentile) {
  138.             PSquarePercentile that = (PSquarePercentile) o;
  139.             boolean isNotNull = markers != null && that.markers != null;
  140.             boolean isNull = markers == null && that.markers == null;
  141.             result = isNotNull ? markers.equals(that.markers) : isNull;
  142.             // markers as in the case of first
  143.             // five observations
  144.             result = result && getN() == that.getN();
  145.         }
  146.         return result;
  147.     }

  148.     /**
  149.      * {@inheritDoc}The internal state updated due to the new value in this
  150.      * context is basically of the marker positions and computation of the
  151.      * approximate quantile.
  152.      *
  153.      * @param observation the observation currently being added.
  154.      */
  155.     @Override
  156.     public void increment(final double observation) {
  157.         // Increment counter
  158.         countOfObservations++;

  159.         // Store last observation
  160.         this.lastObservation = observation;

  161.         // 0. Use Brute force for <5
  162.         if (markers == null) {
  163.             if (initialFive.add(observation)) {
  164.                 Collections.sort(initialFive);
  165.                 pValue =
  166.                         initialFive
  167.                                 .get((int) (quantile * (initialFive.size() - 1)));
  168.                 return;
  169.             }
  170.             // 1. Initialize once after 5th observation
  171.             markers = newMarkers(initialFive, quantile);
  172.         }
  173.         // 2. process a Data Point and return pValue
  174.         pValue = markers.processDataPoint(observation);
  175.     }

  176.     /**
  177.      * Returns a string containing the last observation, the current estimate
  178.      * of the quantile and all markers.
  179.      *
  180.      * @return string representation of state data
  181.      */
  182.     @Override
  183.     public String toString() {

  184.         if (markers == null) {
  185.             return String.format("obs=%s pValue=%s",
  186.                     DECIMAL_FORMAT.format(lastObservation),
  187.                     DECIMAL_FORMAT.format(pValue));
  188.         } else {
  189.             return String.format("obs=%s markers=%s",
  190.                     DECIMAL_FORMAT.format(lastObservation), markers.toString());
  191.         }
  192.     }

  193.     /**
  194.      * {@inheritDoc}
  195.      */
  196.     @Override
  197.     public long getN() {
  198.         return countOfObservations;
  199.     }

  200.     /**
  201.      * {@inheritDoc}
  202.      */
  203.     @Override
  204.     public StorelessUnivariateStatistic copy() {
  205.         // multiply quantile by 100 now as anyway constructor divides it by 100
  206.         PSquarePercentile copy = new PSquarePercentile(100d * quantile);

  207.         if (markers != null) {
  208.             copy.markers = markers.copy();
  209.         }
  210.         copy.countOfObservations = countOfObservations;
  211.         copy.pValue = pValue;
  212.         copy.initialFive.clear();
  213.         copy.initialFive.addAll(initialFive);

  214.         return copy;
  215.     }

  216.     /**
  217.      * Returns the quantile estimated by this statistic in the range [0.0-1.0].
  218.      *
  219.      * @return quantile estimated by {@link #getResult()}
  220.      */
  221.     public double quantile() {
  222.         return quantile;
  223.     }

  224.     /**
  225.      * {@inheritDoc}. This basically clears all the markers, the
  226.      * initialFive list and sets countOfObservations to 0.
  227.      */
  228.     @Override
  229.     public void clear() {
  230.         markers = null;
  231.         initialFive.clear();
  232.         countOfObservations = 0L;
  233.         pValue = Double.NaN;
  234.     }

  235.     /**
  236.      * {@inheritDoc}
  237.      */
  238.     @Override
  239.     public double getResult() {
  240.         if (Double.compare(quantile, 1d) == 0) {
  241.             pValue = maximum();
  242.         } else if (Double.compare(quantile, 0d) == 0) {
  243.             pValue = minimum();
  244.         }
  245.         return pValue;
  246.     }

  247.     /**
  248.      * @return maximum in the data set added to this statistic
  249.      */
  250.     private double maximum() {
  251.         double val = Double.NaN;
  252.         if (markers != null) {
  253.             val = markers.height(PSQUARE_CONSTANT);
  254.         } else if (!initialFive.isEmpty()) {
  255.             val = initialFive.get(initialFive.size() - 1);
  256.         }
  257.         return val;
  258.     }

  259.     /**
  260.      * @return minimum in the data set added to this statistic
  261.      */
  262.     private double minimum() {
  263.         double val = Double.NaN;
  264.         if (markers != null) {
  265.             val = markers.height(1);
  266.         } else if (!initialFive.isEmpty()) {
  267.             val = initialFive.get(0);
  268.         }
  269.         return val;
  270.     }

  271.     /**
  272.      * Markers is an encapsulation of the five markers/buckets as indicated in
  273.      * the original works.
  274.      */
  275.     private static final class Markers implements PSquareMarkers {
  276.         /** Low marker index. */
  277.         private static final int LOW = 2;

  278.         /** High marker index. */
  279.         private static final int HIGH = 4;

  280.         /**
  281.          * Array of 5+1 Markers (The first marker is dummy just so we.
  282.          * can match the rest of indexes [1-5] indicated in the original works
  283.          * which follows unit based index)
  284.          */
  285.         private final Marker[] markerArray;

  286.         /**
  287.          * Kth cell belonging to [1-5] of the markerArray. No need for
  288.          * this to be serialized
  289.          */
  290.         private transient int k = -1;

  291.         /**
  292.          * Constructor.
  293.          *
  294.          * @param theMarkerArray marker array to be used
  295.          */
  296.         private Markers(final Marker[] theMarkerArray) {
  297.             NullArgumentException.check(theMarkerArray);
  298.             markerArray = theMarkerArray;
  299.             for (int i = 1; i < PSQUARE_CONSTANT; i++) {
  300.                 markerArray[i].previous(markerArray[i - 1])
  301.                         .next(markerArray[i + 1]).index(i);
  302.             }
  303.             markerArray[0].previous(markerArray[0]).next(markerArray[1])
  304.                     .index(0);
  305.             markerArray[5].previous(markerArray[4]).next(markerArray[5])
  306.                     .index(5);
  307.         }

  308.         /**
  309.          * Constructor.
  310.          *
  311.          * @param initialFive elements required to build Marker
  312.          * @param p quantile required to be computed
  313.          */
  314.         private Markers(final List<Double> initialFive, final double p) {
  315.             this(createMarkerArray(initialFive, p));
  316.         }

  317.         /**
  318.          * Creates a marker array using initial five elements and a quantile.
  319.          *
  320.          * @param initialFive list of initial five elements
  321.          * @param p the pth quantile
  322.          * @return Marker array
  323.          */
  324.         private static Marker[] createMarkerArray(
  325.                 final List<Double> initialFive, final double p) {
  326.             final int countObserved =
  327.                     initialFive == null ? -1 : initialFive.size();
  328.             if (countObserved < PSQUARE_CONSTANT) {
  329.                 throw new InsufficientDataException(
  330.                         LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
  331.                         countObserved, PSQUARE_CONSTANT);
  332.             }
  333.             Collections.sort(initialFive);
  334.             return new Marker[] {
  335.                     new Marker(),// Null Marker
  336.                     new Marker(initialFive.get(0), 1, 0, 1),
  337.                     new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2),
  338.                     new Marker(initialFive.get(2), 1 + 4 * p, p, 3),
  339.                     new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4),
  340.                     new Marker(initialFive.get(4), 5, 1, 5) };
  341.         }

  342.         /**
  343.          * {@inheritDoc}
  344.          */
  345.         @Override
  346.         public int hashCode() {
  347.             return Arrays.deepHashCode(markerArray);
  348.         }

  349.         /**
  350.          * {@inheritDoc}.This equals method basically checks for marker array to
  351.          * be deep equals.
  352.          *
  353.          * @param o is the other object
  354.          * @return true if the object compares with this object are equivalent
  355.          */
  356.         @Override
  357.         public boolean equals(Object o) {
  358.             boolean result = false;
  359.             if (this == o) {
  360.                 result = true;
  361.             } else if (o instanceof Markers) {
  362.                 Markers that = (Markers) o;
  363.                 result = Arrays.deepEquals(markerArray, that.markerArray);
  364.             }
  365.             return result;
  366.         }

  367.         /**
  368.          * Process a data point.
  369.          *
  370.          * @param inputDataPoint is the data point passed
  371.          * @return computed percentile
  372.          */
  373.         @Override
  374.         public double processDataPoint(final double inputDataPoint) {

  375.             // 1. Find cell and update minima and maxima
  376.             final int kthCell = findCellAndUpdateMinMax(inputDataPoint);

  377.             // 2. Increment positions
  378.             incrementPositions(1, kthCell + 1, 5);

  379.             // 2a. Update desired position with increments
  380.             updateDesiredPositions();

  381.             // 3. Adjust heights of m[2-4] if necessary
  382.             adjustHeightsOfMarkers();

  383.             // 4. Return percentile
  384.             return getPercentileValue();
  385.         }

  386.         /**
  387.          * Returns the percentile computed thus far.
  388.          *
  389.          * @return height of mid point marker
  390.          */
  391.         @Override
  392.         public double getPercentileValue() {
  393.             return height(3);
  394.         }

  395.         /**
  396.          * Finds the cell where the input observation / value fits.
  397.          *
  398.          * @param observation the input value to be checked for
  399.          * @return kth cell (of the markers ranging from 1-5) where observed
  400.          *         sample fits
  401.          */
  402.         private int findCellAndUpdateMinMax(final double observation) {
  403.             k = -1;
  404.             if (observation < height(1)) {
  405.                 markerArray[1].markerHeight = observation;
  406.                 k = 1;
  407.             } else if (observation < height(2)) {
  408.                 k = 1;
  409.             } else if (observation < height(3)) {
  410.                 k = 2;
  411.             } else if (observation < height(4)) {
  412.                 k = 3;
  413.             } else if (observation <= height(5)) {
  414.                 k = 4;
  415.             } else {
  416.                 markerArray[5].markerHeight = observation;
  417.                 k = 4;
  418.             }
  419.             return k;
  420.         }

  421.         /**
  422.          * Adjust marker heights by setting quantile estimates to middle markers.
  423.          */
  424.         private void adjustHeightsOfMarkers() {
  425.             for (int i = LOW; i <= HIGH; i++) {
  426.                 estimate(i);
  427.             }
  428.         }

  429.         /**
  430.          * {@inheritDoc}
  431.          */
  432.         @Override
  433.         public double estimate(final int index) {
  434.             if (index < LOW || index > HIGH) {
  435.                 throw new OutOfRangeException(index, LOW, HIGH);
  436.             }
  437.             return markerArray[index].estimate();
  438.         }

  439.         /**
  440.          * Increment positions by d. Refer to algorithm paper for the
  441.          * definition of d.
  442.          *
  443.          * @param d The increment value for the position
  444.          * @param startIndex start index of the marker array
  445.          * @param endIndex end index of the marker array
  446.          */
  447.         private void incrementPositions(final int d, final int startIndex,
  448.                 final int endIndex) {
  449.             for (int i = startIndex; i <= endIndex; i++) {
  450.                 markerArray[i].incrementPosition(d);
  451.             }
  452.         }

  453.         /**
  454.          * Desired positions incremented by bucket width. The bucket width is
  455.          * basically the desired increments.
  456.          */
  457.         private void updateDesiredPositions() {
  458.             for (int i = 1; i < markerArray.length; i++) {
  459.                 markerArray[i].updateDesiredPosition();
  460.             }
  461.         }

  462.         /**
  463.          * Return marker height given index.
  464.          *
  465.          * @param markerIndex index of marker within (1,6)
  466.          * @return marker height
  467.          */
  468.         @Override
  469.         public double height(final int markerIndex) {
  470.             if (markerIndex >= markerArray.length || markerIndex <= 0) {
  471.                 throw new OutOfRangeException(markerIndex, 1, markerArray.length);
  472.             }
  473.             return markerArray[markerIndex].markerHeight;
  474.         }

  475.         /**
  476.          * Copy markers.
  477.          *
  478.          * @return a new instance.
  479.          */
  480.         public Markers copy() {
  481.             return new Markers(new Marker[] {
  482.                     new Marker(),
  483.                     markerArray[1].copy(),
  484.                     markerArray[2].copy(),
  485.                     markerArray[3].copy(),
  486.                     markerArray[4].copy(),
  487.                     markerArray[5].copy()
  488.                 });
  489.         }

  490.         /**
  491.          * Returns string representation of the Marker array.
  492.          *
  493.          * @return Markers as a string
  494.          */
  495.         @Override
  496.         public String toString() {
  497.             return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]",
  498.                     markerArray[1].toString(), markerArray[2].toString(),
  499.                     markerArray[3].toString(), markerArray[4].toString(),
  500.                     markerArray[5].toString());
  501.         }
  502.     }

  503.     /**
  504.      * The class modeling the attributes of the marker of the P-square algorithm.
  505.      */
  506.     private static final class Marker {
  507.         /**
  508.          * The marker index which is just a serial number for the marker in the
  509.          * marker array of 5+1.
  510.          */
  511.         private int index;

  512.         /**
  513.          * The integral marker position. Refer to the variable n in the original
  514.          * works.
  515.          */
  516.         private double intMarkerPosition;

  517.         /**
  518.          * Desired marker position. Refer to the variable n' in the original
  519.          * works.
  520.          */
  521.         private double desiredMarkerPosition;

  522.         /**
  523.          * Marker height or the quantile. Refer to the variable q in the
  524.          * original works.
  525.          */
  526.         private double markerHeight;

  527.         /**
  528.          * Desired marker increment. Refer to the variable dn' in the original
  529.          * works.
  530.          */
  531.         private double desiredMarkerIncrement;

  532.         /**
  533.          * Next and previous markers for easy linked navigation in loops. this
  534.          * is not serialized as they can be rebuilt during deserialization.
  535.          */
  536.         private transient Marker next;

  537.         /**
  538.          * The previous marker links.
  539.          */
  540.         private transient Marker previous;

  541.         /**
  542.          * Nonlinear interpolator.
  543.          */
  544.         private final UnivariateInterpolator nonLinear = new NevilleInterpolator();

  545.         /**
  546.          * Linear interpolator which is not serializable.
  547.          */
  548.         private transient UnivariateInterpolator linear = new LinearInterpolator();

  549.         /**
  550.          * Default constructor.
  551.          */
  552.         private Marker() {
  553.             this.next = this.previous = this;
  554.         }

  555.         /**
  556.          * Constructor of the marker with parameters.
  557.          *
  558.          * @param heightOfMarker represent the quantile value
  559.          * @param makerPositionDesired represent the desired marker position
  560.          * @param markerPositionIncrement represent increments for position
  561.          * @param markerPositionNumber represent the position number of marker
  562.          */
  563.         private Marker(double heightOfMarker, double makerPositionDesired,
  564.                 double markerPositionIncrement, double markerPositionNumber) {
  565.             this();
  566.             this.markerHeight = heightOfMarker;
  567.             this.desiredMarkerPosition = makerPositionDesired;
  568.             this.desiredMarkerIncrement = markerPositionIncrement;
  569.             this.intMarkerPosition = markerPositionNumber;
  570.         }

  571.         /**
  572.          * Sets the previous marker.
  573.          *
  574.          * @param previousMarker the previous marker to the current marker in
  575.          *            the array of markers
  576.          * @return this instance
  577.          */
  578.         private Marker previous(final Marker previousMarker) {
  579.             NullArgumentException.check(previousMarker);
  580.             this.previous = previousMarker;
  581.             return this;
  582.         }

  583.         /**
  584.          * Sets the next marker.
  585.          *
  586.          * @param nextMarker the next marker to the current marker in the array
  587.          *            of markers
  588.          * @return this instance
  589.          */
  590.         private Marker next(final Marker nextMarker) {
  591.             NullArgumentException.check(nextMarker);
  592.             this.next = nextMarker;
  593.             return this;
  594.         }

  595.         /**
  596.          * Sets the index of the marker.
  597.          *
  598.          * @param indexOfMarker the array index of the marker in marker array
  599.          * @return this instance
  600.          */
  601.         private Marker index(final int indexOfMarker) {
  602.             this.index = indexOfMarker;
  603.             return this;
  604.         }

  605.         /**
  606.          * Update desired Position with increment.
  607.          */
  608.         private void updateDesiredPosition() {
  609.             desiredMarkerPosition += desiredMarkerIncrement;
  610.         }

  611.         /**
  612.          * Increment Position by d.
  613.          *
  614.          * @param d a delta value to increment
  615.          */
  616.         private void incrementPosition(final int d) {
  617.             intMarkerPosition += d;
  618.         }

  619.         /**
  620.          * Difference between desired and actual position.
  621.          *
  622.          * @return difference between desired and actual position
  623.          */
  624.         private double difference() {
  625.             return desiredMarkerPosition - intMarkerPosition;
  626.         }

  627.         /**
  628.          * Estimate the quantile for the current marker.
  629.          *
  630.          * @return estimated quantile
  631.          */
  632.         private double estimate() {
  633.             final double di = difference();
  634.             final boolean isNextHigher =
  635.                     next.intMarkerPosition - intMarkerPosition > 1;
  636.             final boolean isPreviousLower =
  637.                     previous.intMarkerPosition - intMarkerPosition < -1;

  638.             if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) {
  639.                 final int d = di >= 0 ? 1 : -1;
  640.                 final double[] xval =
  641.                         new double[] { previous.intMarkerPosition,
  642.                                 intMarkerPosition, next.intMarkerPosition };
  643.                 final double[] yval =
  644.                         new double[] { previous.markerHeight, markerHeight,
  645.                                 next.markerHeight };
  646.                 final double xD = intMarkerPosition + d;

  647.                 UnivariateFunction univariateFunction =
  648.                         nonLinear.interpolate(xval, yval);
  649.                 markerHeight = univariateFunction.value(xD);

  650.                 // If parabolic estimate is bad then turn linear
  651.                 if (isEstimateBad(yval, markerHeight)) {
  652.                     int delta = xD - xval[1] > 0 ? 1 : -1;
  653.                     final double[] xBad =
  654.                             new double[] { xval[1], xval[1 + delta] };
  655.                     final double[] yBad =
  656.                             new double[] { yval[1], yval[1 + delta] };
  657.                     SortInPlace.ASCENDING.apply(xBad, yBad);// since d can be +/- 1
  658.                     univariateFunction = linear.interpolate(xBad, yBad);
  659.                     markerHeight = univariateFunction.value(xD);
  660.                 }
  661.                 incrementPosition(d);
  662.             }
  663.             return markerHeight;
  664.         }

  665.         /**
  666.          * Check if parabolic/nonlinear estimate is bad by checking if the
  667.          * ordinate found is beyond the y[0] and y[2].
  668.          *
  669.          * @param y the array to get the bounds
  670.          * @param yD the estimate
  671.          * @return true if yD is a bad estimate
  672.          */
  673.         private boolean isEstimateBad(final double[] y, final double yD) {
  674.             return yD <= y[0] || yD >= y[2];
  675.         }

  676.         /**
  677.          * {@inheritDoc}<i>This equals method checks for marker attributes and
  678.          * as well checks if navigation pointers (next and previous) are the same
  679.          * between this and passed in object</i>
  680.          *
  681.          * @param o Other object
  682.          * @return true if this equals passed in other object o
  683.          */
  684.         @Override
  685.         public boolean equals(Object o) {
  686.             boolean result = false;
  687.             if (this == o) {
  688.                 result = true;
  689.             } else if (o instanceof Marker) {
  690.                 Marker that = (Marker) o;

  691.                 result = Double.compare(markerHeight, that.markerHeight) == 0;
  692.                 result =
  693.                         result &&
  694.                                 Double.compare(intMarkerPosition,
  695.                                         that.intMarkerPosition) == 0;
  696.                 result =
  697.                         result &&
  698.                                 Double.compare(desiredMarkerPosition,
  699.                                         that.desiredMarkerPosition) == 0;
  700.                 result =
  701.                         result &&
  702.                                 Double.compare(desiredMarkerIncrement,
  703.                                         that.desiredMarkerIncrement) == 0;

  704.                 result = result && next.index == that.next.index;
  705.                 result = result && previous.index == that.previous.index;
  706.             }
  707.             return result;
  708.         }

  709.         /** {@inheritDoc} */
  710.         @Override
  711.         public int hashCode() {
  712.             return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition,
  713.                 desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index});
  714.         }

  715.         /**
  716.          * Copy this instance.
  717.          *
  718.          * @return a new instance.
  719.          */
  720.         public Marker copy() {
  721.             return new Marker(markerHeight, desiredMarkerPosition, desiredMarkerIncrement, intMarkerPosition);
  722.         }

  723.         /**
  724.          * {@inheritDoc}
  725.          */
  726.         @Override
  727.         public String toString() {
  728.             return String.format(
  729.                     "index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d",
  730.                     (double) index, Precision.round(intMarkerPosition, 0),
  731.                     Precision.round(desiredMarkerPosition, 2),
  732.                     Precision.round(markerHeight, 2),
  733.                     Precision.round(desiredMarkerIncrement, 2), previous.index,
  734.                     next.index);
  735.         }
  736.     }

  737.     /**
  738.      * A simple fixed capacity list that has an upper bound to growth.
  739.      * Once its capacity is reached, {@code add} is a no-op, returning
  740.      * {@code false}.
  741.      *
  742.      * @param <E> type for fixed capacity list
  743.      */
  744.     private static final class FixedCapacityList<E> extends ArrayList<E> {
  745.         /**
  746.          * Capacity of the list.
  747.          */
  748.         private final int capacity;

  749.         /**
  750.          * This constructor constructs the list with given capacity and as well.
  751.          * as stores the capacity
  752.          *
  753.          * @param fixedCapacity the capacity to be fixed for this list
  754.          */
  755.         FixedCapacityList(final int fixedCapacity) {
  756.             super(fixedCapacity);
  757.             this.capacity = fixedCapacity;
  758.         }

  759.         /**
  760.          * {@inheritDoc} In addition it checks if the {@link #size()} returns a
  761.          * size that is within capacity and if true it adds; otherwise the list
  762.          * contents are unchanged and {@code false} is returned.
  763.          *
  764.          * @return true if addition is successful and false otherwise
  765.          */
  766.         @Override
  767.         public boolean add(final E e) {
  768.             return size() < capacity && super.add(e);
  769.         }

  770.         /**
  771.          * {@inheritDoc} In addition it checks if the sum of Collection size and
  772.          * this instance's {@link #size()} returns a value that is within
  773.          * capacity and if true it adds the collection; otherwise the list
  774.          * contents are unchanged and {@code false} is returned.
  775.          *
  776.          * @return true if addition is successful and false otherwise
  777.          */
  778.         @Override
  779.         public boolean addAll(Collection<? extends E> collection) {
  780.             boolean isCollectionLess =
  781.                     collection != null &&
  782.                             collection.size() + size() <= capacity;
  783.             return isCollectionLess && super.addAll(collection);
  784.         }
  785.     }

  786.     /**
  787.      * A creation method to build Markers.
  788.      *
  789.      * @param initialFive list of initial five elements
  790.      * @param p the quantile desired
  791.      * @return an instance of PSquareMarkers
  792.      */
  793.     public static PSquareMarkers newMarkers(final List<Double> initialFive, final double p) {
  794.         return new Markers(initialFive, p);
  795.     }

  796.     /**
  797.      * An interface that encapsulates abstractions of the
  798.      * P-square algorithm markers as is explained in the original works.
  799.      */
  800.     interface PSquareMarkers { // Package-private for unit testing.
  801.         /**
  802.          * Returns Percentile value computed thus far.
  803.          *
  804.          * @return percentile
  805.          */
  806.         double getPercentileValue();

  807.         /**
  808.          * Returns the marker height (or percentile) of a given marker index.
  809.          *
  810.          * @param markerIndex is the index of marker in the marker array
  811.          * @return percentile value of the marker index passed
  812.          * @throws OutOfRangeException in case the index is not within [1-5]
  813.          */
  814.         double height(int markerIndex);

  815.         /**
  816.          * Copy factory.
  817.          *
  818.          * @return a new instance
  819.          */
  820.         PSquareMarkers copy();

  821.         /**
  822.          * Process a data point by moving the marker heights based on estimator.
  823.          *
  824.          * @param inputDataPoint is the data point passed
  825.          * @return computed percentile
  826.          */
  827.         double processDataPoint(double inputDataPoint);

  828.         /**
  829.          * An Estimate of the percentile value of a given Marker.
  830.          *
  831.          * @param index the marker's index in the array of markers
  832.          * @return percentile estimate
  833.          * @throws OutOfRangeException in case if index is not within [1-5]
  834.          */
  835.         double estimate(int index);
  836.     }
  837. }