View Javadoc
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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.stat.descriptive.rank;
23  
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.io.Serializable;
27  import java.text.DecimalFormat;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.List;
33  
34  import org.hipparchus.analysis.UnivariateFunction;
35  import org.hipparchus.analysis.interpolation.LinearInterpolator;
36  import org.hipparchus.analysis.interpolation.NevilleInterpolator;
37  import org.hipparchus.analysis.interpolation.UnivariateInterpolator;
38  import org.hipparchus.exception.LocalizedCoreFormats;
39  import org.hipparchus.exception.MathIllegalArgumentException;
40  import org.hipparchus.stat.descriptive.AbstractStorelessUnivariateStatistic;
41  import org.hipparchus.stat.descriptive.StorelessUnivariateStatistic;
42  import org.hipparchus.util.MathArrays;
43  import org.hipparchus.util.MathUtils;
44  import org.hipparchus.util.Precision;
45  
46  /**
47   * A {@link StorelessUnivariateStatistic} estimating percentiles using the
48   * <a href=http://www.cs.wustl.edu/~jain/papers/ftp/psqr.pdf>P<SUP>2</SUP></a>
49   * Algorithm as explained by <a href=http://www.cse.wustl.edu/~jain/>Raj
50   * Jain</a> and Imrich Chlamtac in
51   * <a href=http://www.cse.wustl.edu/~jain/papers/psqr.htm>P<SUP>2</SUP> Algorithm
52   * for Dynamic Calculation of Quantiles and Histogram Without Storing
53   * Observations</a>.
54   * <p>
55   * Note: This implementation is not synchronized and produces an approximate
56   * result. For small samples, where data can be stored and processed in memory,
57   * {@link Percentile} should be used.
58   */
59  public class PSquarePercentile extends AbstractStorelessUnivariateStatistic
60      implements StorelessUnivariateStatistic, Serializable {
61  
62      /** The maximum array size used for psquare algorithm */
63      private static final int PSQUARE_CONSTANT = 5;
64  
65      /**
66       * A Default quantile needed in case if user prefers to use default no
67       * argument constructor.
68       */
69      private static final double DEFAULT_QUANTILE_DESIRED = 50d;
70  
71      /** Serial ID */
72      private static final long serialVersionUID = 20150412L;
73  
74      /** A decimal formatter for print convenience */
75      private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("00.00");
76  
77      /**
78       * Initial list of 5 numbers corresponding to 5 markers. <b>NOTE:</b>watch
79       * out for the add methods that are overloaded
80       */
81      private final List<Double> initialFive = new FixedCapacityList<>(PSQUARE_CONSTANT);
82  
83      /**
84       * The quantile needed should be in range of 0-1. The constructor
85       * {@link #PSquarePercentile(double)} ensures that passed in percentile is
86       * divided by 100.
87       */
88      private final double quantile;
89  
90      /**
91       * lastObservation is the last observation value/input sample. No need to
92       * serialize.
93       */
94      private transient double lastObservation;
95  
96      /**
97       * Markers is the marker collection object which comes to effect
98       * only after 5 values are inserted
99       */
100     private PSquareMarkers markers;
101 
102     /**
103      * Computed p value (i,e percentile value of data set hither to received)
104      */
105     private double pValue = Double.NaN;
106 
107     /**
108      * Counter to count the values/observations accepted into this data set
109      */
110     private long countOfObservations;
111 
112     /**
113      * Constructs a PSquarePercentile with the specific percentile value.
114      * @param p the percentile
115      * @throws MathIllegalArgumentException  if p is not greater than 0 and less
116      * than or equal to 100
117      */
118     public PSquarePercentile(final double p) {
119         if (p > 100 || p < 0) {
120             throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE,
121                                                    p, 0, 100);
122         }
123         this.quantile = p / 100d;// always set it within (0,1]
124     }
125 
126     /**
127      * Default constructor that assumes a {@link #DEFAULT_QUANTILE_DESIRED
128      * default quantile} needed.
129      */
130     PSquarePercentile() {
131         this(DEFAULT_QUANTILE_DESIRED);
132     }
133 
134     /**
135      * Copy constructor, creates a new {@code PSquarePercentile} identical
136      * to the {@code original}.
137      *
138      * @param original the {@code PSquarePercentile} instance to copy
139      * @throws org.hipparchus.exception.NullArgumentException if original is null
140      */
141     public PSquarePercentile(PSquarePercentile original) {
142         super();
143 
144         this.quantile = original.quantile;
145 
146         if (original.markers != null) {
147             this.markers = original.markers.copySelf();
148         }
149 
150         this.countOfObservations = original.countOfObservations;
151         this.pValue = original.pValue;
152         this.initialFive.addAll(original.initialFive);
153     }
154 
155     /** {@inheritDoc} */
156     @Override
157     public int hashCode() {
158         double result = getResult();
159         result = Double.isNaN(result) ? 37 : result;
160         final double markersHash = markers == null ? 0 : markers.hashCode();
161         final double[] toHash = {result, quantile, markersHash, countOfObservations};
162         return Arrays.hashCode(toHash);
163     }
164 
165     /**
166      * Returns true iff {@code o} is a {@code PSquarePercentile} returning the
167      * same values as this for {@code getResult()} and {@code getN()} and also
168      * having equal markers
169      *
170      * @param o object to compare
171      * @return true if {@code o} is a {@code PSquarePercentile} with
172      * equivalent internal state
173      */
174     @Override
175     public boolean equals(Object o) {
176         boolean result = false;
177         if (this == o) {
178             result = true;
179         } else if (o instanceof PSquarePercentile) {
180             PSquarePercentile that = (PSquarePercentile) o;
181             boolean isNotNull = markers != null && that.markers != null;
182             boolean isNull = markers == null && that.markers == null;
183             result = isNotNull ? markers.equals(that.markers) : isNull;
184             // markers as in the case of first
185             // five observations
186             result = result && getN() == that.getN();
187         }
188         return result;
189     }
190 
191     /**
192      * {@inheritDoc}The internal state updated due to the new value in this
193      * context is basically of the marker positions and computation of the
194      * approximate quantile.
195      *
196      * @param observation the observation currently being added.
197      */
198     @Override
199     public void increment(final double observation) {
200         // Increment counter
201         countOfObservations++;
202 
203         // Store last observation
204         this.lastObservation = observation;
205 
206         // 0. Use Brute force for <5
207         if (markers == null) {
208             if (initialFive.add(observation)) {
209                 Collections.sort(initialFive);
210                 pValue =
211                         initialFive
212                                 .get((int) (quantile * (initialFive.size() - 1)));
213                 return;
214             }
215             // 1. Initialize once after 5th observation
216             markers = newMarkers(initialFive, quantile);
217         }
218         // 2. process a Data Point and return pValue
219         pValue = markers.processDataPoint(observation);
220     }
221 
222     /**
223      * Returns a string containing the last observation, the current estimate
224      * of the quantile and all markers.
225      *
226      * @return string representation of state data
227      */
228     @Override
229     public String toString() {
230         synchronized (this) {
231             synchronized (DECIMAL_FORMAT) {
232                 if (markers == null) {
233                     return String.format("obs=%s pValue=%s",
234                                          DECIMAL_FORMAT.format(lastObservation),
235                                          DECIMAL_FORMAT.format(pValue));
236                 } else {
237                     return String.format("obs=%s markers=%s",
238                                          DECIMAL_FORMAT.format(lastObservation), markers.toString());
239                 }
240             }
241         }
242    }
243 
244     /** {@inheritDoc} */
245     @Override
246     public long getN() {
247         return countOfObservations;
248     }
249 
250     /** {@inheritDoc} */
251     @Override
252     public PSquarePercentile copy() {
253         return new PSquarePercentile(this);
254     }
255 
256     /**
257      * Returns the quantile estimated by this statistic in the range [0.0-1.0]
258      *
259      * @return quantile estimated by {@link #getResult()}
260      */
261     public double quantile() {
262         return quantile;
263     }
264 
265     /**
266      * {@inheritDoc}. This basically clears all the markers, the
267      * initialFive list and sets countOfObservations to 0.
268      */
269     @Override
270     public void clear() {
271         markers = null;
272         initialFive.clear();
273         countOfObservations = 0L;
274         pValue = Double.NaN;
275     }
276 
277     /**
278      * {@inheritDoc}
279      */
280     @Override
281     public double getResult() {
282         if (Double.compare(quantile, 1d) == 0) {
283             pValue = maximum();
284         } else if (Double.compare(quantile, 0d) == 0) {
285             pValue = minimum();
286         }
287         return pValue;
288     }
289 
290     /** Get quantile estimated by this statistic.
291      * @return the quantile estimated by this statistic
292      */
293     public double getQuantile() {
294         return quantile;
295     }
296 
297     /** Get maximum in the data set added to this statistic.
298      * @return maximum in the data set added to this statistic
299      */
300     private double maximum() {
301         double val = Double.NaN;
302         if (markers != null) {
303             val = markers.height(PSQUARE_CONSTANT);
304         } else if (!initialFive.isEmpty()) {
305             val = initialFive.get(initialFive.size() - 1);
306         }
307         return val;
308     }
309 
310     /** Get minimum in the data set added to this statistic.
311      * @return minimum in the data set added to this statistic
312      */
313     private double minimum() {
314         double val = Double.NaN;
315         if (markers != null) {
316             val = markers.height(1);
317         } else if (!initialFive.isEmpty()) {
318             val = initialFive.get(0);
319         }
320         return val;
321     }
322 
323     /**
324      * Markers is an encapsulation of the five markers/buckets as indicated in
325      * the original works.
326      */
327     private static class Markers implements PSquareMarkers, Serializable {
328         /**
329          * Serial version id
330          */
331         private static final long serialVersionUID = 1L;
332 
333         /** Low marker index */
334         private static final int LOW = 2;
335 
336         /** High marker index */
337         private static final int HIGH = 4;
338 
339         /**
340          * Array of 5+1 Markers (The first marker is dummy just so we
341          * can match the rest of indexes [1-5] indicated in the original works
342          * which follows unit based index)
343          */
344         private final Marker[] markerArray;
345 
346         /**
347          * Constructor
348          *
349          * @param theMarkerArray marker array to be used, a reference to the array will be stored
350          */
351         private Markers(final Marker[] theMarkerArray) { // NOPMD - storing a reference to the array is intentional and documented here
352             MathUtils.checkNotNull(theMarkerArray);
353             markerArray = theMarkerArray;
354             for (int i = 1; i < PSQUARE_CONSTANT; i++) {
355                 markerArray[i].previous(markerArray[i - 1])
356                         .next(markerArray[i + 1]).index(i);
357             }
358             markerArray[0].previous(markerArray[0])
359                           .next(markerArray[1])
360                           .index(0);
361             markerArray[5].previous(markerArray[4])
362                           .next(markerArray[5])
363                           .index(5);
364         }
365 
366         /**
367          * Constructor
368          *
369          * @param initialFive elements required to build Marker
370          * @param p quantile required to be computed
371          */
372         private Markers(final List<Double> initialFive, final double p) {
373             this(createMarkerArray(initialFive, p));
374         }
375 
376         /**
377          * Creates a marker array using initial five elements and a quantile
378          *
379          * @param initialFive list of initial five elements
380          * @param p the pth quantile
381          * @return Marker array
382          */
383         private static Marker[] createMarkerArray(
384                 final List<Double> initialFive, final double p) {
385             final int countObserved =
386                     initialFive == null ? -1 : initialFive.size();
387             if (countObserved < PSQUARE_CONSTANT) {
388                 throw new MathIllegalArgumentException(
389                         LocalizedCoreFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
390                         countObserved, PSQUARE_CONSTANT);
391             }
392             Collections.sort(initialFive);
393             return new Marker[] {
394                     new Marker(),// Null Marker
395                     new Marker(initialFive.get(0), 1, 0, 1),
396                     new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2),
397                     new Marker(initialFive.get(2), 1 + 4 * p, p, 3),
398                     new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4),
399                     new Marker(initialFive.get(4), 5, 1, 5) };
400         }
401 
402         /**
403          * {@inheritDoc}
404          */
405         @Override
406         public int hashCode() {
407             return Arrays.deepHashCode(markerArray);
408         }
409 
410         /**
411          * {@inheritDoc}.This equals method basically checks for marker array to
412          * be deep equals.
413          *
414          * @param o is the other object
415          * @return true if the object compares with this object are equivalent
416          */
417         @Override
418         public boolean equals(Object o) {
419             boolean result = false;
420             if (this == o) {
421                 result = true;
422             } else if (o instanceof Markers) {
423                 Markers that = (Markers) o;
424                 result = Arrays.deepEquals(markerArray, that.markerArray);
425             }
426             return result;
427         }
428 
429         /**
430          * Process a data point
431          *
432          * @param inputDataPoint is the data point passed
433          * @return computed percentile
434          */
435         @Override
436         public double processDataPoint(final double inputDataPoint) {
437 
438             // 1. Find cell and update minima and maxima
439             final int kthCell = findCellAndUpdateMinMax(inputDataPoint);
440 
441             // 2. Increment positions
442             incrementPositions(1, kthCell + 1, 5);
443 
444             // 2a. Update desired position with increments
445             updateDesiredPositions();
446 
447             // 3. Adjust heights of m[2-4] if necessary
448             adjustHeightsOfMarkers();
449 
450             // 4. Return percentile
451             return getPercentileValue();
452         }
453 
454         /**
455          * Returns the percentile computed thus far.
456          *
457          * @return height of mid point marker
458          */
459         @Override
460         public double getPercentileValue() {
461             return height(3);
462         }
463 
464         /**
465          * Finds the cell where the input observation / value fits.
466          *
467          * @param observation the input value to be checked for
468          * @return kth cell (of the markers ranging from 1-5) where observed
469          *         sample fits
470          */
471         private int findCellAndUpdateMinMax(final double observation) {
472             if (observation < height(1)) {
473                 markerArray[1].markerHeight = observation;
474                 return 1;
475             } else if (observation < height(2)) {
476                 return 1;
477             } else if (observation < height(3)) {
478                 return 2;
479             } else if (observation < height(4)) {
480                 return 3;
481             } else if (observation <= height(5)) {
482                 return 4;
483             } else {
484                 markerArray[5].markerHeight = observation;
485                 return 4;
486             }
487         }
488 
489         /**
490          * Adjust marker heights by setting quantile estimates to middle markers.
491          */
492         private void adjustHeightsOfMarkers() {
493             for (int i = LOW; i <= HIGH; i++) {
494                 estimate(i);
495             }
496         }
497 
498         /**
499          * {@inheritDoc}
500          */
501         @Override
502         public double estimate(final int index) {
503             MathUtils.checkRangeInclusive(index, LOW, HIGH);
504             return markerArray[index].estimate();
505         }
506 
507         /**
508          * Increment positions by d. Refer to algorithm paper for the
509          * definition of d.
510          *
511          * @param d The increment value for the position
512          * @param startIndex start index of the marker array
513          * @param endIndex end index of the marker array
514          */
515         private void incrementPositions(final int d, final int startIndex,
516                 final int endIndex) {
517             for (int i = startIndex; i <= endIndex; i++) {
518                 markerArray[i].incrementPosition(d);
519             }
520         }
521 
522         /**
523          * Desired positions incremented by bucket width. The bucket width is
524          * basically the desired increments.
525          */
526         private void updateDesiredPositions() {
527             for (int i = 1; i < markerArray.length; i++) {
528                 markerArray[i].updateDesiredPosition();
529             }
530         }
531 
532         /**
533          * Sets previous and next markers after default read is done.
534          *
535          * @param anInputStream the input stream to be deserialized
536          * @throws ClassNotFoundException thrown when a desired class not found
537          * @throws IOException thrown due to any io errors
538          */
539         private void readObject(ObjectInputStream anInputStream)
540                 throws ClassNotFoundException, IOException {
541             // always perform the default de-serialization first
542             anInputStream.defaultReadObject();
543             // Build links
544             for (int i = 1; i < PSQUARE_CONSTANT; i++) {
545                 markerArray[i].previous(markerArray[i - 1]).next(markerArray[i + 1]).index(i);
546             }
547             markerArray[0].previous(markerArray[0]).next(markerArray[1]).index(0);
548             markerArray[5].previous(markerArray[4]).next(markerArray[5]).index(5);
549         }
550 
551         /**
552          * Return marker height given index
553          *
554          * @param markerIndex index of marker within (1,6)
555          * @return marker height
556          */
557         @Override
558         public double height(final int markerIndex) {
559             MathUtils.checkRangeInclusive(markerIndex, 1, markerArray.length - 1);
560             return markerArray[markerIndex].markerHeight;
561         }
562 
563         /** {@inheritDoc} */
564         @Override
565         public Markers copySelf() {
566             return new Markers(new Marker[] {
567                 new Marker(),
568                 markerArray[1].copySelf(),
569                 markerArray[2].copySelf(),
570                 markerArray[3].copySelf(),
571                 markerArray[4].copySelf(),
572                 markerArray[5].copySelf()
573             });
574 
575         }
576 
577         /**
578          * Returns string representation of the Marker array.
579          *
580          * @return Markers as a string
581          */
582         @Override
583         public String toString() {
584             return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]",
585                     markerArray[1].toString(), markerArray[2].toString(),
586                     markerArray[3].toString(), markerArray[4].toString(),
587                     markerArray[5].toString());
588         }
589 
590     }
591 
592     /**
593      * The class modeling the attributes of the marker of the P-square algorithm
594      */
595     private static class Marker implements Serializable {
596 
597         /**
598          * Serial Version ID
599          */
600         private static final long serialVersionUID = -3575879478288538431L;
601 
602         /**
603          * The marker index which is just a serial number for the marker in the
604          * marker array of 5+1.
605          */
606         private int index;
607 
608         /**
609          * The integral marker position. Refer to the variable n in the original
610          * works.
611          */
612         private double intMarkerPosition;
613 
614         /**
615          * Desired marker position. Refer to the variable n' in the original
616          * works.
617          */
618         private double desiredMarkerPosition;
619 
620         /**
621          * Marker height or the quantile. Refer to the variable q in the
622          * original works.
623          */
624         private double markerHeight;
625 
626         /**
627          * Desired marker increment. Refer to the variable dn' in the original
628          * works.
629          */
630         private double desiredMarkerIncrement;
631 
632         /**
633          * Next and previous markers for easy linked navigation in loops. this
634          * is not serialized as they can be rebuilt during deserialization.
635          */
636         private transient Marker next;
637 
638         /**
639          * The previous marker links
640          */
641         private transient Marker previous;
642 
643         /**
644          * Nonlinear interpolator
645          */
646         private final UnivariateInterpolator nonLinear = new NevilleInterpolator();
647 
648         /**
649          * Linear interpolator which is not serializable
650          */
651         private transient UnivariateInterpolator linear = new LinearInterpolator();
652 
653         /**
654          * Default constructor
655          */
656         private Marker() {
657             this.next = this.previous = this;
658         }
659 
660         /**
661          * Constructor of the marker with parameters
662          *
663          * @param heightOfMarker represent the quantile value
664          * @param makerPositionDesired represent the desired marker position
665          * @param markerPositionIncrement represent increments for position
666          * @param markerPositionNumber represent the position number of marker
667          */
668         private Marker(double heightOfMarker, double makerPositionDesired,
669                 double markerPositionIncrement, double markerPositionNumber) {
670             this();
671             this.markerHeight = heightOfMarker;
672             this.desiredMarkerPosition = makerPositionDesired;
673             this.desiredMarkerIncrement = markerPositionIncrement;
674             this.intMarkerPosition = markerPositionNumber;
675         }
676 
677         /**
678          * Sets the previous marker.
679          *
680          * @param previousMarker the previous marker to the current marker in
681          *            the array of markers
682          * @return this instance
683          */
684         private Marker previous(final Marker previousMarker) {
685             MathUtils.checkNotNull(previousMarker);
686             this.previous = previousMarker;
687             return this;
688         }
689 
690         /**
691          * Sets the next marker.
692          *
693          * @param nextMarker the next marker to the current marker in the array
694          *            of markers
695          * @return this instance
696          */
697         private Marker next(final Marker nextMarker) {
698             MathUtils.checkNotNull(nextMarker);
699             this.next = nextMarker;
700             return this;
701         }
702 
703         /**
704          * Sets the index of the marker.
705          *
706          * @param indexOfMarker the array index of the marker in marker array
707          * @return this instance
708          */
709         private Marker index(final int indexOfMarker) {
710             this.index = indexOfMarker;
711             return this;
712         }
713 
714         /**
715          * Update desired Position with increment.
716          */
717         private void updateDesiredPosition() {
718             desiredMarkerPosition += desiredMarkerIncrement;
719         }
720 
721         /**
722          * Increment Position by d.
723          *
724          * @param d a delta value to increment
725          */
726         private void incrementPosition(final int d) {
727             intMarkerPosition += d;
728         }
729 
730         /**
731          * Difference between desired and actual position
732          *
733          * @return difference between desired and actual position
734          */
735         private double difference() {
736             return desiredMarkerPosition - intMarkerPosition;
737         }
738 
739         /**
740          * Estimate the quantile for the current marker.
741          *
742          * @return estimated quantile
743          */
744         private double estimate() {
745             final double di = difference();
746             final boolean isNextHigher =
747                     next.intMarkerPosition - intMarkerPosition > 1;
748             final boolean isPreviousLower =
749                     previous.intMarkerPosition - intMarkerPosition < -1;
750 
751             if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) {
752                 final int d = di >= 0 ? 1 : -1;
753                 final double[] xval = { previous.intMarkerPosition, intMarkerPosition, next.intMarkerPosition };
754                 final double[] yval = { previous.markerHeight, markerHeight, next.markerHeight };
755                 final double xD = intMarkerPosition + d;
756 
757                 UnivariateFunction univariateFunction =
758                         nonLinear.interpolate(xval, yval);
759                 markerHeight = univariateFunction.value(xD);
760 
761                 // If parabolic estimate is bad then turn linear
762                 if (isEstimateBad(yval, markerHeight)) {
763                     int delta = xD - xval[1] > 0 ? 1 : -1;
764                     final double[] xBad = { xval[1], xval[1 + delta] };
765                     final double[] yBad = { yval[1], yval[1 + delta] };
766                     MathArrays.sortInPlace(xBad, yBad);// since d can be +/- 1
767                     univariateFunction = linear.interpolate(xBad, yBad);
768                     markerHeight = univariateFunction.value(xD);
769                 }
770                 incrementPosition(d);
771             }
772             return markerHeight;
773         }
774 
775         /**
776          * Check if parabolic/nonlinear estimate is bad by checking if the
777          * ordinate found is beyond the y[0] and y[2].
778          *
779          * @param y the array to get the bounds
780          * @param yD the estimate
781          * @return true if yD is a bad estimate
782          */
783         private boolean isEstimateBad(final double[] y, final double yD) {
784             return yD <= y[0] || yD >= y[2];
785         }
786 
787         /**
788          * {@inheritDoc}<i>This equals method checks for marker attributes and
789          * as well checks if navigation pointers (next and previous) are the same
790          * between this and passed in object</i>
791          *
792          * @param o Other object
793          * @return true if this equals passed in other object o
794          */
795         @Override
796         public boolean equals(Object o) {
797             boolean result = false;
798             if (this == o) {
799                 result = true;
800             } else if (o instanceof Marker) {
801                 Marker that = (Marker) o;
802 
803                 result = Double.compare(markerHeight, that.markerHeight) == 0;
804                 result =
805                         result &&
806                                 Double.compare(intMarkerPosition,
807                                         that.intMarkerPosition) == 0;
808                 result =
809                         result &&
810                                 Double.compare(desiredMarkerPosition,
811                                         that.desiredMarkerPosition) == 0;
812                 result =
813                         result &&
814                                 Double.compare(desiredMarkerIncrement,
815                                         that.desiredMarkerIncrement) == 0;
816 
817                 result = result && next.index == that.next.index;
818                 result = result && previous.index == that.previous.index;
819             }
820             return result;
821         }
822 
823         /** {@inheritDoc} */
824         @Override
825         public int hashCode() {
826             return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition,
827                 desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index});
828         }
829 
830         /**
831          * Read Object to deserialize.
832          *
833          * @param anInstream Stream Object data
834          * @throws IOException thrown for IO Errors
835          * @throws ClassNotFoundException thrown for class not being found
836          */
837         private void readObject(ObjectInputStream anInstream)
838                 throws ClassNotFoundException, IOException {
839             anInstream.defaultReadObject();
840             previous=next=this;
841             linear = new LinearInterpolator();
842         }
843 
844         /** Copy this instance.
845          * @return copy of the instance
846          */
847         public Marker copySelf() {
848             return new Marker(markerHeight, desiredMarkerPosition, desiredMarkerIncrement, intMarkerPosition);
849         }
850 
851         /**
852          * {@inheritDoc}
853          */
854         @Override
855         public String toString() {
856             return String.format(
857                     "index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d",
858                     (double) index, Precision.round(intMarkerPosition, 0),
859                     Precision.round(desiredMarkerPosition, 2),
860                     Precision.round(markerHeight, 2),
861                     Precision.round(desiredMarkerIncrement, 2), previous.index,
862                     next.index);
863         }
864     }
865 
866     /**
867      * A simple fixed capacity list that has an upper bound to growth.
868      * Once its capacity is reached, {@code add} is a no-op, returning
869      * {@code false}.
870      *
871      * @param <E> type of the elements
872      */
873     private static class FixedCapacityList<E> extends ArrayList<E> implements Serializable {
874 
875         /**
876          * Serialization Version Id
877          */
878         private static final long serialVersionUID = 2283952083075725479L;
879         /**
880          * Capacity of the list
881          */
882         private final int capacity;
883 
884         /**
885          * This constructor constructs the list with given capacity and as well
886          * as stores the capacity
887          *
888          * @param fixedCapacity the capacity to be fixed for this list
889          */
890         FixedCapacityList(final int fixedCapacity) {
891             super(fixedCapacity);
892             this.capacity = fixedCapacity;
893         }
894 
895         /**
896          * {@inheritDoc} In addition it checks if the {@link #size()} returns a
897          * size that is within capacity and if true it adds; otherwise the list
898          * contents are unchanged and {@code false} is returned.
899          *
900          * @return true if addition is successful and false otherwise
901          */
902         @Override
903         public boolean add(final E e) {
904             return size() < capacity && super.add(e);
905         }
906 
907         /**
908          * {@inheritDoc} In addition it checks if the sum of Collection size and
909          * this instance's {@link #size()} returns a value that is within
910          * capacity and if true it adds the collection; otherwise the list
911          * contents are unchanged and {@code false} is returned.
912          *
913          * @return true if addition is successful and false otherwise
914          */
915         @Override
916         public boolean addAll(Collection<? extends E> collection) {
917             boolean isCollectionLess =
918                     collection != null &&
919                             collection.size() + size() <= capacity;
920             return isCollectionLess && super.addAll(collection);
921         }
922 
923         /** {@inheritDoc} */
924         @Override
925         public boolean equals(final Object other) {
926             return super.equals(other) && capacity == ((FixedCapacityList<?>) other).capacity;
927         }
928 
929         /** {@inheritDoc} */
930         @Override
931         public int hashCode() {
932             return super.hashCode() + capacity;
933         }
934 
935     }
936 
937     /**
938      * A creation method to build Markers
939      *
940      * @param initialFive list of initial five elements
941      * @param p the quantile desired
942      * @return an instance of PSquareMarkers
943      */
944     public static PSquareMarkers newMarkers(final List<Double> initialFive, final double p) {
945         return new Markers(initialFive, p);
946     }
947 
948     /**
949      * An interface that encapsulates abstractions of the
950      * P-square algorithm markers as is explained in the original works. This
951      * interface is exposed with protected access to help in testability.
952      */
953     protected interface PSquareMarkers {
954         /**
955          * Returns Percentile value computed thus far.
956          *
957          * @return percentile
958          */
959         double getPercentileValue();
960 
961         /**
962          * A deep copy function to clone the current instance.
963          *
964          * @return deep copy of this instance
965          */
966         PSquareMarkers copySelf();
967 
968         /**
969          * Returns the marker height (or percentile) of a given marker index.
970          *
971          * @param markerIndex is the index of marker in the marker array
972          * @return percentile value of the marker index passed
973          * @throws MathIllegalArgumentException in case the index is not within [1-5]
974          */
975         double height(int markerIndex);
976 
977         /**
978          * Process a data point by moving the marker heights based on estimator.
979          *
980          * @param inputDataPoint is the data point passed
981          * @return computed percentile
982          */
983         double processDataPoint(double inputDataPoint);
984 
985         /**
986          * An Estimate of the percentile value of a given Marker
987          *
988          * @param index the marker's index in the array of markers
989          * @return percentile estimate
990          * @throws MathIllegalArgumentException in case if index is not within [1-5]
991          */
992         double estimate(int index);
993     }
994 }