PSquarePercentile.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.stat.descriptive.rank;

  22. import java.io.IOException;
  23. import java.io.ObjectInputStream;
  24. import java.io.Serializable;
  25. import java.text.DecimalFormat;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.Collection;
  29. import java.util.Collections;
  30. import java.util.List;

  31. import org.hipparchus.analysis.UnivariateFunction;
  32. import org.hipparchus.analysis.interpolation.LinearInterpolator;
  33. import org.hipparchus.analysis.interpolation.NevilleInterpolator;
  34. import org.hipparchus.analysis.interpolation.UnivariateInterpolator;
  35. import org.hipparchus.exception.LocalizedCoreFormats;
  36. import org.hipparchus.exception.MathIllegalArgumentException;
  37. import org.hipparchus.stat.descriptive.AbstractStorelessUnivariateStatistic;
  38. import org.hipparchus.stat.descriptive.StorelessUnivariateStatistic;
  39. import org.hipparchus.util.MathArrays;
  40. import org.hipparchus.util.MathUtils;
  41. import org.hipparchus.util.Precision;

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

  57.     /** The maximum array size used for psquare algorithm */
  58.     private static final int PSQUARE_CONSTANT = 5;

  59.     /**
  60.      * A Default quantile needed in case if user prefers to use default no
  61.      * argument constructor.
  62.      */
  63.     private static final double DEFAULT_QUANTILE_DESIRED = 50d;

  64.     /** Serial ID */
  65.     private static final long serialVersionUID = 20150412L;

  66.     /** A decimal formatter for print convenience */
  67.     private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("00.00");

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

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

  79.     /**
  80.      * lastObservation is the last observation value/input sample. No need to
  81.      * serialize.
  82.      */
  83.     private transient double lastObservation;

  84.     /**
  85.      * Markers is the marker collection object which comes to effect
  86.      * only after 5 values are inserted
  87.      */
  88.     private PSquareMarkers markers;

  89.     /**
  90.      * Computed p value (i,e percentile value of data set hither to received)
  91.      */
  92.     private double pValue = Double.NaN;

  93.     /**
  94.      * Counter to count the values/observations accepted into this data set
  95.      */
  96.     private long countOfObservations;

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

  110.     /**
  111.      * Default constructor that assumes a {@link #DEFAULT_QUANTILE_DESIRED
  112.      * default quantile} needed.
  113.      */
  114.     PSquarePercentile() {
  115.         this(DEFAULT_QUANTILE_DESIRED);
  116.     }

  117.     /**
  118.      * Copy constructor, creates a new {@code PSquarePercentile} identical
  119.      * to the {@code original}.
  120.      *
  121.      * @param original the {@code PSquarePercentile} instance to copy
  122.      * @throws org.hipparchus.exception.NullArgumentException if original is null
  123.      */
  124.     public PSquarePercentile(PSquarePercentile original) {
  125.         super();

  126.         this.quantile = original.quantile;

  127.         if (original.markers != null) {
  128.             this.markers = original.markers.copySelf();
  129.         }

  130.         this.countOfObservations = original.countOfObservations;
  131.         this.pValue = original.pValue;
  132.         this.initialFive.addAll(original.initialFive);
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public int hashCode() {
  137.         double result = getResult();
  138.         result = Double.isNaN(result) ? 37 : result;
  139.         final double markersHash = markers == null ? 0 : markers.hashCode();
  140.         final double[] toHash = {result, quantile, markersHash, countOfObservations};
  141.         return Arrays.hashCode(toHash);
  142.     }

  143.     /**
  144.      * Returns true iff {@code o} is a {@code PSquarePercentile} returning the
  145.      * same values as this for {@code getResult()} and {@code getN()} and also
  146.      * having equal markers
  147.      *
  148.      * @param o object to compare
  149.      * @return true if {@code o} is a {@code PSquarePercentile} with
  150.      * equivalent internal state
  151.      */
  152.     @Override
  153.     public boolean equals(Object o) {
  154.         boolean result = false;
  155.         if (this == o) {
  156.             result = true;
  157.         } else if (o instanceof PSquarePercentile) {
  158.             PSquarePercentile that = (PSquarePercentile) o;
  159.             boolean isNotNull = markers != null && that.markers != null;
  160.             boolean isNull = markers == null && that.markers == null;
  161.             result = isNotNull ? markers.equals(that.markers) : isNull;
  162.             // markers as in the case of first
  163.             // five observations
  164.             result = result && getN() == that.getN();
  165.         }
  166.         return result;
  167.     }

  168.     /**
  169.      * {@inheritDoc}The internal state updated due to the new value in this
  170.      * context is basically of the marker positions and computation of the
  171.      * approximate quantile.
  172.      *
  173.      * @param observation the observation currently being added.
  174.      */
  175.     @Override
  176.     public void increment(final double observation) {
  177.         // Increment counter
  178.         countOfObservations++;

  179.         // Store last observation
  180.         this.lastObservation = observation;

  181.         // 0. Use Brute force for <5
  182.         if (markers == null) {
  183.             if (initialFive.add(observation)) {
  184.                 Collections.sort(initialFive);
  185.                 pValue =
  186.                         initialFive
  187.                                 .get((int) (quantile * (initialFive.size() - 1)));
  188.                 return;
  189.             }
  190.             // 1. Initialize once after 5th observation
  191.             markers = newMarkers(initialFive, quantile);
  192.         }
  193.         // 2. process a Data Point and return pValue
  194.         pValue = markers.processDataPoint(observation);
  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.         synchronized (this) {
  205.             synchronized (DECIMAL_FORMAT) {
  206.                 if (markers == null) {
  207.                     return String.format("obs=%s pValue=%s",
  208.                                          DECIMAL_FORMAT.format(lastObservation),
  209.                                          DECIMAL_FORMAT.format(pValue));
  210.                 } else {
  211.                     return String.format("obs=%s markers=%s",
  212.                                          DECIMAL_FORMAT.format(lastObservation), markers.toString());
  213.                 }
  214.             }
  215.         }
  216.    }

  217.     /** {@inheritDoc} */
  218.     @Override
  219.     public long getN() {
  220.         return countOfObservations;
  221.     }

  222.     /** {@inheritDoc} */
  223.     @Override
  224.     public PSquarePercentile copy() {
  225.         return new PSquarePercentile(this);
  226.     }

  227.     /**
  228.      * Returns the quantile estimated by this statistic in the range [0.0-1.0]
  229.      *
  230.      * @return quantile estimated by {@link #getResult()}
  231.      */
  232.     public double quantile() {
  233.         return quantile;
  234.     }

  235.     /**
  236.      * {@inheritDoc}. This basically clears all the markers, the
  237.      * initialFive list and sets countOfObservations to 0.
  238.      */
  239.     @Override
  240.     public void clear() {
  241.         markers = null;
  242.         initialFive.clear();
  243.         countOfObservations = 0L;
  244.         pValue = Double.NaN;
  245.     }

  246.     /**
  247.      * {@inheritDoc}
  248.      */
  249.     @Override
  250.     public double getResult() {
  251.         if (Double.compare(quantile, 1d) == 0) {
  252.             pValue = maximum();
  253.         } else if (Double.compare(quantile, 0d) == 0) {
  254.             pValue = minimum();
  255.         }
  256.         return pValue;
  257.     }

  258.     /** Get quantile estimated by this statistic.
  259.      * @return the quantile estimated by this statistic
  260.      */
  261.     public double getQuantile() {
  262.         return quantile;
  263.     }

  264.     /** Get maximum in the data set added to this statistic.
  265.      * @return maximum in the data set added to this statistic
  266.      */
  267.     private double maximum() {
  268.         double val = Double.NaN;
  269.         if (markers != null) {
  270.             val = markers.height(PSQUARE_CONSTANT);
  271.         } else if (!initialFive.isEmpty()) {
  272.             val = initialFive.get(initialFive.size() - 1);
  273.         }
  274.         return val;
  275.     }

  276.     /** Get minimum in the data set added to this statistic.
  277.      * @return minimum in the data set added to this statistic
  278.      */
  279.     private double minimum() {
  280.         double val = Double.NaN;
  281.         if (markers != null) {
  282.             val = markers.height(1);
  283.         } else if (!initialFive.isEmpty()) {
  284.             val = initialFive.get(0);
  285.         }
  286.         return val;
  287.     }

  288.     /**
  289.      * Markers is an encapsulation of the five markers/buckets as indicated in
  290.      * the original works.
  291.      */
  292.     private static class Markers implements PSquareMarkers, Serializable {
  293.         /**
  294.          * Serial version id
  295.          */
  296.         private static final long serialVersionUID = 1L;

  297.         /** Low marker index */
  298.         private static final int LOW = 2;

  299.         /** High marker index */
  300.         private static final int HIGH = 4;

  301.         /**
  302.          * Array of 5+1 Markers (The first marker is dummy just so we
  303.          * can match the rest of indexes [1-5] indicated in the original works
  304.          * which follows unit based index)
  305.          */
  306.         private final Marker[] markerArray;

  307.         /**
  308.          * Constructor
  309.          *
  310.          * @param theMarkerArray marker array to be used, a reference to the array will be stored
  311.          */
  312.         private Markers(final Marker[] theMarkerArray) { // NOPMD - storing a reference to the array is intentional and documented here
  313.             MathUtils.checkNotNull(theMarkerArray);
  314.             markerArray = theMarkerArray;
  315.             for (int i = 1; i < PSQUARE_CONSTANT; i++) {
  316.                 markerArray[i].previous(markerArray[i - 1])
  317.                         .next(markerArray[i + 1]).index(i);
  318.             }
  319.             markerArray[0].previous(markerArray[0])
  320.                           .next(markerArray[1])
  321.                           .index(0);
  322.             markerArray[5].previous(markerArray[4])
  323.                           .next(markerArray[5])
  324.                           .index(5);
  325.         }

  326.         /**
  327.          * Constructor
  328.          *
  329.          * @param initialFive elements required to build Marker
  330.          * @param p quantile required to be computed
  331.          */
  332.         private Markers(final List<Double> initialFive, final double p) {
  333.             this(createMarkerArray(initialFive, p));
  334.         }

  335.         /**
  336.          * Creates a marker array using initial five elements and a quantile
  337.          *
  338.          * @param initialFive list of initial five elements
  339.          * @param p the pth quantile
  340.          * @return Marker array
  341.          */
  342.         private static Marker[] createMarkerArray(
  343.                 final List<Double> initialFive, final double p) {
  344.             final int countObserved =
  345.                     initialFive == null ? -1 : initialFive.size();
  346.             if (countObserved < PSQUARE_CONSTANT) {
  347.                 throw new MathIllegalArgumentException(
  348.                         LocalizedCoreFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
  349.                         countObserved, PSQUARE_CONSTANT);
  350.             }
  351.             Collections.sort(initialFive);
  352.             return new Marker[] {
  353.                     new Marker(),// Null Marker
  354.                     new Marker(initialFive.get(0), 1, 0, 1),
  355.                     new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2),
  356.                     new Marker(initialFive.get(2), 1 + 4 * p, p, 3),
  357.                     new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4),
  358.                     new Marker(initialFive.get(4), 5, 1, 5) };
  359.         }

  360.         /**
  361.          * {@inheritDoc}
  362.          */
  363.         @Override
  364.         public int hashCode() {
  365.             return Arrays.deepHashCode(markerArray);
  366.         }

  367.         /**
  368.          * {@inheritDoc}.This equals method basically checks for marker array to
  369.          * be deep equals.
  370.          *
  371.          * @param o is the other object
  372.          * @return true if the object compares with this object are equivalent
  373.          */
  374.         @Override
  375.         public boolean equals(Object o) {
  376.             boolean result = false;
  377.             if (this == o) {
  378.                 result = true;
  379.             } else if (o instanceof Markers) {
  380.                 Markers that = (Markers) o;
  381.                 result = Arrays.deepEquals(markerArray, that.markerArray);
  382.             }
  383.             return result;
  384.         }

  385.         /**
  386.          * Process a data point
  387.          *
  388.          * @param inputDataPoint is the data point passed
  389.          * @return computed percentile
  390.          */
  391.         @Override
  392.         public double processDataPoint(final double inputDataPoint) {

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

  395.             // 2. Increment positions
  396.             incrementPositions(1, kthCell + 1, 5);

  397.             // 2a. Update desired position with increments
  398.             updateDesiredPositions();

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

  401.             // 4. Return percentile
  402.             return getPercentileValue();
  403.         }

  404.         /**
  405.          * Returns the percentile computed thus far.
  406.          *
  407.          * @return height of mid point marker
  408.          */
  409.         @Override
  410.         public double getPercentileValue() {
  411.             return height(3);
  412.         }

  413.         /**
  414.          * Finds the cell where the input observation / value fits.
  415.          *
  416.          * @param observation the input value to be checked for
  417.          * @return kth cell (of the markers ranging from 1-5) where observed
  418.          *         sample fits
  419.          */
  420.         private int findCellAndUpdateMinMax(final double observation) {
  421.             if (observation < height(1)) {
  422.                 markerArray[1].markerHeight = observation;
  423.                 return 1;
  424.             } else if (observation < height(2)) {
  425.                 return 1;
  426.             } else if (observation < height(3)) {
  427.                 return 2;
  428.             } else if (observation < height(4)) {
  429.                 return 3;
  430.             } else if (observation <= height(5)) {
  431.                 return 4;
  432.             } else {
  433.                 markerArray[5].markerHeight = observation;
  434.                 return 4;
  435.             }
  436.         }

  437.         /**
  438.          * Adjust marker heights by setting quantile estimates to middle markers.
  439.          */
  440.         private void adjustHeightsOfMarkers() {
  441.             for (int i = LOW; i <= HIGH; i++) {
  442.                 estimate(i);
  443.             }
  444.         }

  445.         /**
  446.          * {@inheritDoc}
  447.          */
  448.         @Override
  449.         public double estimate(final int index) {
  450.             MathUtils.checkRangeInclusive(index, LOW, HIGH);
  451.             return markerArray[index].estimate();
  452.         }

  453.         /**
  454.          * Increment positions by d. Refer to algorithm paper for the
  455.          * definition of d.
  456.          *
  457.          * @param d The increment value for the position
  458.          * @param startIndex start index of the marker array
  459.          * @param endIndex end index of the marker array
  460.          */
  461.         private void incrementPositions(final int d, final int startIndex,
  462.                 final int endIndex) {
  463.             for (int i = startIndex; i <= endIndex; i++) {
  464.                 markerArray[i].incrementPosition(d);
  465.             }
  466.         }

  467.         /**
  468.          * Desired positions incremented by bucket width. The bucket width is
  469.          * basically the desired increments.
  470.          */
  471.         private void updateDesiredPositions() {
  472.             for (int i = 1; i < markerArray.length; i++) {
  473.                 markerArray[i].updateDesiredPosition();
  474.             }
  475.         }

  476.         /**
  477.          * Sets previous and next markers after default read is done.
  478.          *
  479.          * @param anInputStream the input stream to be deserialized
  480.          * @throws ClassNotFoundException thrown when a desired class not found
  481.          * @throws IOException thrown due to any io errors
  482.          */
  483.         private void readObject(ObjectInputStream anInputStream)
  484.                 throws ClassNotFoundException, IOException {
  485.             // always perform the default de-serialization first
  486.             anInputStream.defaultReadObject();
  487.             // Build links
  488.             for (int i = 1; i < PSQUARE_CONSTANT; i++) {
  489.                 markerArray[i].previous(markerArray[i - 1]).next(markerArray[i + 1]).index(i);
  490.             }
  491.             markerArray[0].previous(markerArray[0]).next(markerArray[1]).index(0);
  492.             markerArray[5].previous(markerArray[4]).next(markerArray[5]).index(5);
  493.         }

  494.         /**
  495.          * Return marker height given index
  496.          *
  497.          * @param markerIndex index of marker within (1,6)
  498.          * @return marker height
  499.          */
  500.         @Override
  501.         public double height(final int markerIndex) {
  502.             MathUtils.checkRangeInclusive(markerIndex, 1, markerArray.length - 1);
  503.             return markerArray[markerIndex].markerHeight;
  504.         }

  505.         /** {@inheritDoc} */
  506.         @Override
  507.         public Markers copySelf() {
  508.             return new Markers(new Marker[] {
  509.                 new Marker(),
  510.                 markerArray[1].copySelf(),
  511.                 markerArray[2].copySelf(),
  512.                 markerArray[3].copySelf(),
  513.                 markerArray[4].copySelf(),
  514.                 markerArray[5].copySelf()
  515.             });

  516.         }

  517.         /**
  518.          * Returns string representation of the Marker array.
  519.          *
  520.          * @return Markers as a string
  521.          */
  522.         @Override
  523.         public String toString() {
  524.             return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]",
  525.                     markerArray[1].toString(), markerArray[2].toString(),
  526.                     markerArray[3].toString(), markerArray[4].toString(),
  527.                     markerArray[5].toString());
  528.         }

  529.     }

  530.     /**
  531.      * The class modeling the attributes of the marker of the P-square algorithm
  532.      */
  533.     private static class Marker implements Serializable {

  534.         /**
  535.          * Serial Version ID
  536.          */
  537.         private static final long serialVersionUID = -3575879478288538431L;

  538.         /**
  539.          * The marker index which is just a serial number for the marker in the
  540.          * marker array of 5+1.
  541.          */
  542.         private int index;

  543.         /**
  544.          * The integral marker position. Refer to the variable n in the original
  545.          * works.
  546.          */
  547.         private double intMarkerPosition;

  548.         /**
  549.          * Desired marker position. Refer to the variable n' in the original
  550.          * works.
  551.          */
  552.         private double desiredMarkerPosition;

  553.         /**
  554.          * Marker height or the quantile. Refer to the variable q in the
  555.          * original works.
  556.          */
  557.         private double markerHeight;

  558.         /**
  559.          * Desired marker increment. Refer to the variable dn' in the original
  560.          * works.
  561.          */
  562.         private double desiredMarkerIncrement;

  563.         /**
  564.          * Next and previous markers for easy linked navigation in loops. this
  565.          * is not serialized as they can be rebuilt during deserialization.
  566.          */
  567.         private transient Marker next;

  568.         /**
  569.          * The previous marker links
  570.          */
  571.         private transient Marker previous;

  572.         /**
  573.          * Nonlinear interpolator
  574.          */
  575.         private final UnivariateInterpolator nonLinear = new NevilleInterpolator();

  576.         /**
  577.          * Linear interpolator which is not serializable
  578.          */
  579.         private transient UnivariateInterpolator linear = new LinearInterpolator();

  580.         /**
  581.          * Default constructor
  582.          */
  583.         private Marker() {
  584.             this.next = this.previous = this;
  585.         }

  586.         /**
  587.          * Constructor of the marker with parameters
  588.          *
  589.          * @param heightOfMarker represent the quantile value
  590.          * @param makerPositionDesired represent the desired marker position
  591.          * @param markerPositionIncrement represent increments for position
  592.          * @param markerPositionNumber represent the position number of marker
  593.          */
  594.         private Marker(double heightOfMarker, double makerPositionDesired,
  595.                 double markerPositionIncrement, double markerPositionNumber) {
  596.             this();
  597.             this.markerHeight = heightOfMarker;
  598.             this.desiredMarkerPosition = makerPositionDesired;
  599.             this.desiredMarkerIncrement = markerPositionIncrement;
  600.             this.intMarkerPosition = markerPositionNumber;
  601.         }

  602.         /**
  603.          * Sets the previous marker.
  604.          *
  605.          * @param previousMarker the previous marker to the current marker in
  606.          *            the array of markers
  607.          * @return this instance
  608.          */
  609.         private Marker previous(final Marker previousMarker) {
  610.             MathUtils.checkNotNull(previousMarker);
  611.             this.previous = previousMarker;
  612.             return this;
  613.         }

  614.         /**
  615.          * Sets the next marker.
  616.          *
  617.          * @param nextMarker the next marker to the current marker in the array
  618.          *            of markers
  619.          * @return this instance
  620.          */
  621.         private Marker next(final Marker nextMarker) {
  622.             MathUtils.checkNotNull(nextMarker);
  623.             this.next = nextMarker;
  624.             return this;
  625.         }

  626.         /**
  627.          * Sets the index of the marker.
  628.          *
  629.          * @param indexOfMarker the array index of the marker in marker array
  630.          * @return this instance
  631.          */
  632.         private Marker index(final int indexOfMarker) {
  633.             this.index = indexOfMarker;
  634.             return this;
  635.         }

  636.         /**
  637.          * Update desired Position with increment.
  638.          */
  639.         private void updateDesiredPosition() {
  640.             desiredMarkerPosition += desiredMarkerIncrement;
  641.         }

  642.         /**
  643.          * Increment Position by d.
  644.          *
  645.          * @param d a delta value to increment
  646.          */
  647.         private void incrementPosition(final int d) {
  648.             intMarkerPosition += d;
  649.         }

  650.         /**
  651.          * Difference between desired and actual position
  652.          *
  653.          * @return difference between desired and actual position
  654.          */
  655.         private double difference() {
  656.             return desiredMarkerPosition - intMarkerPosition;
  657.         }

  658.         /**
  659.          * Estimate the quantile for the current marker.
  660.          *
  661.          * @return estimated quantile
  662.          */
  663.         private double estimate() {
  664.             final double di = difference();
  665.             final boolean isNextHigher =
  666.                     next.intMarkerPosition - intMarkerPosition > 1;
  667.             final boolean isPreviousLower =
  668.                     previous.intMarkerPosition - intMarkerPosition < -1;

  669.             if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) {
  670.                 final int d = di >= 0 ? 1 : -1;
  671.                 final double[] xval = { previous.intMarkerPosition, intMarkerPosition, next.intMarkerPosition };
  672.                 final double[] yval = { previous.markerHeight, markerHeight, next.markerHeight };
  673.                 final double xD = intMarkerPosition + d;

  674.                 UnivariateFunction univariateFunction =
  675.                         nonLinear.interpolate(xval, yval);
  676.                 markerHeight = univariateFunction.value(xD);

  677.                 // If parabolic estimate is bad then turn linear
  678.                 if (isEstimateBad(yval, markerHeight)) {
  679.                     int delta = xD - xval[1] > 0 ? 1 : -1;
  680.                     final double[] xBad = { xval[1], xval[1 + delta] };
  681.                     final double[] yBad = { yval[1], yval[1 + delta] };
  682.                     MathArrays.sortInPlace(xBad, yBad);// since d can be +/- 1
  683.                     univariateFunction = linear.interpolate(xBad, yBad);
  684.                     markerHeight = univariateFunction.value(xD);
  685.                 }
  686.                 incrementPosition(d);
  687.             }
  688.             return markerHeight;
  689.         }

  690.         /**
  691.          * Check if parabolic/nonlinear estimate is bad by checking if the
  692.          * ordinate found is beyond the y[0] and y[2].
  693.          *
  694.          * @param y the array to get the bounds
  695.          * @param yD the estimate
  696.          * @return true if yD is a bad estimate
  697.          */
  698.         private boolean isEstimateBad(final double[] y, final double yD) {
  699.             return yD <= y[0] || yD >= y[2];
  700.         }

  701.         /**
  702.          * {@inheritDoc}<i>This equals method checks for marker attributes and
  703.          * as well checks if navigation pointers (next and previous) are the same
  704.          * between this and passed in object</i>
  705.          *
  706.          * @param o Other object
  707.          * @return true if this equals passed in other object o
  708.          */
  709.         @Override
  710.         public boolean equals(Object o) {
  711.             boolean result = false;
  712.             if (this == o) {
  713.                 result = true;
  714.             } else if (o instanceof Marker) {
  715.                 Marker that = (Marker) o;

  716.                 result = Double.compare(markerHeight, that.markerHeight) == 0;
  717.                 result =
  718.                         result &&
  719.                                 Double.compare(intMarkerPosition,
  720.                                         that.intMarkerPosition) == 0;
  721.                 result =
  722.                         result &&
  723.                                 Double.compare(desiredMarkerPosition,
  724.                                         that.desiredMarkerPosition) == 0;
  725.                 result =
  726.                         result &&
  727.                                 Double.compare(desiredMarkerIncrement,
  728.                                         that.desiredMarkerIncrement) == 0;

  729.                 result = result && next.index == that.next.index;
  730.                 result = result && previous.index == that.previous.index;
  731.             }
  732.             return result;
  733.         }

  734.         /** {@inheritDoc} */
  735.         @Override
  736.         public int hashCode() {
  737.             return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition,
  738.                 desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index});
  739.         }

  740.         /**
  741.          * Read Object to deserialize.
  742.          *
  743.          * @param anInstream Stream Object data
  744.          * @throws IOException thrown for IO Errors
  745.          * @throws ClassNotFoundException thrown for class not being found
  746.          */
  747.         private void readObject(ObjectInputStream anInstream)
  748.                 throws ClassNotFoundException, IOException {
  749.             anInstream.defaultReadObject();
  750.             previous=next=this;
  751.             linear = new LinearInterpolator();
  752.         }

  753.         /** Copy this instance.
  754.          * @return copy of the instance
  755.          */
  756.         public Marker copySelf() {
  757.             return new Marker(markerHeight, desiredMarkerPosition, desiredMarkerIncrement, intMarkerPosition);
  758.         }

  759.         /**
  760.          * {@inheritDoc}
  761.          */
  762.         @Override
  763.         public String toString() {
  764.             return String.format(
  765.                     "index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d",
  766.                     (double) index, Precision.round(intMarkerPosition, 0),
  767.                     Precision.round(desiredMarkerPosition, 2),
  768.                     Precision.round(markerHeight, 2),
  769.                     Precision.round(desiredMarkerIncrement, 2), previous.index,
  770.                     next.index);
  771.         }
  772.     }

  773.     /**
  774.      * A simple fixed capacity list that has an upper bound to growth.
  775.      * Once its capacity is reached, {@code add} is a no-op, returning
  776.      * {@code false}.
  777.      *
  778.      * @param <E> type of the elements
  779.      */
  780.     private static class FixedCapacityList<E> extends ArrayList<E> implements Serializable {

  781.         /**
  782.          * Serialization Version Id
  783.          */
  784.         private static final long serialVersionUID = 2283952083075725479L;
  785.         /**
  786.          * Capacity of the list
  787.          */
  788.         private final int capacity;

  789.         /**
  790.          * This constructor constructs the list with given capacity and as well
  791.          * as stores the capacity
  792.          *
  793.          * @param fixedCapacity the capacity to be fixed for this list
  794.          */
  795.         FixedCapacityList(final int fixedCapacity) {
  796.             super(fixedCapacity);
  797.             this.capacity = fixedCapacity;
  798.         }

  799.         /**
  800.          * {@inheritDoc} In addition it checks if the {@link #size()} returns a
  801.          * size that is within capacity and if true it adds; otherwise the list
  802.          * contents are unchanged and {@code false} is returned.
  803.          *
  804.          * @return true if addition is successful and false otherwise
  805.          */
  806.         @Override
  807.         public boolean add(final E e) {
  808.             return size() < capacity && super.add(e);
  809.         }

  810.         /**
  811.          * {@inheritDoc} In addition it checks if the sum of Collection size and
  812.          * this instance's {@link #size()} returns a value that is within
  813.          * capacity and if true it adds the collection; otherwise the list
  814.          * contents are unchanged and {@code false} is returned.
  815.          *
  816.          * @return true if addition is successful and false otherwise
  817.          */
  818.         @Override
  819.         public boolean addAll(Collection<? extends E> collection) {
  820.             boolean isCollectionLess =
  821.                     collection != null &&
  822.                             collection.size() + size() <= capacity;
  823.             return isCollectionLess && super.addAll(collection);
  824.         }

  825.         /** {@inheritDoc} */
  826.         @Override
  827.         public boolean equals(final Object other) {
  828.             return super.equals(other) && capacity == ((FixedCapacityList<?>) other).capacity;
  829.         }

  830.         /** {@inheritDoc} */
  831.         @Override
  832.         public int hashCode() {
  833.             return super.hashCode() + capacity;
  834.         }

  835.     }

  836.     /**
  837.      * A creation method to build Markers
  838.      *
  839.      * @param initialFive list of initial five elements
  840.      * @param p the quantile desired
  841.      * @return an instance of PSquareMarkers
  842.      */
  843.     public static PSquareMarkers newMarkers(final List<Double> initialFive, final double p) {
  844.         return new Markers(initialFive, p);
  845.     }

  846.     /**
  847.      * An interface that encapsulates abstractions of the
  848.      * P-square algorithm markers as is explained in the original works. This
  849.      * interface is exposed with protected access to help in testability.
  850.      */
  851.     protected interface PSquareMarkers {
  852.         /**
  853.          * Returns Percentile value computed thus far.
  854.          *
  855.          * @return percentile
  856.          */
  857.         double getPercentileValue();

  858.         /**
  859.          * A deep copy function to clone the current instance.
  860.          *
  861.          * @return deep copy of this instance
  862.          */
  863.         PSquareMarkers copySelf();

  864.         /**
  865.          * Returns the marker height (or percentile) of a given marker index.
  866.          *
  867.          * @param markerIndex is the index of marker in the marker array
  868.          * @return percentile value of the marker index passed
  869.          * @throws MathIllegalArgumentException in case the index is not within [1-5]
  870.          */
  871.         double height(int markerIndex);

  872.         /**
  873.          * Process a data point by moving the marker heights based on estimator.
  874.          *
  875.          * @param inputDataPoint is the data point passed
  876.          * @return computed percentile
  877.          */
  878.         double processDataPoint(double inputDataPoint);

  879.         /**
  880.          * An Estimate of the percentile value of a given Marker
  881.          *
  882.          * @param index the marker's index in the array of markers
  883.          * @return percentile estimate
  884.          * @throws MathIllegalArgumentException in case if index is not within [1-5]
  885.          */
  886.         double estimate(int index);
  887.     }
  888. }