package yi.stat.em;

import java.util.*;
import org.jmat.data.*;
import java.io.*;
import yi.stat.em.*;


/**
 * SimplifiedEMCumulator doesn't count on co-variance.  Instead, it only
 * uses variances for multiple variable Gaussian.
 *
 * It is useful when the data are not so many to form a good Gaussian model.
 */

public class SimplifiedEMCumulator extends yi.stat.em.GaussianComponent {
  double MIN_VARIANCE = 0; // sometimes, the variance of a specified dimension
  // is 0, which doesn't make sense to some
  // applications.  Change it to MIN_VARIANCE if it is 0
  double MAX_VARIANCE = 0; // sometimes, the variance of a specified dimension
  // is 0, which doesn't make sense to some
  // applications.  Change it to MIN_VARIANCE if it is 0
  public SimplifiedEMCumulator() {
    super();
  }

  public SimplifiedEMCumulator(double min_variance) {
    super();
    MIN_VARIANCE = min_variance;
  }

  public SimplifiedEMCumulator(double min_variance, double max_variance) {
    super();
    MIN_VARIANCE = min_variance;
    MAX_VARIANCE = max_variance;
  }

  public double[][] getCovariance() throws GaussianModelSingularException{
    double[][] covariance = super.getCovariance();
    // only use the elements on the diagonal of the covariance matrix
    for (int i=0; i<covariance.length; i++) {
      for (int j=0; j<covariance.length; j++) {
        if (i==j) {
          if (MIN_VARIANCE > 0 && covariance[i][i] < MIN_VARIANCE) covariance[i][i]  = MIN_VARIANCE;
          if (MAX_VARIANCE > 0 && covariance[i][i] > MAX_VARIANCE) covariance[i][i] = MAX_VARIANCE;
        } else {
          covariance[i][j] = 0;
        }
      }
    }
    return covariance;
  }

  public double getDensity(double[] data) throws GaussianModelSingularException {

    int data_length = getDataLength();
    // if there is no data in the EM model, return probability as 0 always
    if (data_length == 0) return 0;

    Matrix mx_data = new Matrix(data, data_length);

    double[] mean = getMeans();

    Matrix mx_mean = new Matrix(mean, data_length);

    double[][] covariance = getCovariance();
//    // only use the elements on the diagonal of the covariance matrix
//    for (int i=0; i<covariance.length; i++) {
//      for (int j=0; j<covariance.length; j++) {
//        if (i==j) {
//          if (MIN_VARIANCE > 0 && covariance[i][i] < MIN_VARIANCE) covariance[i][i]  = MIN_VARIANCE;
//          if (MAX_VARIANCE > 0 && covariance[i][i]  > MAX_VARIANCE) covariance[i][i] = MAX_VARIANCE;
//        } else {
//          covariance[i][j] = 0;
//        }
//      }
//    }
    if (covariance == null) {
      System.err.println("covariance == null.  Probability is assigned to 0");
    }
    Matrix mx_covariance = new Matrix(covariance);

    AbstractMatrix mx_diff = mx_data.minus(mx_mean);
    AbstractMatrix mx_diff_trans = mx_diff.transpose();
    AbstractMatrix mx_covariance_inv = getCovarianceInverse();
    AbstractMatrix mx_up = mx_diff_trans.times(mx_covariance_inv).times(mx_diff);
    double up = - mx_up.toDouble() / 2;
    double densityDenominator = getDensityDenominator();
    return Math.exp(up) / densityDenominator;
  }

  public boolean fallenIn(double[] data, double threshold) throws GaussianModelSingularException {

    int data_length = getDataLength();
    // if there is no data in the EM model, return probability as 0 always
    if (data_length == 0) return false;

    double[] mean = getMeans();

    double[][] covariance = getCovariance();
    // only use the elements on the diagonal of the covariance matrix
    for (int i=0; i<covariance.length; i++) {
      double sigma = covariance[i][i] ;
//      if (MIN_VARIANCE > 0 && sigma < MIN_VARIANCE) sigma = MIN_VARIANCE;
//      if (MAX_VARIANCE > 0 && sigma > MAX_VARIANCE) sigma= MAX_VARIANCE;
      double t = threshold * Math.sqrt(sigma);
      double diff = Math.abs(data[i] - mean[i]);
      if (diff >= t) return false;
    }
    return true;
  }


  public void printSigmas(double threshold) throws GaussianModelSingularException {

    double[][] covariance = getCovariance();
    // only use the elements on the diagonal of the covariance matrix
    for (int i = 0; i < covariance.length; i++) {
      double sigma = covariance[i][i];
      double t = threshold * Math.sqrt(sigma);
      if (i!=0) System.out.print(", ");
      System.out.print(t);
    }
    System.out.println();
  }

  public void printSigmas() throws GaussianModelSingularException {

    double[][] covariance = getCovariance();
    // only use the elements on the diagonal of the covariance matrix
    for (int i = 0; i < covariance.length; i++) {
      double sigma = covariance[i][i];
      if (i!=0) System.out.print(", ");
      System.out.print(sigma);
    }
    System.out.println();
  }
}