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 */
017package org.apache.commons.math4.legacy.stat.descriptive;
018
019import org.apache.commons.math4.legacy.exception.MathIllegalStateException;
020import org.apache.commons.math4.legacy.exception.NullArgumentException;
021import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
022import org.apache.commons.math4.legacy.stat.descriptive.moment.GeometricMean;
023import org.apache.commons.math4.legacy.stat.descriptive.moment.Mean;
024import org.apache.commons.math4.legacy.stat.descriptive.moment.SecondMoment;
025import org.apache.commons.math4.legacy.stat.descriptive.moment.Variance;
026import org.apache.commons.math4.legacy.stat.descriptive.rank.Max;
027import org.apache.commons.math4.legacy.stat.descriptive.rank.Min;
028import org.apache.commons.math4.legacy.stat.descriptive.summary.Sum;
029import org.apache.commons.math4.legacy.stat.descriptive.summary.SumOfLogs;
030import org.apache.commons.math4.legacy.stat.descriptive.summary.SumOfSquares;
031import org.apache.commons.math4.core.jdkmath.JdkMath;
032import org.apache.commons.numbers.core.Precision;
033
034/**
035 * <p>
036 * Computes summary statistics for a stream of data values added using the
037 * {@link #addValue(double) addValue} method. The data values are not stored in
038 * memory, so this class can be used to compute statistics for very large data
039 * streams.
040 * </p>
041 * <p>
042 * The {@link StorelessUnivariateStatistic} instances used to maintain summary
043 * state and compute statistics are configurable via setters. For example, the
044 * default implementation for the variance can be overridden by calling
045 * {@link #setVarianceImpl(StorelessUnivariateStatistic)}. Actual parameters to
046 * these methods must implement the {@link StorelessUnivariateStatistic}
047 * interface and configuration must be completed before <code>addValue</code>
048 * is called. No configuration is necessary to use the default, commons-math
049 * provided implementations.
050 * </p>
051 * <p>
052 * Note: This class is not thread-safe. Use
053 * {@link SynchronizedSummaryStatistics} if concurrent access from multiple
054 * threads is required.
055 * </p>
056 */
057public class SummaryStatistics implements StatisticalSummary {
058    /** count of values that have been added. */
059    private long n;
060
061    /** SecondMoment is used to compute the mean and variance. */
062    private SecondMoment secondMoment = new SecondMoment();
063
064    /** sum of values that have been added. */
065    private Sum sum = new Sum();
066
067    /** sum of the square of each value that has been added. */
068    private SumOfSquares sumsq = new SumOfSquares();
069
070    /** min of values that have been added. */
071    private Min min = new Min();
072
073    /** max of values that have been added. */
074    private Max max = new Max();
075
076    /** sumLog of values that have been added. */
077    private SumOfLogs sumLog = new SumOfLogs();
078
079    /** geoMean of values that have been added. */
080    private GeometricMean geoMean = new GeometricMean(sumLog);
081
082    /** mean of values that have been added. */
083    private Mean mean = new Mean(secondMoment);
084
085    /** variance of values that have been added. */
086    private Variance variance = new Variance(secondMoment);
087
088    /** Sum statistic implementation - can be reset by setter. */
089    private StorelessUnivariateStatistic sumImpl = sum;
090
091    /** Sum of squares statistic implementation - can be reset by setter. */
092    private StorelessUnivariateStatistic sumsqImpl = sumsq;
093
094    /** Minimum statistic implementation - can be reset by setter. */
095    private StorelessUnivariateStatistic minImpl = min;
096
097    /** Maximum statistic implementation - can be reset by setter. */
098    private StorelessUnivariateStatistic maxImpl = max;
099
100    /** Sum of log statistic implementation - can be reset by setter. */
101    private StorelessUnivariateStatistic sumLogImpl = sumLog;
102
103    /** Geometric mean statistic implementation - can be reset by setter. */
104    private StorelessUnivariateStatistic geoMeanImpl = geoMean;
105
106    /** Mean statistic implementation - can be reset by setter. */
107    private StorelessUnivariateStatistic meanImpl = mean;
108
109    /** Variance statistic implementation - can be reset by setter. */
110    private StorelessUnivariateStatistic varianceImpl = variance;
111
112    /**
113     * Construct a SummaryStatistics instance.
114     */
115    public SummaryStatistics() {
116    }
117
118    /**
119     * A copy constructor. Creates a deep-copy of the {@code original}.
120     *
121     * @param original the {@code SummaryStatistics} instance to copy
122     * @throws NullArgumentException if original is null
123     */
124    public SummaryStatistics(SummaryStatistics original) throws NullArgumentException {
125        copy(original, this);
126    }
127
128    /**
129     * Return a {@link StatisticalSummaryValues} instance reporting current
130     * statistics.
131     * @return Current values of statistics
132     */
133    public StatisticalSummary getSummary() {
134        return new StatisticalSummaryValues(getMean(), getVariance(), getN(),
135                getMax(), getMin(), getSum());
136    }
137
138    /**
139     * Add a value to the data.
140     * @param value the value to add
141     */
142    public void addValue(double value) {
143        sumImpl.increment(value);
144        sumsqImpl.increment(value);
145        minImpl.increment(value);
146        maxImpl.increment(value);
147        sumLogImpl.increment(value);
148        secondMoment.increment(value);
149        // If mean, variance or geomean have been overridden,
150        // need to increment these
151        if (meanImpl != mean) {
152            meanImpl.increment(value);
153        }
154        if (varianceImpl != variance) {
155            varianceImpl.increment(value);
156        }
157        if (geoMeanImpl != geoMean) {
158            geoMeanImpl.increment(value);
159        }
160        n++;
161    }
162
163    /**
164     * Returns the number of available values.
165     * @return The number of available values
166     */
167    @Override
168    public long getN() {
169        return n;
170    }
171
172    /**
173     * Returns the sum of the values that have been added.
174     * @return The sum or <code>Double.NaN</code> if no values have been added
175     */
176    @Override
177    public double getSum() {
178        return sumImpl.getResult();
179    }
180
181    /**
182     * Returns the sum of the squares of the values that have been added.
183     * <p>
184     * Double.NaN is returned if no values have been added.
185     * </p>
186     * @return The sum of squares
187     */
188    public double getSumsq() {
189        return sumsqImpl.getResult();
190    }
191
192    /**
193     * Returns the mean of the values that have been added.
194     * <p>
195     * Double.NaN is returned if no values have been added.
196     * </p>
197     * @return the mean
198     */
199    @Override
200    public double getMean() {
201        return meanImpl.getResult();
202    }
203
204    /**
205     * Returns the standard deviation of the values that have been added.
206     * <p>
207     * Double.NaN is returned if no values have been added.
208     * </p>
209     * @return the standard deviation
210     */
211    @Override
212    public double getStandardDeviation() {
213        double stdDev = Double.NaN;
214        if (getN() > 0) {
215            if (getN() > 1) {
216                stdDev = JdkMath.sqrt(getVariance());
217            } else {
218                stdDev = 0.0;
219            }
220        }
221        return stdDev;
222    }
223
224    /**
225     * Returns the quadratic mean, a.k.a.
226     * <a href="http://mathworld.wolfram.com/Root-Mean-Square.html">
227     * root-mean-square</a> of the available values
228     * @return The quadratic mean or {@code Double.NaN} if no values
229     * have been added.
230     */
231    public double getQuadraticMean() {
232        final long size = getN();
233        return size > 0 ? JdkMath.sqrt(getSumsq() / size) : Double.NaN;
234    }
235
236    /**
237     * Returns the (sample) variance of the available values.
238     *
239     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in
240     * the denominator).  Use {@link #getPopulationVariance()} for the non-bias-corrected
241     * population variance.</p>
242     *
243     * <p>Double.NaN is returned if no values have been added.</p>
244     *
245     * @return the variance
246     */
247    @Override
248    public double getVariance() {
249        return varianceImpl.getResult();
250    }
251
252    /**
253     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">
254     * population variance</a> of the values that have been added.
255     *
256     * <p>Double.NaN is returned if no values have been added.</p>
257     *
258     * @return the population variance
259     */
260    public double getPopulationVariance() {
261        Variance populationVariance = new Variance(secondMoment);
262        populationVariance.setBiasCorrected(false);
263        return populationVariance.getResult();
264    }
265
266    /**
267     * Returns the maximum of the values that have been added.
268     * <p>
269     * Double.NaN is returned if no values have been added.
270     * </p>
271     * @return the maximum
272     */
273    @Override
274    public double getMax() {
275        return maxImpl.getResult();
276    }
277
278    /**
279     * Returns the minimum of the values that have been added.
280     * <p>
281     * Double.NaN is returned if no values have been added.
282     * </p>
283     * @return the minimum
284     */
285    @Override
286    public double getMin() {
287        return minImpl.getResult();
288    }
289
290    /**
291     * Returns the geometric mean of the values that have been added.
292     * <p>
293     * Double.NaN is returned if no values have been added.
294     * </p>
295     * @return the geometric mean
296     */
297    public double getGeometricMean() {
298        return geoMeanImpl.getResult();
299    }
300
301    /**
302     * Returns the sum of the logs of the values that have been added.
303     * <p>
304     * Double.NaN is returned if no values have been added.
305     * </p>
306     * @return the sum of logs
307     * @since 1.2
308     */
309    public double getSumOfLogs() {
310        return sumLogImpl.getResult();
311    }
312
313    /**
314     * Returns a statistic related to the Second Central Moment.  Specifically,
315     * what is returned is the sum of squared deviations from the sample mean
316     * among the values that have been added.
317     * <p>
318     * Returns <code>Double.NaN</code> if no data values have been added and
319     * returns <code>0</code> if there is just one value in the data set.
320     * </p>
321     * @return second central moment statistic
322     * @since 2.0
323     */
324    public double getSecondMoment() {
325        return secondMoment.getResult();
326    }
327
328    /**
329     * Generates a text report displaying summary statistics from values that
330     * have been added.
331     * @return String with line feeds displaying statistics
332     * @since 1.2
333     */
334    @Override
335    public String toString() {
336        StringBuilder outBuffer = new StringBuilder();
337        String endl = "\n";
338        outBuffer.append("SummaryStatistics:").append(endl);
339        outBuffer.append("n: ").append(getN()).append(endl);
340        outBuffer.append("min: ").append(getMin()).append(endl);
341        outBuffer.append("max: ").append(getMax()).append(endl);
342        outBuffer.append("sum: ").append(getSum()).append(endl);
343        outBuffer.append("mean: ").append(getMean()).append(endl);
344        outBuffer.append("geometric mean: ").append(getGeometricMean())
345            .append(endl);
346        outBuffer.append("variance: ").append(getVariance()).append(endl);
347        outBuffer.append("population variance: ").append(getPopulationVariance()).append(endl);
348        outBuffer.append("second moment: ").append(getSecondMoment()).append(endl);
349        outBuffer.append("sum of squares: ").append(getSumsq()).append(endl);
350        outBuffer.append("standard deviation: ").append(getStandardDeviation())
351            .append(endl);
352        outBuffer.append("sum of logs: ").append(getSumOfLogs()).append(endl);
353        return outBuffer.toString();
354    }
355
356    /**
357     * Resets all statistics and storage.
358     */
359    public void clear() {
360        this.n = 0;
361        minImpl.clear();
362        maxImpl.clear();
363        sumImpl.clear();
364        sumLogImpl.clear();
365        sumsqImpl.clear();
366        geoMeanImpl.clear();
367        secondMoment.clear();
368        if (meanImpl != mean) {
369            meanImpl.clear();
370        }
371        if (varianceImpl != variance) {
372            varianceImpl.clear();
373        }
374    }
375
376    /**
377     * Returns true iff <code>object</code> is a
378     * <code>SummaryStatistics</code> instance and all statistics have the
379     * same values as this.
380     * @param object the object to test equality against.
381     * @return true if object equals this
382     */
383    @Override
384    public boolean equals(Object object) {
385        if (object == this) {
386            return true;
387        }
388        if (!(object instanceof SummaryStatistics)) {
389            return false;
390        }
391        SummaryStatistics stat = (SummaryStatistics)object;
392        return Precision.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) &&
393               Precision.equalsIncludingNaN(stat.getMax(),           getMax())           &&
394               Precision.equalsIncludingNaN(stat.getMean(),          getMean())          &&
395               Precision.equalsIncludingNaN(stat.getMin(),           getMin())           &&
396               Precision.equalsIncludingNaN(stat.getN(),             getN())             &&
397               Precision.equalsIncludingNaN(stat.getSum(),           getSum())           &&
398               Precision.equalsIncludingNaN(stat.getSumsq(),         getSumsq())         &&
399               Precision.equalsIncludingNaN(stat.getVariance(),      getVariance());
400    }
401
402    /**
403     * Returns hash code based on values of statistics.
404     * @return hash code
405     */
406    @Override
407    public int hashCode() {
408        int result = 31 + Double.hashCode(getGeometricMean());
409        result = result * 31 + Double.hashCode(getGeometricMean());
410        result = result * 31 + Double.hashCode(getMax());
411        result = result * 31 + Double.hashCode(getMean());
412        result = result * 31 + Double.hashCode(getMin());
413        result = result * 31 + Double.hashCode(getN());
414        result = result * 31 + Double.hashCode(getSum());
415        result = result * 31 + Double.hashCode(getSumsq());
416        result = result * 31 + Double.hashCode(getVariance());
417        return result;
418    }
419
420    // Getters and setters for statistics implementations
421    /**
422     * Returns the currently configured Sum implementation.
423     * @return the StorelessUnivariateStatistic implementing the sum
424     * @since 1.2
425     */
426    public StorelessUnivariateStatistic getSumImpl() {
427        return sumImpl;
428    }
429
430    /**
431     * <p>
432     * Sets the implementation for the Sum.
433     * </p>
434     * <p>
435     * This method cannot be activated after data has been added - i.e.,
436     * after {@link #addValue(double) addValue} has been used to add data.
437     * If it is activated after data has been added, an IllegalStateException
438     * will be thrown.
439     * </p>
440     * @param sumImpl the StorelessUnivariateStatistic instance to use for
441     *        computing the Sum
442     * @throws MathIllegalStateException if data has already been added (i.e if n &gt;0)
443     * @since 1.2
444     */
445    public void setSumImpl(StorelessUnivariateStatistic sumImpl)
446    throws MathIllegalStateException {
447        checkEmpty();
448        this.sumImpl = sumImpl;
449    }
450
451    /**
452     * Returns the currently configured sum of squares implementation.
453     * @return the StorelessUnivariateStatistic implementing the sum of squares
454     * @since 1.2
455     */
456    public StorelessUnivariateStatistic getSumsqImpl() {
457        return sumsqImpl;
458    }
459
460    /**
461     * <p>
462     * Sets the implementation for the sum of squares.
463     * </p>
464     * <p>
465     * This method cannot be activated after data has been added - i.e.,
466     * after {@link #addValue(double) addValue} has been used to add data.
467     * If it is activated after data has been added, an IllegalStateException
468     * will be thrown.
469     * </p>
470     * @param sumsqImpl the StorelessUnivariateStatistic instance to use for
471     *        computing the sum of squares
472     * @throws MathIllegalStateException if data has already been added (i.e if n &gt; 0)
473     * @since 1.2
474     */
475    public void setSumsqImpl(StorelessUnivariateStatistic sumsqImpl)
476    throws MathIllegalStateException {
477        checkEmpty();
478        this.sumsqImpl = sumsqImpl;
479    }
480
481    /**
482     * Returns the currently configured minimum implementation.
483     * @return the StorelessUnivariateStatistic implementing the minimum
484     * @since 1.2
485     */
486    public StorelessUnivariateStatistic getMinImpl() {
487        return minImpl;
488    }
489
490    /**
491     * <p>
492     * Sets the implementation for the minimum.
493     * </p>
494     * <p>
495     * This method cannot be activated after data has been added - i.e.,
496     * after {@link #addValue(double) addValue} has been used to add data.
497     * If it is activated after data has been added, an IllegalStateException
498     * will be thrown.
499     * </p>
500     * @param minImpl the StorelessUnivariateStatistic instance to use for
501     *        computing the minimum
502     * @throws MathIllegalStateException if data has already been added (i.e if n &gt; 0)
503     * @since 1.2
504     */
505    public void setMinImpl(StorelessUnivariateStatistic minImpl)
506    throws MathIllegalStateException {
507        checkEmpty();
508        this.minImpl = minImpl;
509    }
510
511    /**
512     * Returns the currently configured maximum implementation.
513     * @return the StorelessUnivariateStatistic implementing the maximum
514     * @since 1.2
515     */
516    public StorelessUnivariateStatistic getMaxImpl() {
517        return maxImpl;
518    }
519
520    /**
521     * <p>
522     * Sets the implementation for the maximum.
523     * </p>
524     * <p>
525     * This method cannot be activated after data has been added - i.e.,
526     * after {@link #addValue(double) addValue} has been used to add data.
527     * If it is activated after data has been added, an IllegalStateException
528     * will be thrown.
529     * </p>
530     * @param maxImpl the StorelessUnivariateStatistic instance to use for
531     *        computing the maximum
532     * @throws MathIllegalStateException if data has already been added (i.e if n &gt; 0)
533     * @since 1.2
534     */
535    public void setMaxImpl(StorelessUnivariateStatistic maxImpl)
536    throws MathIllegalStateException {
537        checkEmpty();
538        this.maxImpl = maxImpl;
539    }
540
541    /**
542     * Returns the currently configured sum of logs implementation.
543     * @return the StorelessUnivariateStatistic implementing the log sum
544     * @since 1.2
545     */
546    public StorelessUnivariateStatistic getSumLogImpl() {
547        return sumLogImpl;
548    }
549
550    /**
551     * <p>
552     * Sets the implementation for the sum of logs.
553     * </p>
554     * <p>
555     * This method cannot be activated after data has been added - i.e.,
556     * after {@link #addValue(double) addValue} has been used to add data.
557     * If it is activated after data has been added, an IllegalStateException
558     * will be thrown.
559     * </p>
560     * @param sumLogImpl the StorelessUnivariateStatistic instance to use for
561     *        computing the log sum
562     * @throws MathIllegalStateException if data has already been added (i.e if n &gt; 0)
563     * @since 1.2
564     */
565    public void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl)
566    throws MathIllegalStateException {
567        checkEmpty();
568        this.sumLogImpl = sumLogImpl;
569        geoMean.setSumLogImpl(sumLogImpl);
570    }
571
572    /**
573     * Returns the currently configured geometric mean implementation.
574     * @return the StorelessUnivariateStatistic implementing the geometric mean
575     * @since 1.2
576     */
577    public StorelessUnivariateStatistic getGeoMeanImpl() {
578        return geoMeanImpl;
579    }
580
581    /**
582     * <p>
583     * Sets the implementation for the geometric mean.
584     * </p>
585     * <p>
586     * This method cannot be activated after data has been added - i.e.,
587     * after {@link #addValue(double) addValue} has been used to add data.
588     * If it is activated after data has been added, an IllegalStateException
589     * will be thrown.
590     * </p>
591     * @param geoMeanImpl the StorelessUnivariateStatistic instance to use for
592     *        computing the geometric mean
593     * @throws MathIllegalStateException if data has already been added (i.e if n &gt; 0)
594     * @since 1.2
595     */
596    public void setGeoMeanImpl(StorelessUnivariateStatistic geoMeanImpl)
597    throws MathIllegalStateException {
598        checkEmpty();
599        this.geoMeanImpl = geoMeanImpl;
600    }
601
602    /**
603     * Returns the currently configured mean implementation.
604     * @return the StorelessUnivariateStatistic implementing the mean
605     * @since 1.2
606     */
607    public StorelessUnivariateStatistic getMeanImpl() {
608        return meanImpl;
609    }
610
611    /**
612     * <p>
613     * Sets the implementation for the mean.
614     * </p>
615     * <p>
616     * This method cannot be activated after data has been added - i.e.,
617     * after {@link #addValue(double) addValue} has been used to add data.
618     * If it is activated after data has been added, an IllegalStateException
619     * will be thrown.
620     * </p>
621     * @param meanImpl the StorelessUnivariateStatistic instance to use for
622     *        computing the mean
623     * @throws MathIllegalStateException if data has already been added (i.e if n &gt; 0)
624     * @since 1.2
625     */
626    public void setMeanImpl(StorelessUnivariateStatistic meanImpl)
627    throws MathIllegalStateException {
628        checkEmpty();
629        this.meanImpl = meanImpl;
630    }
631
632    /**
633     * Returns the currently configured variance implementation.
634     * @return the StorelessUnivariateStatistic implementing the variance
635     * @since 1.2
636     */
637    public StorelessUnivariateStatistic getVarianceImpl() {
638        return varianceImpl;
639    }
640
641    /**
642     * <p>
643     * Sets the implementation for the variance.
644     * </p>
645     * <p>
646     * This method cannot be activated after data has been added - i.e.,
647     * after {@link #addValue(double) addValue} has been used to add data.
648     * If it is activated after data has been added, an IllegalStateException
649     * will be thrown.
650     * </p>
651     * @param varianceImpl the StorelessUnivariateStatistic instance to use for
652     *        computing the variance
653     * @throws MathIllegalStateException if data has already been added (i.e if n &gt; 0)
654     * @since 1.2
655     */
656    public void setVarianceImpl(StorelessUnivariateStatistic varianceImpl)
657    throws MathIllegalStateException {
658        checkEmpty();
659        this.varianceImpl = varianceImpl;
660    }
661
662    /**
663     * Throws IllegalStateException if n > 0.
664     * @throws MathIllegalStateException if data has been added
665     */
666    private void checkEmpty() throws MathIllegalStateException {
667        if (n > 0) {
668            throw new MathIllegalStateException(
669                LocalizedFormats.VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC, n);
670        }
671    }
672
673    /**
674     * Returns a copy of this SummaryStatistics instance with the same internal state.
675     *
676     * @return a copy of this
677     */
678    public SummaryStatistics copy() {
679        SummaryStatistics result = new SummaryStatistics();
680        // No try-catch or advertised exception because arguments are guaranteed non-null
681        copy(this, result);
682        return result;
683    }
684
685    /**
686     * Copies source to dest.
687     * <p>Neither source nor dest can be null.</p>
688     *
689     * @param source SummaryStatistics to copy
690     * @param dest SummaryStatistics to copy to
691     * @throws NullArgumentException if either source or dest is null
692     */
693    public static void copy(SummaryStatistics source, SummaryStatistics dest)
694        throws NullArgumentException {
695        NullArgumentException.check(source);
696        NullArgumentException.check(dest);
697        dest.maxImpl = source.maxImpl.copy();
698        dest.minImpl = source.minImpl.copy();
699        dest.sumImpl = source.sumImpl.copy();
700        dest.sumLogImpl = source.sumLogImpl.copy();
701        dest.sumsqImpl = source.sumsqImpl.copy();
702        dest.secondMoment = source.secondMoment.copy();
703        dest.n = source.n;
704
705        // Keep commons-math supplied statistics with embedded moments in synch
706        if (source.getVarianceImpl() instanceof Variance) {
707            dest.varianceImpl = new Variance(dest.secondMoment);
708        } else {
709            dest.varianceImpl = source.varianceImpl.copy();
710        }
711        if (source.meanImpl instanceof Mean) {
712            dest.meanImpl = new Mean(dest.secondMoment);
713        } else {
714            dest.meanImpl = source.meanImpl.copy();
715        }
716        if (source.getGeoMeanImpl() instanceof GeometricMean) {
717            dest.geoMeanImpl = new GeometricMean((SumOfLogs) dest.sumLogImpl);
718        } else {
719            dest.geoMeanImpl = source.geoMeanImpl.copy();
720        }
721
722        // Make sure that if stat == statImpl in source, same
723        // holds in dest; otherwise copy stat
724        if (source.geoMean == source.geoMeanImpl) {
725            dest.geoMean = (GeometricMean) dest.geoMeanImpl;
726        } else {
727            GeometricMean.copy(source.geoMean, dest.geoMean);
728        }
729        if (source.max == source.maxImpl) {
730            dest.max = (Max) dest.maxImpl;
731        } else {
732            Max.copy(source.max, dest.max);
733        }
734        if (source.mean == source.meanImpl) {
735            dest.mean = (Mean) dest.meanImpl;
736        } else {
737            Mean.copy(source.mean, dest.mean);
738        }
739        if (source.min == source.minImpl) {
740            dest.min = (Min) dest.minImpl;
741        } else {
742            Min.copy(source.min, dest.min);
743        }
744        if (source.sum == source.sumImpl) {
745            dest.sum = (Sum) dest.sumImpl;
746        } else {
747            Sum.copy(source.sum, dest.sum);
748        }
749        if (source.variance == source.varianceImpl) {
750            dest.variance = (Variance) dest.varianceImpl;
751        } else {
752            Variance.copy(source.variance, dest.variance);
753        }
754        if (source.sumLog == source.sumLogImpl) {
755            dest.sumLog = (SumOfLogs) dest.sumLogImpl;
756        } else {
757            SumOfLogs.copy(source.sumLog, dest.sumLog);
758        }
759        if (source.sumsq == source.sumsqImpl) {
760            dest.sumsq = (SumOfSquares) dest.sumsqImpl;
761        } else {
762            SumOfSquares.copy(source.sumsq, dest.sumsq);
763        }
764    }
765}