content: /*  Aggregation.java
  
      Copyright (c) 2009-2014 Andrew Rosenberg
  
    This file is part of the AuToBI prosodic analysis package.
  
    AuToBI is free software: you can redistribute it and/or modify
    it under the terms of the Apache License (see boilerplate below)
  
   ***********************************************************************************************************************
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
   * the License. You should have received a copy of the Apache 2.0 License along with AuToBI.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
   * specific language governing permissions and limitations under the License.
   *
   ***********************************************************************************************************************
   */
  package edu.cuny.qc.speech.AuToBI.core;
  
  import java.util.Collection;
  import java.io.Serializable;
  
  import static org.apache.commons.math3.special.Erf.erf;
  
  /**
   * A class to store aggregations of real numbered information.
   * <p/>
   * Aggregation is used to store and serialize running means and standard deviations of values such as pitch and
   * intensity.
   */
  public class Aggregation implements Serializable {
    private static final long serialVersionUID = 2012709453361591892L;
  
    private String label; // an optional label for the aggregation
    private Double min;   // the maximum value in the aggregation
    private Double max;   // the minimum value in the aggregation
    private Double sum;   // the sum of all values added to the aggregation
    private Double ssq;   // the sum of squares of all values added to the aggregation
    private Integer n;    // the number of elements in the aggregation
  
    /**
     * Constructs a new Aggregation
     */
    public Aggregation() {
      this.label = "";
      this.min = Double.MAX_VALUE;
      this.max = -Double.MAX_VALUE;
      this.sum = 0.0;
      this.ssq = 0.0;
      this.n = 0;
    }
  
    /**
     * Constructs a new Aggregation with a label
     *
     * @param label the label
     */
    public Aggregation(String label) {
      this();
      this.label = label;
    }
  
  
    /**
     * Sets the label.
     *
     * @param label The new label
     */
    public void setLabel(String label) {
      this.label = label;
    }
  
    /**
     * Gets the label
     *
     * @return label
     */
    public String getLabel() {
      return label;
    }
  
  
    /**
     * Inserts a new value in the Aggregation
     *
     * @param v the value
     */
    public void insert(Double v) {
      sum += v;
      ssq += (v * v);
      max = (max == null) ? v : Math.max(max, v);
      min = (min == null) ? v : Math.min(min, v);
      n++;
    }
  
    /**
     * Removes a value from the Aggregation.
     * <p/>
     * Removing a value from an Aggregation can invalidate its minimum and maximum calculation if the value was equal to
     * the current minmum or maximum.
     * <p/>
     * Note: no check is made that the value was ever initially added to the Aggregation.
     *
     * @param v the value
     */
    public void remove(Double v) {
      sum -= v;
      ssq -= (v * v);
      if (v.equals(max)) {
        max = Double.NaN;  // no running max and min
      }
      if (v.equals(min)) {
        min = Double.NaN;
      }
      n--;
    }
  
    /**
     * Inserts a Collection of values.
     *
     * @param values the values
     */
    public void insert(Collection<Double> values) {
      for (Double d : values)
        this.insert(d);
    }
  
    /**
     * Removes a Collection of values.
     *
     * @param values the values
     */
    public void remove(Collection<Double> values) {
      for (Double d : values)
        this.remove(d);
    }
  
    /**
     * Calculates the mean value.
     * <p/>
     * If there are no elements in the Aggregation, the mean is zero.
     *
     * @return the mean
     */
    public Double getMean() {
      if (n < 1) return 0.0;
      return sum / n;
    }
  
    /**
     * Calculates the standard deviation.
     * <p/>
     * If there are less than 2 elements in the Aggregation, the standard deviation is zero.
     *
     * @return the standard deviation
     */
    public Double getStdev() {
      if (n < 2) return 0.0;
  
      return Math.sqrt(getVariance());
    }
  
    /**
     * Calculates the variance
     * <p/>
     * If there are less than 2 elements in the Aggregation, the variance is zero.
     *
     * @return the variance
     */
    public Double getVariance() {
      if (n < 2) return 0.0;
  
      Double mean = sum / n;
      return (ssq - (n * mean * mean)) / (n - 1);
    }
  
    /**
     * Calculates the root mean squared value.
     *
     * @return the rms
     */
    public Double getRMS() {
      return Math.sqrt(ssq / n);
    }
  
    /**
     * Retrieves the minimum value.
     *
     * @return the minimum
     */
    public Double getMin() {
      return min;
    }
  
    /**
     * Overrides the current min with an externally calculated value
     * <p/>
     * This is helpful if the minimum value gets invalidated by remove.
     *
     * @param min the minimum value
     */
    public void setMin(Double min) {
      this.min = min;
    }
  
    /**
     * Retrieves the maximum value.
     *
     * @return the maximum
     */
    public Double getMax() {
      return max;
    }
  
    /**
     * Overrides the current max with an externally calculated value
     * <p/>
     * This is helpful if the maximum value gets invalidated by remove.
     *
     * @param max the maximum value
     */
    public void setMax(Double max) {
      this.max = max;
    }
  
    /**
     * Retrieves the number of elements.
     *
     * @return the number of elements.
     */
    public int getSize() {
      return n;
    }
  
    /**
     * Treating this aggregation as a Gaussian probability distribution function, evaluates the probability that a value
     * generated was generated by this aggregation.
     *
     * @param value the value to evaluate
     * @return the gaussian PDF evaluated at value
     */
    public double evaluateGaussianPDF(double value) {
      double mean = getMean();
      double stdev = getStdev();
  
      double pdf = 1 / (stdev * Math.sqrt(2 * Math.PI));
      pdf *= Math.pow(Math.E, (-(value - mean) * (value - mean)) / (2 * stdev * stdev));
      return pdf;
    }
  
    /**
     * Evaluates the CDF of the aggregation.  The aggregation is treated as a Gaussian distribution in this case
     * <p/>
     * The CDF is the probability of a value drawn from the distribution falling below f
     * <p/>
     * cdf(x) = 1/2 [1 + erf(x - mu/sqrt(2 * stdev^2))]
     *
     * @param value the value
     * @return the CDF
     */
    public double evaluateGaussianCDF(double value) {
      double stdev = getStdev();
      return .5 * (1 + erf((value - getMean()) / Math.sqrt(2 * stdev * stdev)));
    }
  
    /**
     * Generates a descriprion of the Aggregation
     *
     * @return the description
     */
    public String toString() {
      String s = "";
      s += "mean: " + getMean();
      s += " stdev: " + getStdev();
      s += " variance: " + getVariance();
      s += " stderr: " + (getStdev() / getSize());
      s += " min: " + getMin();
      s += " max: " + getMax();
      return s;
    }
  }
  