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