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 }