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 = initialFive.get((int) (quantile * (initialFive.size() - 1)));
211                 return;
212             }
213             // 1. Initialize once after 5th observation
214             markers = newMarkers(initialFive, quantile);
215         }
216         // 2. process a Data Point and return pValue
217         pValue = markers.processDataPoint(observation);
218     }
219 
220     /**
221      * Returns a string containing the last observation, the current estimate
222      * of the quantile and all markers.
223      *
224      * @return string representation of state data
225      */
226     @Override
227     public String toString() {
228         synchronized (this) {
229             synchronized (DECIMAL_FORMAT) {
230                 if (markers == null) {
231                     return String.format("obs=%s pValue=%s",
232                                          DECIMAL_FORMAT.format(lastObservation),
233                                          DECIMAL_FORMAT.format(pValue));
234                 } else {
235                     return String.format("obs=%s markers=%s",
236                                          DECIMAL_FORMAT.format(lastObservation), markers.toString());
237                 }
238             }
239         }
240    }
241 
242     /** {@inheritDoc} */
243     @Override
244     public long getN() {
245         return countOfObservations;
246     }
247 
248     /** {@inheritDoc} */
249     @Override
250     public PSquarePercentile copy() {
251         return new PSquarePercentile(this);
252     }
253 
254     /**
255      * Returns the quantile estimated by this statistic in the range [0.0-1.0]
256      *
257      * @return quantile estimated by {@link #getResult()}
258      */
259     public double quantile() {
260         return quantile;
261     }
262 
263     /**
264      * {@inheritDoc}. This basically clears all the markers, the
265      * initialFive list and sets countOfObservations to 0.
266      */
267     @Override
268     public void clear() {
269         markers = null;
270         initialFive.clear();
271         countOfObservations = 0L;
272         pValue = Double.NaN;
273     }
274 
275     /**
276      * {@inheritDoc}
277      */
278     @Override
279     public double getResult() {
280         if (Double.compare(quantile, 1d) == 0) {
281             pValue = maximum();
282         } else if (Double.compare(quantile, 0d) == 0) {
283             pValue = minimum();
284         }
285         return pValue;
286     }
287 
288     /** Get quantile estimated by this statistic.
289      * @return the quantile estimated by this statistic
290      */
291     public double getQuantile() {
292         return quantile;
293     }
294 
295     /** Get maximum in the data set added to this statistic.
296      * @return maximum in the data set added to this statistic
297      */
298     private double maximum() {
299         double val = Double.NaN;
300         if (markers != null) {
301             val = markers.height(PSQUARE_CONSTANT);
302         } else if (!initialFive.isEmpty()) {
303             val = initialFive.get(initialFive.size() - 1);
304         }
305         return val;
306     }
307 
308     /** Get minimum in the data set added to this statistic.
309      * @return minimum in the data set added to this statistic
310      */
311     private double minimum() {
312         double val = Double.NaN;
313         if (markers != null) {
314             val = markers.height(1);
315         } else if (!initialFive.isEmpty()) {
316             val = initialFive.get(0);
317         }
318         return val;
319     }
320 
321     /**
322      * Markers is an encapsulation of the five markers/buckets as indicated in
323      * the original works.
324      */
325     private static class Markers implements PSquareMarkers, Serializable {
326         /**
327          * Serial version id
328          */
329         private static final long serialVersionUID = 1L;
330 
331         /** Low marker index */
332         private static final int LOW = 2;
333 
334         /** High marker index */
335         private static final int HIGH = 4;
336 
337         /**
338          * Array of 5+1 Markers (The first marker is dummy just so we
339          * can match the rest of indexes [1-5] indicated in the original works
340          * which follows unit based index)
341          */
342         private final Marker[] markerArray;
343 
344         /**
345          * Constructor
346          *
347          * @param theMarkerArray marker array to be used, a reference to the array will be stored
348          */
349         private Markers(final Marker[] theMarkerArray) { // NOPMD - storing a reference to the array is intentional and documented here
350             MathUtils.checkNotNull(theMarkerArray);
351             markerArray = theMarkerArray;
352             for (int i = 1; i < PSQUARE_CONSTANT; i++) {
353                 markerArray[i].previous(markerArray[i - 1])
354                         .next(markerArray[i + 1]).index(i);
355             }
356             markerArray[0].previous(markerArray[0])
357                           .next(markerArray[1])
358                           .index(0);
359             markerArray[5].previous(markerArray[4])
360                           .next(markerArray[5])
361                           .index(5);
362         }
363 
364         /**
365          * Constructor
366          *
367          * @param initialFive elements required to build Marker
368          * @param p quantile required to be computed
369          */
370         private Markers(final List<Double> initialFive, final double p) {
371             this(createMarkerArray(initialFive, p));
372         }
373 
374         /**
375          * Creates a marker array using initial five elements and a quantile
376          *
377          * @param initialFive list of initial five elements
378          * @param p the pth quantile
379          * @return Marker array
380          */
381         private static Marker[] createMarkerArray(
382                 final List<Double> initialFive, final double p) {
383             final int countObserved =
384                     initialFive == null ? -1 : initialFive.size();
385             if (countObserved < PSQUARE_CONSTANT) {
386                 throw new MathIllegalArgumentException(
387                         LocalizedCoreFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
388                         countObserved, PSQUARE_CONSTANT);
389             }
390             Collections.sort(initialFive);
391             return new Marker[] {
392                     new Marker(),// Null Marker
393                     new Marker(initialFive.get(0), 1, 0, 1),
394                     new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2),
395                     new Marker(initialFive.get(2), 1 + 4 * p, p, 3),
396                     new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4),
397                     new Marker(initialFive.get(4), 5, 1, 5) };
398         }
399 
400         /**
401          * {@inheritDoc}
402          */
403         @Override
404         public int hashCode() {
405             return Arrays.deepHashCode(markerArray);
406         }
407 
408         /**
409          * {@inheritDoc}.This equals method basically checks for marker array to
410          * be deep equals.
411          *
412          * @param o is the other object
413          * @return true if the object compares with this object are equivalent
414          */
415         @Override
416         public boolean equals(Object o) {
417             boolean result = false;
418             if (this == o) {
419                 result = true;
420             } else if (o instanceof Markers) {
421                 Markers that = (Markers) o;
422                 result = Arrays.deepEquals(markerArray, that.markerArray);
423             }
424             return result;
425         }
426 
427         /**
428          * Process a data point
429          *
430          * @param inputDataPoint is the data point passed
431          * @return computed percentile
432          */
433         @Override
434         public double processDataPoint(final double inputDataPoint) {
435 
436             // 1. Find cell and update minima and maxima
437             final int kthCell = findCellAndUpdateMinMax(inputDataPoint);
438 
439             // 2. Increment positions
440             incrementPositions(1, kthCell + 1, 5);
441 
442             // 2a. Update desired position with increments
443             updateDesiredPositions();
444 
445             // 3. Adjust heights of m[2-4] if necessary
446             adjustHeightsOfMarkers();
447 
448             // 4. Return percentile
449             return getPercentileValue();
450         }
451 
452         /**
453          * Returns the percentile computed thus far.
454          *
455          * @return height of mid point marker
456          */
457         @Override
458         public double getPercentileValue() {
459             return height(3);
460         }
461 
462         /**
463          * Finds the cell where the input observation / value fits.
464          *
465          * @param observation the input value to be checked for
466          * @return kth cell (of the markers ranging from 1-5) where observed
467          *         sample fits
468          */
469         private int findCellAndUpdateMinMax(final double observation) {
470             if (observation < height(1)) {
471                 markerArray[1].markerHeight = observation;
472                 return 1;
473             } else if (observation < height(2)) {
474                 return 1;
475             } else if (observation < height(3)) {
476                 return 2;
477             } else if (observation < height(4)) {
478                 return 3;
479             } else if (observation <= height(5)) {
480                 return 4;
481             } else {
482                 markerArray[5].markerHeight = observation;
483                 return 4;
484             }
485         }
486 
487         /**
488          * Adjust marker heights by setting quantile estimates to middle markers.
489          */
490         private void adjustHeightsOfMarkers() {
491             for (int i = LOW; i <= HIGH; i++) {
492                 estimate(i);
493             }
494         }
495 
496         /**
497          * {@inheritDoc}
498          */
499         @Override
500         public double estimate(final int index) {
501             MathUtils.checkRangeInclusive(index, LOW, HIGH);
502             return markerArray[index].estimate();
503         }
504 
505         /**
506          * Increment positions by d. Refer to algorithm paper for the
507          * definition of d.
508          *
509          * @param d The increment value for the position
510          * @param startIndex start index of the marker array
511          * @param endIndex end index of the marker array
512          */
513         private void incrementPositions(final int d, final int startIndex,
514                 final int endIndex) {
515             for (int i = startIndex; i <= endIndex; i++) {
516                 markerArray[i].incrementPosition(d);
517             }
518         }
519 
520         /**
521          * Desired positions incremented by bucket width. The bucket width is
522          * basically the desired increments.
523          */
524         private void updateDesiredPositions() {
525             for (int i = 1; i < markerArray.length; i++) {
526                 markerArray[i].updateDesiredPosition();
527             }
528         }
529 
530         /**
531          * Sets previous and next markers after default read is done.
532          *
533          * @param anInputStream the input stream to be deserialized
534          * @throws ClassNotFoundException thrown when a desired class not found
535          * @throws IOException thrown due to any io errors
536          */
537         private void readObject(ObjectInputStream anInputStream)
538                 throws ClassNotFoundException, IOException {
539             // always perform the default de-serialization first
540             anInputStream.defaultReadObject();
541             // Build links
542             for (int i = 1; i < PSQUARE_CONSTANT; i++) {
543                 markerArray[i].previous(markerArray[i - 1]).next(markerArray[i + 1]).index(i);
544             }
545             markerArray[0].previous(markerArray[0]).next(markerArray[1]).index(0);
546             markerArray[5].previous(markerArray[4]).next(markerArray[5]).index(5);
547         }
548 
549         /**
550          * Return marker height given index
551          *
552          * @param markerIndex index of marker within (1,6)
553          * @return marker height
554          */
555         @Override
556         public double height(final int markerIndex) {
557             MathUtils.checkRangeInclusive(markerIndex, 1, markerArray.length - 1);
558             return markerArray[markerIndex].markerHeight;
559         }
560 
561         /** {@inheritDoc} */
562         @Override
563         public Markers copySelf() {
564             return new Markers(new Marker[] {
565                 new Marker(),
566                 markerArray[1].copySelf(),
567                 markerArray[2].copySelf(),
568                 markerArray[3].copySelf(),
569                 markerArray[4].copySelf(),
570                 markerArray[5].copySelf()
571             });
572 
573         }
574 
575         /**
576          * Returns string representation of the Marker array.
577          *
578          * @return Markers as a string
579          */
580         @Override
581         public String toString() {
582             return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]",
583                     markerArray[1].toString(), markerArray[2].toString(),
584                     markerArray[3].toString(), markerArray[4].toString(),
585                     markerArray[5].toString());
586         }
587 
588     }
589 
590     /**
591      * The class modeling the attributes of the marker of the P-square algorithm
592      */
593     private static class Marker implements Serializable {
594 
595         /**
596          * Serial Version ID
597          */
598         private static final long serialVersionUID = -3575879478288538431L;
599 
600         /**
601          * The marker index which is just a serial number for the marker in the
602          * marker array of 5+1.
603          */
604         private int index;
605 
606         /**
607          * The integral marker position. Refer to the variable n in the original
608          * works.
609          */
610         private double intMarkerPosition;
611 
612         /**
613          * Desired marker position. Refer to the variable n' in the original
614          * works.
615          */
616         private double desiredMarkerPosition;
617 
618         /**
619          * Marker height or the quantile. Refer to the variable q in the
620          * original works.
621          */
622         private double markerHeight;
623 
624         /**
625          * Desired marker increment. Refer to the variable dn' in the original
626          * works.
627          */
628         private double desiredMarkerIncrement;
629 
630         /**
631          * Next and previous markers for easy linked navigation in loops. this
632          * is not serialized as they can be rebuilt during deserialization.
633          */
634         private transient Marker next;
635 
636         /**
637          * The previous marker links
638          */
639         private transient Marker previous;
640 
641         /**
642          * Nonlinear interpolator
643          */
644         private final UnivariateInterpolator nonLinear = new NevilleInterpolator();
645 
646         /**
647          * Linear interpolator which is not serializable
648          */
649         private transient UnivariateInterpolator linear = new LinearInterpolator();
650 
651         /**
652          * Default constructor
653          */
654         private Marker() {
655             this.next = this.previous = this;
656         }
657 
658         /**
659          * Constructor of the marker with parameters
660          *
661          * @param heightOfMarker represent the quantile value
662          * @param makerPositionDesired represent the desired marker position
663          * @param markerPositionIncrement represent increments for position
664          * @param markerPositionNumber represent the position number of marker
665          */
666         private Marker(double heightOfMarker, double makerPositionDesired,
667                 double markerPositionIncrement, double markerPositionNumber) {
668             this();
669             this.markerHeight = heightOfMarker;
670             this.desiredMarkerPosition = makerPositionDesired;
671             this.desiredMarkerIncrement = markerPositionIncrement;
672             this.intMarkerPosition = markerPositionNumber;
673         }
674 
675         /**
676          * Sets the previous marker.
677          *
678          * @param previousMarker the previous marker to the current marker in
679          *            the array of markers
680          * @return this instance
681          */
682         private Marker previous(final Marker previousMarker) {
683             MathUtils.checkNotNull(previousMarker);
684             this.previous = previousMarker;
685             return this;
686         }
687 
688         /**
689          * Sets the next marker.
690          *
691          * @param nextMarker the next marker to the current marker in the array
692          *            of markers
693          * @return this instance
694          */
695         private Marker next(final Marker nextMarker) {
696             MathUtils.checkNotNull(nextMarker);
697             this.next = nextMarker;
698             return this;
699         }
700 
701         /**
702          * Sets the index of the marker.
703          *
704          * @param indexOfMarker the array index of the marker in marker array
705          * @return this instance
706          */
707         private Marker index(final int indexOfMarker) {
708             this.index = indexOfMarker;
709             return this;
710         }
711 
712         /**
713          * Update desired Position with increment.
714          */
715         private void updateDesiredPosition() {
716             desiredMarkerPosition += desiredMarkerIncrement;
717         }
718 
719         /**
720          * Increment Position by d.
721          *
722          * @param d a delta value to increment
723          */
724         private void incrementPosition(final int d) {
725             intMarkerPosition += d;
726         }
727 
728         /**
729          * Difference between desired and actual position
730          *
731          * @return difference between desired and actual position
732          */
733         private double difference() {
734             return desiredMarkerPosition - intMarkerPosition;
735         }
736 
737         /**
738          * Estimate the quantile for the current marker.
739          *
740          * @return estimated quantile
741          */
742         private double estimate() {
743             final double di = difference();
744             final boolean isNextHigher =
745                     next.intMarkerPosition - intMarkerPosition > 1;
746             final boolean isPreviousLower =
747                     previous.intMarkerPosition - intMarkerPosition < -1;
748 
749             if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) {
750                 final int d = di >= 0 ? 1 : -1;
751                 final double[] xval = { previous.intMarkerPosition, intMarkerPosition, next.intMarkerPosition };
752                 final double[] yval = { previous.markerHeight, markerHeight, next.markerHeight };
753                 final double xD = intMarkerPosition + d;
754 
755                 UnivariateFunction univariateFunction =
756                         nonLinear.interpolate(xval, yval);
757                 markerHeight = univariateFunction.value(xD);
758 
759                 // If parabolic estimate is bad then turn linear
760                 if (isEstimateBad(yval, markerHeight)) {
761                     int delta = xD - xval[1] > 0 ? 1 : -1;
762                     final double[] xBad = { xval[1], xval[1 + delta] };
763                     final double[] yBad = { yval[1], yval[1 + delta] };
764                     MathArrays.sortInPlace(xBad, yBad);// since d can be +/- 1
765                     univariateFunction = linear.interpolate(xBad, yBad);
766                     markerHeight = univariateFunction.value(xD);
767                 }
768                 incrementPosition(d);
769             }
770             return markerHeight;
771         }
772 
773         /**
774          * Check if parabolic/nonlinear estimate is bad by checking if the
775          * ordinate found is beyond the y[0] and y[2].
776          *
777          * @param y the array to get the bounds
778          * @param yD the estimate
779          * @return true if yD is a bad estimate
780          */
781         private boolean isEstimateBad(final double[] y, final double yD) {
782             return yD <= y[0] || yD >= y[2];
783         }
784 
785         /**
786          * {@inheritDoc}<i>This equals method checks for marker attributes and
787          * as well checks if navigation pointers (next and previous) are the same
788          * between this and passed in object</i>
789          *
790          * @param o Other object
791          * @return true if this equals passed in other object o
792          */
793         @Override
794         public boolean equals(Object o) {
795             boolean result = false;
796             if (this == o) {
797                 result = true;
798             } else if (o instanceof Marker) {
799                 Marker that = (Marker) o;
800 
801                 result = Double.compare(markerHeight, that.markerHeight) == 0;
802                 result =
803                         result &&
804                                 Double.compare(intMarkerPosition,
805                                         that.intMarkerPosition) == 0;
806                 result =
807                         result &&
808                                 Double.compare(desiredMarkerPosition,
809                                         that.desiredMarkerPosition) == 0;
810                 result =
811                         result &&
812                                 Double.compare(desiredMarkerIncrement,
813                                         that.desiredMarkerIncrement) == 0;
814 
815                 result = result && next.index == that.next.index;
816                 result = result && previous.index == that.previous.index;
817             }
818             return result;
819         }
820 
821         /** {@inheritDoc} */
822         @Override
823         public int hashCode() {
824             return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition,
825                 desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index});
826         }
827 
828         /**
829          * Read Object to deserialize.
830          *
831          * @param anInstream Stream Object data
832          * @throws IOException thrown for IO Errors
833          * @throws ClassNotFoundException thrown for class not being found
834          */
835         private void readObject(ObjectInputStream anInstream)
836                 throws ClassNotFoundException, IOException {
837             anInstream.defaultReadObject();
838             previous=next=this;
839             linear = new LinearInterpolator();
840         }
841 
842         /** Copy this instance.
843          * @return copy of the instance
844          */
845         public Marker copySelf() {
846             return new Marker(markerHeight, desiredMarkerPosition, desiredMarkerIncrement, intMarkerPosition);
847         }
848 
849         /**
850          * {@inheritDoc}
851          */
852         @Override
853         public String toString() {
854             return String.format(
855                     "index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d",
856                     (double) index, Precision.round(intMarkerPosition, 0),
857                     Precision.round(desiredMarkerPosition, 2),
858                     Precision.round(markerHeight, 2),
859                     Precision.round(desiredMarkerIncrement, 2), previous.index,
860                     next.index);
861         }
862     }
863 
864     /**
865      * A simple fixed capacity list that has an upper bound to growth.
866      * Once its capacity is reached, {@code add} is a no-op, returning
867      * {@code false}.
868      *
869      * @param <E> type of the elements
870      */
871     private static class FixedCapacityList<E> extends ArrayList<E> implements Serializable {
872 
873         /**
874          * Serialization Version Id
875          */
876         private static final long serialVersionUID = 2283952083075725479L;
877         /**
878          * Capacity of the list
879          */
880         private final int capacity;
881 
882         /**
883          * This constructor constructs the list with given capacity and as well
884          * as stores the capacity
885          *
886          * @param fixedCapacity the capacity to be fixed for this list
887          */
888         FixedCapacityList(final int fixedCapacity) {
889             super(fixedCapacity);
890             this.capacity = fixedCapacity;
891         }
892 
893         /**
894          * {@inheritDoc} In addition it checks if the {@link #size()} returns a
895          * size that is within capacity and if true it adds; otherwise the list
896          * contents are unchanged and {@code false} is returned.
897          *
898          * @return true if addition is successful and false otherwise
899          */
900         @Override
901         public boolean add(final E e) {
902             return size() < capacity && super.add(e);
903         }
904 
905         /**
906          * {@inheritDoc} In addition it checks if the sum of Collection size and
907          * this instance's {@link #size()} returns a value that is within
908          * capacity and if true it adds the collection; otherwise the list
909          * contents are unchanged and {@code false} is returned.
910          *
911          * @return true if addition is successful and false otherwise
912          */
913         @Override
914         public boolean addAll(Collection<? extends E> collection) {
915             boolean isCollectionLess =
916                     collection != null &&
917                             collection.size() + size() <= capacity;
918             return isCollectionLess && super.addAll(collection);
919         }
920 
921         /** {@inheritDoc} */
922         @Override
923         public boolean equals(final Object other) {
924             return super.equals(other) && capacity == ((FixedCapacityList<?>) other).capacity;
925         }
926 
927         /** {@inheritDoc} */
928         @Override
929         public int hashCode() {
930             return super.hashCode() + capacity;
931         }
932 
933     }
934 
935     /**
936      * A creation method to build Markers
937      *
938      * @param initialFive list of initial five elements
939      * @param p the quantile desired
940      * @return an instance of PSquareMarkers
941      */
942     public static PSquareMarkers newMarkers(final List<Double> initialFive, final double p) {
943         return new Markers(initialFive, p);
944     }
945 
946     /**
947      * An interface that encapsulates abstractions of the
948      * P-square algorithm markers as is explained in the original works. This
949      * interface is exposed with protected access to help in testability.
950      */
951     protected interface PSquareMarkers {
952         /**
953          * Returns Percentile value computed thus far.
954          *
955          * @return percentile
956          */
957         double getPercentileValue();
958 
959         /**
960          * A deep copy function to clone the current instance.
961          *
962          * @return deep copy of this instance
963          */
964         PSquareMarkers copySelf();
965 
966         /**
967          * Returns the marker height (or percentile) of a given marker index.
968          *
969          * @param markerIndex is the index of marker in the marker array
970          * @return percentile value of the marker index passed
971          * @throws MathIllegalArgumentException in case the index is not within [1-5]
972          */
973         double height(int markerIndex);
974 
975         /**
976          * Process a data point by moving the marker heights based on estimator.
977          *
978          * @param inputDataPoint is the data point passed
979          * @return computed percentile
980          */
981         double processDataPoint(double inputDataPoint);
982 
983         /**
984          * An Estimate of the percentile value of a given Marker
985          *
986          * @param index the marker's index in the array of markers
987          * @return percentile estimate
988          * @throws MathIllegalArgumentException in case if index is not within [1-5]
989          */
990         double estimate(int index);
991     }
992 }