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.math.stat.descriptive.moment;
018    
019    import java.io.Serializable;
020    
021    import org.apache.commons.math.MathRuntimeException;
022    import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
023    import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatistic;
024    import org.apache.commons.math.stat.descriptive.summary.SumOfLogs;
025    
026    /**
027     * Returns the <a href="http://www.xycoon.com/geometric_mean.htm">
028     * geometric mean </a> of the available values.
029     * <p>
030     * Uses a {@link SumOfLogs} instance to compute sum of logs and returns
031     * <code> exp( 1/n  (sum of logs) ).</code>  Therefore, </p>
032     * <ul>
033     * <li>If any of values are < 0, the result is <code>NaN.</code></li>
034     * <li>If all values are non-negative and less than 
035     * <code>Double.POSITIVE_INFINITY</code>,  but at least one value is 0, the 
036     * result is <code>0.</code></li>
037     * <li>If both <code>Double.POSITIVE_INFINITY</code> and 
038     * <code>Double.NEGATIVE_INFINITY</code> are among the values, the result is
039     * <code>NaN.</code></li>
040     * </ul> </p>
041     * <p>
042     * <strong>Note that this implementation is not synchronized.</strong> If 
043     * multiple threads access an instance of this class concurrently, and at least
044     * one of the threads invokes the <code>increment()</code> or 
045     * <code>clear()</code> method, it must be synchronized externally.</p>
046     * 
047     *
048     * @version $Revision: 780541 $ $Date: 2009-05-31 20:47:02 -0400 (Sun, 31 May 2009) $
049     */
050    public class GeometricMean extends AbstractStorelessUnivariateStatistic implements Serializable {
051    
052        /** Serializable version identifier */
053        private static final long serialVersionUID = -8178734905303459453L;  
054        
055        /** Wrapped SumOfLogs instance */
056        private StorelessUnivariateStatistic sumOfLogs;
057    
058        /**
059         * Create a GeometricMean instance
060         */
061        public GeometricMean() {
062            sumOfLogs = new SumOfLogs();
063        }
064        
065        /**
066         * Copy constructor, creates a new {@code GeometricMean} identical
067         * to the {@code original}
068         * 
069         * @param original the {@code GeometricMean} instance to copy
070         */
071        public GeometricMean(GeometricMean original) {
072            super();
073            copy(original, this);
074        }
075        
076        /**
077         * {@inheritDoc}
078         */
079        @Override
080        public GeometricMean copy() {
081            GeometricMean result = new GeometricMean();
082            copy(this, result);
083            return result;
084        }
085        
086        /**
087         * Create a GeometricMean instance using the given SumOfLogs instance
088         * @param sumOfLogs sum of logs instance to use for computation
089         */
090        public GeometricMean(SumOfLogs sumOfLogs) {
091            this.sumOfLogs = sumOfLogs;
092        }
093        
094        /**
095         * {@inheritDoc}
096         */
097        @Override
098        public void increment(final double d) {
099            sumOfLogs.increment(d);
100        }
101    
102        /**
103         * {@inheritDoc}
104         */
105        @Override
106        public double getResult() {
107            if (sumOfLogs.getN() > 0) {
108                return Math.exp(sumOfLogs.getResult() / sumOfLogs.getN());
109            } else {
110                return Double.NaN;
111            }
112        }
113    
114        /**
115         * {@inheritDoc}
116         */
117        @Override
118        public void clear() {
119            sumOfLogs.clear();
120        }
121    
122        /**
123         * Returns the geometric mean of the entries in the specified portion
124         * of the input array.
125         * <p>
126         * See {@link GeometricMean} for details on the computing algorithm.</p>
127         * <p>
128         * Throws <code>IllegalArgumentException</code> if the array is null.</p>
129         * 
130         * @param values input array containing the values
131         * @param begin first array element to include
132         * @param length the number of elements to include
133         * @return the geometric mean or Double.NaN if length = 0 or
134         * any of the values are &lt;= 0.
135         * @throws IllegalArgumentException if the input array is null or the array
136         * index parameters are not valid
137         */
138        @Override
139        public double evaluate(
140            final double[] values, final int begin, final int length) {
141            return Math.exp(
142                sumOfLogs.evaluate(values, begin, length) / length);
143        }
144        
145        /**
146         * {@inheritDoc}
147         */
148        public long getN() {
149            return sumOfLogs.getN();
150        }
151        
152        /**
153         * <p>Sets the implementation for the sum of logs.</p>
154         * <p>This method must be activated before any data has been added - i.e.,
155         * before {@link #increment(double) increment} has been used to add data; 
156         * otherwise an IllegalStateException will be thrown.</p>
157         * 
158         * @param sumLogImpl the StorelessUnivariateStatistic instance to use
159         * for computing the log sum
160         * @throws IllegalStateException if data has already been added 
161         *  (i.e if n > 0)
162         */
163        public void setSumLogImpl(
164                StorelessUnivariateStatistic sumLogImpl) {
165            checkEmpty();
166            this.sumOfLogs = sumLogImpl;
167        }
168        
169        /**
170         * Returns the currently configured sum of logs implementation
171         * 
172         * @return the StorelessUnivariateStatistic implementing the log sum
173         */
174        public StorelessUnivariateStatistic getSumLogImpl() {
175            return sumOfLogs;
176        }
177        
178        /**
179         * Copies source to dest.
180         * <p>Neither source nor dest can be null.</p>
181         * 
182         * @param source GeometricMean to copy
183         * @param dest GeometricMean to copy to
184         * @throws NullPointerException if either source or dest is null
185         */
186        public static void copy(GeometricMean source, GeometricMean dest) {
187            dest.sumOfLogs = source.sumOfLogs.copy();
188        }
189        
190        
191        /**
192         * Throws IllegalStateException if n > 0.
193         */
194        private void checkEmpty() {
195            if (getN() > 0) {
196                throw MathRuntimeException.createIllegalStateException(
197                        "{0} values have been added before statistic is configured",
198                        getN());
199            }
200        }
201    
202    }