    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.math3.stat.descriptive;
    018
    019    import org.apache.commons.math3.exception.DimensionMismatchException;
    020    import org.apache.commons.math3.exception.NotPositiveException;
    021    import org.apache.commons.math3.exception.NullArgumentException;
    022    import org.apache.commons.math3.exception.NumberIsTooLargeException;
    023    import org.apache.commons.math3.exception.MathIllegalArgumentException;
    024    import org.apache.commons.math3.exception.util.LocalizedFormats;
    025
    026    /**
    027     * Abstract base class for all implementations of the
    028     * {@link UnivariateStatistic} interface.
    029     * <p>
    030     * Provides a default implementation of <code>evaluate(double[]),</code>
    031     * delegating to <code>evaluate(double[], int, int)</code> in the natural way.
    032     * </p>
    033     * <p>
    034     * Also includes a <code>test</code> method that performs generic parameter
    035     * validation for the <code>evaluate</code> methods.</p>
    036     *
    037     * @version $Id: AbstractUnivariateStatistic.java 1416643 2012-12-03 19:37:14Z tn $
    038     */
    039    public abstract class AbstractUnivariateStatistic
    040        implements UnivariateStatistic {
    041
    042        /** Stored data. */
    043        private double[] storedData;
    044
    045        /**
    046         * Set the data array.
    047         * <p>
    048         * The stored value is a copy of the parameter array, not the array itself.
    049         * </p>
    050         * @param values data array to store (may be null to remove stored data)
    051         * @see #evaluate()
    052         */
    053        public void setData(final double[] values) {
    054            storedData = (values == null) ? null : values.clone();
    055        }
    056
    057        /**
    058         * Get a copy of the stored data array.
    059         * @return copy of the stored data array (may be null)
    060         */
    061        public double[] getData() {
    062            return (storedData == null) ? null : storedData.clone();
    063        }
    064
    065        /**
    066         * Get a reference to the stored data array.
    067         * @return reference to the stored data array (may be null)
    068         */
    069        protected double[] getDataRef() {
    070            return storedData;
    071        }
    072
    073        /**
    074         * Set the data array.  The input array is copied, not referenced.
    075         *
    076         * @param values data array to store
    077         * @param begin the index of the first element to include
    078         * @param length the number of elements to include
    079         * @throws MathIllegalArgumentException if values is null or the indices
    080         * are not valid
    081         * @see #evaluate()
    082         */
    083        public void setData(final double[] values, final int begin, final int length)
    084        throws MathIllegalArgumentException {
    085            if (values == null) {
    086                throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
    087            }
    088
    089            if (begin < 0) {
    090                throw new NotPositiveException(LocalizedFormats.START_POSITION, begin);
    091            }
    092
    093            if (length < 0) {
    094                throw new NotPositiveException(LocalizedFormats.LENGTH, length);
    095            }
    096
    097            if (begin + length > values.length) {
    098                throw new NumberIsTooLargeException(LocalizedFormats.SUBARRAY_ENDS_AFTER_ARRAY_END,
    099                                                    begin + length, values.length, true);
    100            }
    101            storedData = new double[length];
    102            System.arraycopy(values, begin, storedData, 0, length);
    103        }
    104
    105        /**
    106         * Returns the result of evaluating the statistic over the stored data.
    107         * <p>
    108         * The stored array is the one which was set by previous calls to {@link #setData(double[])}.
    109         * </p>
    110         * @return the value of the statistic applied to the stored data
    111         * @throws MathIllegalArgumentException if the stored data array is null
    112         */
    113        public double evaluate() throws MathIllegalArgumentException {
    114            return evaluate(storedData);
    115        }
    116
    117        /**
    118         * {@inheritDoc}
    119         */
    120        public double evaluate(final double[] values) throws MathIllegalArgumentException {
    121            test(values, 0, 0);
    122            return evaluate(values, 0, values.length);
    123        }
    124
    125        /**
    126         * {@inheritDoc}
    127         */
    128        public abstract double evaluate(final double[] values, final int begin, final int length)
    129        throws MathIllegalArgumentException;
    130
    131        /**
    132         * {@inheritDoc}
    133         */
    134        public abstract UnivariateStatistic copy();
    135
    136        /**
    137         * This method is used by <code>evaluate(double[], int, int)</code> methods
    138         * to verify that the input parameters designate a subarray of positive length.
    139         * <p>
    140         * <ul>
    141         * <li>returns <code>true</code> iff the parameters designate a subarray of
    142         * positive length</li>
    143         * <li>throws <code>MathIllegalArgumentException</code> if the array is null or
    144         * or the indices are invalid</li>
    145         * <li>returns <code>false</li> if the array is non-null, but
    146         * <code>length</code> is 0.
    147         * </ul></p>
    148         *
    149         * @param values the input array
    150         * @param begin index of the first array element to include
    151         * @param length the number of elements to include
    152         * @return true if the parameters are valid and designate a subarray of positive length
    153         * @throws MathIllegalArgumentException if the indices are invalid or the array is null
    154         */
    155        protected boolean test(
    156            final double[] values,
    157            final int begin,
    158            final int length) throws MathIllegalArgumentException {
    159            return test(values, begin, length, false);
    160        }
    161
    162        /**
    163         * This method is used by <code>evaluate(double[], int, int)</code> methods
    164         * to verify that the input parameters designate a subarray of positive length.
    165         * <p>
    166         * <ul>
    167         * <li>returns <code>true</code> iff the parameters designate a subarray of
    168         * non-negative length</li>
    169         * <li>throws <code>IllegalArgumentException</code> if the array is null or
    170         * or the indices are invalid</li>
    171         * <li>returns <code>false</li> if the array is non-null, but
    172         * <code>length</code> is 0 unless <code>allowEmpty</code> is <code>true</code>
    173         * </ul></p>
    174         *
    175         * @param values the input array
    176         * @param begin index of the first array element to include
    177         * @param length the number of elements to include
    178         * @param allowEmpty if <code>true</code> then zero length arrays are allowed
    179         * @return true if the parameters are valid
    180         * @throws MathIllegalArgumentException if the indices are invalid or the array is null
    181         * @since 3.0
    182         */
    183        protected boolean test(final double[] values, final int begin,
    184                final int length, final boolean allowEmpty) throws MathIllegalArgumentException {
    185
    186            if (values == null) {
    187                throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
    188            }
    189
    190            if (begin < 0) {
    191                throw new NotPositiveException(LocalizedFormats.START_POSITION, begin);
    192            }
    193
    194            if (length < 0) {
    195                throw new NotPositiveException(LocalizedFormats.LENGTH, length);
    196            }
    197
    198            if (begin + length > values.length) {
    199                throw new NumberIsTooLargeException(LocalizedFormats.SUBARRAY_ENDS_AFTER_ARRAY_END,
    200                                                    begin + length, values.length, true);
    201            }
    202
    203            if (length == 0 && !allowEmpty) {
    204                return false;
    205            }
    206
    207            return true;
    208
    209        }
    210
    211        /**
    212         * This method is used by <code>evaluate(double[], double[], int, int)</code> methods
    213         * to verify that the begin and length parameters designate a subarray of positive length
    214         * and the weights are all non-negative, non-NaN, finite, and not all zero.
    215         * <p>
    216         * <ul>
    217         * <li>returns <code>true</code> iff the parameters designate a subarray of
    218         * positive length and the weights array contains legitimate values.</li>
    219         * <li>throws <code>IllegalArgumentException</code> if any of the following are true:
    220         * <ul><li>the values array is null</li>
    221         *     <li>the weights array is null</li>
    222         *     <li>the weights array does not have the same length as the values array</li>
    223         *     <li>the weights array contains one or more infinite values</li>
    224         *     <li>the weights array contains one or more NaN values</li>
    225         *     <li>the weights array contains negative values</li>
    226         *     <li>the start and length arguments do not determine a valid array</li></ul>
    227         * </li>
    228         * <li>returns <code>false</li> if the array is non-null, but
    229         * <code>length</code> is 0.
    230         * </ul></p>
    231         *
    232         * @param values the input array
    233         * @param weights the weights array
    234         * @param begin index of the first array element to include
    235         * @param length the number of elements to include
    236         * @return true if the parameters are valid and designate a subarray of positive length
    237         * @throws MathIllegalArgumentException if the indices are invalid or the array is null
    238         * @since 2.1
    239         */
    240        protected boolean test(
    241            final double[] values,
    242            final double[] weights,
    243            final int begin,
    244            final int length) throws MathIllegalArgumentException {
    245            return test(values, weights, begin, length, false);
    246        }
    247
    248        /**
    249         * This method is used by <code>evaluate(double[], double[], int, int)</code> methods
    250         * to verify that the begin and length parameters designate a subarray of positive length
    251         * and the weights are all non-negative, non-NaN, finite, and not all zero.
    252         * <p>
    253         * <ul>
    254         * <li>returns <code>true</code> iff the parameters designate a subarray of
    255         * non-negative length and the weights array contains legitimate values.</li>
    256         * <li>throws <code>MathIllegalArgumentException</code> if any of the following are true:
    257         * <ul><li>the values array is null</li>
    258         *     <li>the weights array is null</li>
    259         *     <li>the weights array does not have the same length as the values array</li>
    260         *     <li>the weights array contains one or more infinite values</li>
    261         *     <li>the weights array contains one or more NaN values</li>
    262         *     <li>the weights array contains negative values</li>
    263         *     <li>the start and length arguments do not determine a valid array</li></ul>
    264         * </li>
    265         * <li>returns <code>false</li> if the array is non-null, but
    266         * <code>length</code> is 0 unless <code>allowEmpty</code> is <code>true</code>.
    267         * </ul></p>
    268         *
    269         * @param values the input array.
    270         * @param weights the weights array.
    271         * @param begin index of the first array element to include.
    272         * @param length the number of elements to include.
    273         * @param allowEmpty if {@code true} than allow zero length arrays to pass.
    274         * @return {@code true} if the parameters are valid.
    275         * @throws NullArgumentException if either of the arrays are null
    276         * @throws MathIllegalArgumentException if the array indices are not valid,
    277         * the weights array contains NaN, infinite or negative elements, or there
    278         * are no positive weights.
    279         * @since 3.0
    280         */
    281        protected boolean test(final double[] values, final double[] weights,
    282                final int begin, final int length, final boolean allowEmpty) throws MathIllegalArgumentException {
    283
    284            if (weights == null || values == null) {
    285                throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
    286            }
    287
    288            if (weights.length != values.length) {
    289                throw new DimensionMismatchException(weights.length, values.length);
    290            }
    291
    292            boolean containsPositiveWeight = false;
    293            for (int i = begin; i < begin + length; i++) {
    294                if (Double.isNaN(weights[i])) {
    295                    throw new MathIllegalArgumentException(LocalizedFormats.NAN_ELEMENT_AT_INDEX, i);
    296                }
    297                if (Double.isInfinite(weights[i])) {
    298                    throw new MathIllegalArgumentException(LocalizedFormats.INFINITE_ARRAY_ELEMENT, weights[i], i);
    299                }
    300                if (weights[i] < 0) {
    301                    throw new MathIllegalArgumentException(LocalizedFormats.NEGATIVE_ELEMENT_AT_INDEX, i, weights[i]);
    302                }
    303                if (!containsPositiveWeight && weights[i] > 0.0) {
    304                    containsPositiveWeight = true;
    305                }
    306            }
    307
    308            if (!containsPositiveWeight) {
    309                throw new MathIllegalArgumentException(LocalizedFormats.WEIGHT_AT_LEAST_ONE_NON_ZERO);
    310            }
    311
    312            return test(values, begin, length, allowEmpty);
    313        }
    314    }
    315