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