// file: $isip/class/stats/MixtureModel/mm_07.cc
// version: $Id: mm_07.cc 7556 2001-12-26 22:32:53Z alphonso $
//

// isip include files
//
#include "MixtureModel.h"

// method: add
//
// arguments:
//  StatisticalModelBase& ptr: (input) pointer
//
// return: a boolean value indicating status
//
// add models to models_d
//
boolean MixtureModel::add(StatisticalModelBase& ptr_a) {

  // declare statistical model
  //
  StatisticalModel m;

  // assign input pointer to StatisticalModel
  //
  m.assign(ptr_a);

  // set the mode of the model
  //
  m.setMode(mode_d);
  
  // add StatisticalModel to list
  //
  models_d.insert(&m);
        
  // exit gracefully
  //
  return true;
}

// method: setModels
//
// arguments:
//  SingleLinkedList<StatisticalModel>& ptr: (input) pointer
//
// return: a boolean value indicating status
//
// replace models_d with the input
//
boolean MixtureModel::setModels(SingleLinkedList<StatisticalModel>& ptr_a) {

  // assign the models
  //
  models_d.assign(ptr_a);

  // set each model to be in the compute mode that we are in
  //
  for (boolean more_models = models_d.gotoFirst();
       more_models; more_models = models_d.gotoNext()) {
    
    models_d.getCurr()->setMode(mode_d);
  }

  // exit gracefully
  //
  is_valid_d = false;
  return true;
}

// method: setWeights
//
// arguments:
//  const VectorFloat& arg: (input) vector of weights
//
// return: a boolean value indicating status
//
// this method sets the weight for each mixture
//
boolean MixtureModel::setWeights(const VectorFloat& arg_a) {

  // set the weight
  //
  weights_d.assign(arg_a);

  // exit gracefully
  //
  is_valid_d = false;
  return true;
}

// method: setMode
//
// arguments:
//  MODE arg: (input) mode to set
//
// return: a boolean value indicating status
//
// this method sets the computation mode
//
boolean MixtureModel::setMode(MODE arg_a) {

  // set computation mode of all the models in list
  //
  for (boolean more_models = models_d.gotoFirst();
       more_models; more_models = models_d.gotoNext()) {
    models_d.getCurr()->setMode(arg_a);
  }

  // change the weights if necessary
  //
  if ((mode_d == PRECOMPUTE) && (is_valid_d)) {
    weights_d.exp();
  }
  
  // force reinitialization
  //
  is_valid_d = false;
  mode_d = arg_a;

  // exit gracefully
  //
  return true;
}

// method: init
//
// arguments: none
//
// return: a boolean value indicating status
//
// this method precomputes the necessary data for fast computation
//
boolean MixtureModel::init() {

  // if we are not in the precompute mode, we don't need to do anything.
  //
  if (mode_d != PRECOMPUTE) {
    is_valid_d = true;
    return true;
  }

  // if the data are valid, we also don't need to do anything.
  //
  else if (is_valid_d) {
    return true;
  }

  // check the arguments
  //
  long len = weights_d.length();

  if (len <= 0) {
    is_valid_d = false;
    return false;
  }
       
  // init all the models in list
  //
  for (boolean more_models = models_d.gotoFirst();
       more_models; more_models = models_d.gotoNext()) {
    models_d.getCurr()->init();
  }

  // convert to natural log scaling
  //
  weights_d.log();
  
  // exit gracefully
  //
  return (is_valid_d = true);
}

// method: getWeights
//
// arguments:
//  VectorFloat& weights: (output) output weight vector
//
// return: a boolean value indicating status
//
// this method gives the weights of the Mixture model
//
boolean MixtureModel::getWeights(VectorFloat& weights_a) const {

  // if the weights have not been initialized, return them
  //
  if ((mode_d == NONE) ||
      ((mode_d == PRECOMPUTE) && (!is_valid_d))) {
    return weights_a.assign(weights_d);
  }

  // else: convert
  //
  else {
    return weights_a.exp(weights_d);
  }
}

// method: isNormalized
//
// arguments:
//  float norm: (input) the value that the weights should sum to
//
// return: true if the weights sum up to 'norm', false otherwise
//
// this method determines if the weights are normalized to a certain 'norm'
// value
//
boolean MixtureModel::isNormalized(float norm_a) const {

  if ((mode_d == NONE) ||
      ((mode_d == PRECOMPUTE) && (!is_valid_d))) {

    // loop over the weights and sum
    //
    float sum = weights_d.sum();

    // are we normalized?
    //
    return (Integral::almostEqual(sum, norm_a));
  }

  // else we are in PRECOMPUTE mode and the weights have been initialized
  //
  else {

    // loop over the weights and sum
    //
    float sum = 0;
    long length = weights_d.length();
    for (long i = 0; i < length; i++) {
      sum += Integral::exp(weights_d(i));
    }

    // are we normalized?
    //
    return (Integral::almostEqual(sum, norm_a));
  }
}

// method: initializeWeights
//
// arguments: none
//
// return: boolean value indicating status
//
// this method initialize the weight vector
//
boolean MixtureModel::initializeWeights() {

  // set the weight vector to be the same dimension as the number of models
  //
  if (weights_d.length() != models_d.length()) {
    weights_d.setLength(models_d.length());
  }
  
  // mode: NONE
  //
  if ((mode_d == NONE) || (!is_valid_d)) {

    // compute the weight for each mixture
    //
    float weight = 1.0 / (float)weights_d.length();
    
    // initialize the weight vector
    //
    for (int i=0; i < weights_d.length(); i++) {
      weights_d(i) = weight;
    }
  }

  // mode: PRECOMPUTE
  //
  else {

    // compute the weight for each mixture
    //
    float weight = -log(weights_d.length());
    
    // initialize the weight vector
    //
    for (int i=0; i < weights_d.length(); i++) {
      weights_d(i) = weight;
    }
    
  }
  
  // exit gracefully
  //
  return true;
}

// method: normalizeWeights
//
// arguments:
//  float norm: (input) the value that the weights should sum to
//
// return: boolean value indicating status
//
// this method renormalizes the weight vector so that the weights sum
// to the input 'norm' value.
//
boolean MixtureModel::normalizeWeights(float norm_a) {

  // mode: NONE
  //
  if ((mode_d == NONE) || (!is_valid_d)) {

    // loop over the weights and sum
    //
    float sum = weights_d.sum();

    // divide each weight by the sum and multiply each by norm_a to
    // renormalize to norm_a
    //
    weights_d.mult(norm_a / sum);
  }

  // mode: PRECOMPUTE
  //
  else {

    // pull the weights back to a linear scale
    //
    weights_d.exp();
    
    // loop over the weights and sum
    //
    float sum = weights_d.sum();

    // divide each weight by the sum and multiply each by norm_a
    // to renormalize to norm_a
    //
    weights_d.mult(norm_a / sum);

    // now make the weights log again
    //
    weights_d.log();
  }

  // exit gracefully
  //
  return true;
}
