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