// file: $isip_ifc/class/algo/MutualInformation/mi_05.cc
// version: $Id: mi_05.cc 10439 2006-02-23 20:37:12Z pannuri $
//

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

// method: apply
//
// arguments:
//  Vector<AlgorithmData>& output: (output) output data
//  const Vector< CircularBuffer<AlgorithmData> >& input: (input) input data
//
// return: a boolean value indicating status
//
// this method selects the appropriate computation methods according
// to the input data type
//
boolean MutualInformation::apply(Vector<AlgorithmData>& output_a,
		 const Vector< CircularBuffer<AlgorithmData> >& input_a) {
  
  // determine the number of input channels and force the output to be
  // that number
  //
  long len = input_a.length();
  output_a.setLength(len);
  boolean res = true;
  
  // start the debugging output
  //
  displayStart(this);
  
  // loop over the channels and call the compute method
  //
  for (long c = 0; c < len; c++) {
    
    // display the channel number
    //
    displayChannel(c);
    
    // branch on algorithm: autocorrelation only has one input,
    // crosscorrelation has two inputs
    //
    if (input_a(c)(0).getDataType() == AlgorithmData::VECTOR_FLOAT) {
      
      // call AlgorithmData::makeVectorFloat to force the output vector for
      // this channel to be a VectorFloat.
      // call AlgorithmData::getVectorFloat on the input for this channel to
      // check that the input is already a VectorFloat and return that
      // vector.
      //
      res &= compute(output_a(c).makeVectorFloat(),
		     input_a(c),
		     input_a(c)(0).getCoefType(),
		     c);
    }
    
    // for crosscorrelation the input should be a combination of two
    // VectorFloat's -- one for each signal to obtain the correlation
    // between.
    //
    else if (input_a(c)(0).getDataType() == AlgorithmData::COMBINATION) {
      
      // two input VectorFloat signals are expected here
      //
      if (input_a(c)(0).getCombination().length() != 2) {
	return Error::handle(name(), L"apply", ERR_INPUT, __FILE__, __LINE__);
      }
      
      // force the output to be a vector through
      // AlgorithmData::makeVectorFloat. for each of the two input signals
      // call getVectorFloat to obtain the VectorFloat signal.
      //
      res &= compute(output_a(c).makeVectorFloat(),
		     input_a(c)(0).getCombination()(0).getVectorFloat(),
		     input_a(c)(0).getCombination()(1).getVectorFloat(),
		     input_a(c)(0).getCoefType(),
		     c);
    }
    
    // error condition
    //
    else {
      return Error::handle(name(), L"apply", ERR_UNKTYP, __FILE__, __LINE__);
    }
    
    // set the coefficient type for the output
    //
    // branch on algorithm:
    //  Algorithm: AUTO
    //
    if ((algorithm_d == AUTO)) {
      output_a(c).setCoefType(AlgorithmData::MUTUAL_INFORMATION);
    }

    // Algorithm: CROSS
    //
    else {
      output_a(c).setCoefType(AlgorithmData::SIGNAL);
    }
  }
  
  // finish the debugging output
  //
  displayFinish(this);
  
  // exit gracefully
  //
  return res;
}

// method: compute
//
// arguments:
//  VectorFloat& output: (output) output data
//  const CircularBuffer<AlgorithmData>& input: (input) input data
//  AlgorithmData::COEF_TYPE coef_type: (input) coeff type of input signal
//  long channel_index: (input) the channel index of input signal
//
// return: a boolean value containing status
//
// this method applies the specified window on the input channel
//
boolean MutualInformation::compute(VectorFloat& output_a,
				  const CircularBuffer<AlgorithmData>& input_a,
				  AlgorithmData::COEF_TYPE coef_type_a,
				  long channel_index_a) {
  
  // for FRAME_INTERNAL mode, we just process what data we have
  //
  if (cmode_d == FRAME_INTERNAL) {
    return compute(output_a, input_a(0).getVectorFloat(), coef_type_a,
		   channel_index_a);
  }
  
  // for other modes, we error
  //
  else if (cmode_d != CROSS_FRAME) {
    return Error::handle(name(), L"compute",
			 ERR_UNSUPM, __FILE__, __LINE__);
  }
  
  // make sure there is enough data
  //
  if ((input_a.getNumForward() < getLeadingPad()) ||
      (input_a.getNumBackward() < getTrailingPad())) {
    return Error::handle(name(), L"compute", ERR_DATA, __FILE__, __LINE__);
  }
  
  // precompute some useful constants
  //
  double frame_nsamp = frame_dur_d * sample_freq_d;
  long order = order_d;
  
  double num_frames = order / frame_nsamp;
  long frame = (long)Integral::ceil(num_frames);
  
  VectorFloat buf;
  
  // algorithm: AUTO
  //
  if (algorithm_d == AUTO) {
    
    // this is the number of samples not included from the first frame
    //
    long index = frame ;
    long first_offset = frame * (long)Integral::ceil(frame_nsamp) - order;
    long first_nelem = (long)Integral::ceil(frame_nsamp) - first_offset;
    double num_left = order + frame_nsamp;
    
    // loop over the remaining data
    //
    while (num_left > 0) {
      
      // for the first frame, grab only the non-zero samples
      //
      if (index == frame) {
	buf.move(input_a(-index).getVectorFloat(),
		 first_nelem, first_offset, 0);
	num_left -= first_nelem;
      }
      
      // for all other frames, we copy the entire frame
      //
      else {
	buf.concat(input_a(-index).getVectorFloat());
	num_left -= frame_nsamp;
      }
      
      // decrement the frame counter
      //
      index--;
    }
    
       // apply the correlation function
        //
        if (implementation_d == HISTOGRAM) {
          return computeAutoMutualInformation(output_a, buf);
        }
   
    else
      return Error::handle(name(), L"compute",
			   ERR_UNSUPI, __FILE__, __LINE__);
  }
  
  // algorithm: CROSS
  //
  else if (algorithm_d == CROSS) {
    return Error::handle(name(), L"compute",
			 ERR_UNSUPA, __FILE__, __LINE__);
  }
  
  // else: unknown algorithm
  //
  else
    return Error::handle(name(), L"compute",
			 ERR_UNKALG, __FILE__, __LINE__);
}

// method: compute
//
// arguments:
//  VectorFloat& output: (output) output data
//  const VectorFloat& input: (input) input data
//  AlgorithmData::COEF_TYPE input_coef_type: (input) type of input
//  long channel_index: (input) channel index
//
// return: a boolean value indicating status
//
// this method selects the appropriate computational method to compute
// autocorrelation.
//
boolean MutualInformation::compute(VectorFloat& output_a,
			     const VectorFloat& input_a,
			     AlgorithmData::COEF_TYPE input_coef_type_a,
			     long channel_index_a) {
  
  // declare local variable
  //
  boolean status = false;

  // algorithm: AUTO
  //
  if ((algorithm_d == AUTO)) {
    
    // else: signals
    //
    if (input_coef_type_a == AlgorithmData::SIGNAL) {

      // implementation: HISTOGRAM
      //
      if (implementation_d == HISTOGRAM) {
	status = computeAutoMutualInformation(output_a, input_a);
      }      
      else {
	return Error::handle(name(), L"compute",
 			     ERR_UNKIMP, __FILE__, __LINE__);
      }      
    }
    
    // error: unknown coefficient type
    //
    else {
      return Error::handle(name(), L"compute",
			   ERR_UNCTYP, __FILE__, __LINE__);
    }
  }
  
  // error: unknown algorithm
  //
  else {
    status = Error::handle(name(), L"compute",
			   ERR_UNKALG, __FILE__, __LINE__);
  }
  
  // possibly display the data
  //
  display(output_a, input_a, name());
  
  // exit gracefully
  //
  return status;
}

// method: compute
//
// arguments:
//  VectorFloat& output: (output) output data
//  const VectorFloat& input1: (input) input data
//  const VectorFloat& input2: (input) input data
//  AlgorithmData::COEF_TYPE input_coef_type: (input) type of input
//  long channel_index: (input) channel index
//
// return: a boolean value indicating status
//
// this method selects the appropriate computational method to compute
// cross-correlation and convolution
//

boolean MutualInformation::compute(VectorFloat& output_a,
				   const VectorFloat& input1_a,
				   const VectorFloat& input2_a,   
				   AlgorithmData::COEF_TYPE input_coef_type_a,
				   long channel_index_a) {
  
  // declare local variable
  //
  boolean status = false;

  // algorithm: AUTO
  //
  if (algorithm_d == CROSS) {
    
    // implementation: HISTOGRAM
    //
    if (implementation_d == HISTOGRAM) {
      status = computeCrossMutualInformation(output_a, input1_a, input2_a);
    }
    
    // error: unknown implementation
    //      
    else {
      return Error::handle(name(), L"compute",
			   ERR_UNKIMP, __FILE__, __LINE__);
    }      
  }
  
  // error: unknown algorithm
  //   
  else {
    status = Error::handle(name(), L"compute",
			   ERR_UNKALG, __FILE__, __LINE__);
  }  
  
  // exit gracefully
  //
  return status;
}


//
// arguments:
//  VectorFloat& output: (output) Correlation coefficients
//  const VectorFloat& input: (input) input data
//
// return: a boolean value indicating status
//
// this method computes the AutoMutualInformation function for a given time
// series
//
//  H. Kantz and T. Schreiber,
//  Nonlinear Time Series Analysis, Second edition
//  Cambridge University Press, Cambridge, UK, pp. 150-152, 1999.
//
boolean MutualInformation::computeAutoMutualInformation(VectorFloat& output_a,
							const VectorFloat& input_a) {
  
  // the local variables
  //
  long N = input_a.length();
  boolean status = false;
  double element_to_insert;
  VectorFloat series_one;
  VectorFloat series_delayed;
  Histogram h1, h2, h3;
  float min_value, max_value;
  VectorFloat tmp_series_one, tmp_series_delayed;
  Vector<VectorFloat> tmp_series_joint(2);
  VectorDouble pdf_series_one, pdf_series_delayed, pdf_series_joint;
  
  // resize the output
  //
  if (!output_a.setLength(order_d)) {
    return Error::handle(name(), L"compute",
			 ERR, __FILE__, __LINE__);
  }
  
  // algorithm: AUTO
  //  in this approach, we consider the sum from order to N-1
  //
  if (algorithm_d == AUTO) {
    
    for(long current_order = 0; current_order < order_d; current_order++) {
      
      // setting the length of the time series
      //
      series_one.setLength(N - current_order - 1);
      series_one.move(input_a, N - current_order - 1, 0, 0);
      
      // for the delayed time series
      //
      series_delayed.setLength(N - current_order - 1);
      series_delayed.move(input_a, N - current_order - 1, current_order, 0);
      
      // setting dimensions of the histograms
      //
      h1.setDim(1);
      h2.setDim(1);
      h3.setDim(2);
      
      // getting the minimum and maximum in the time series
      //
      min_value = input_a.min();
      max_value = input_a.max();
      
      // setting the number of bins for the histogram
      //
      h1.setBins((long) num_bins_d, min_value, max_value);
      h2.setBins((long) num_bins_d, min_value, max_value);
      
      // setting the same number of bins in both the dimensions
      // 
      h3.setBins((long) num_bins_d, min_value, max_value);
      
      // computing the pdf of the first series
      //
      tmp_series_one.assign(series_one);
      h1.compute(tmp_series_one);
      h1.pdf(pdf_series_one);
      
      // computing pdf for the delayed time series
      //
      tmp_series_delayed.assign(series_delayed);
      h2.compute(tmp_series_delayed);
      h2.pdf(pdf_series_delayed);
      
      // histogram for two dimensions
      //
      tmp_series_joint(0).assign(series_one);
      tmp_series_joint(1).assign(series_delayed);
      h3.compute(tmp_series_joint);
      h3.pdf(pdf_series_joint);
      
      // accessing the two dimensional pdf
      //
      MatrixDouble pdf_2d(num_bins_d, num_bins_d);
      Vector<VectorLong> bin_indices;
      h3.getBinIndexCoordinates(bin_indices);

      // converting the 2D histogram to a matrix so that it can be
      // accessed using the row and column index
      //
      for (long i = 0; i < bin_indices.length(); i++) {
	long x, y;
	x = bin_indices(i)(0);
	y = bin_indices(i)(1);
	pdf_2d.setValue(y, x, pdf_series_joint(i));
      }
      
      // finding auto mutual information for the time series
      //
      double after_log = (double)0;
      double bin_information = (double)0;
      for (long x = 0; x < num_bins_d; x++) {
	if (pdf_series_delayed(x) != (double)0) {
	  for (long y = 0; y < num_bins_d; y++) {
	    if (pdf_series_one(y) != (double)0) {
	      element_to_insert = pdf_2d.getValue(x, y);
	      if (element_to_insert != (double)0) {
		element_to_insert /= pdf_series_one(y);
		element_to_insert /= pdf_series_delayed(x);
		after_log = Integral::log(element_to_insert);
		bin_information += pdf_2d.getValue(x, y) * after_log;
	      }
	    }
	  }
	}
      }
      output_a(current_order) = bin_information;
    }
    status = true;
  }
  
  // if the algorithm is not AUTO, error out
  //
  else {
    return Error::handle(name(), L"compute",
			 ERR, __FILE__, __LINE__);
  }
  
  // exit gracefully
  //
  return status;
}

// // method: computeCross
//
// arguments:
//  VectorFloat& output: (output) Correlation coefficients
//  const VectorFloat& ref: (input) reference signal
//  const VectorFloat& tst: (input) test signal
//
// return: a boolean value indicating status
//
// this method performs crossmutual information between two signals. note that
// this implementation is not symmetric - the first signal is taken as
// the reference and the length of the correlation window is taken
// as the length of the second signal.
//
boolean MutualInformation::computeCrossMutualInformation(VectorFloat& output_a,
						 const VectorFloat& input1_a,
						 const VectorFloat& input2_a) {
  
  // the local variables
  //
  boolean status = false;
  float min_value_1, max_value_1, min_value_2, max_value_2;
  VectorFloat min_value(2), max_value(2);
  VectorFloat tmp_series_one, tmp_series_delayed;
  Vector<VectorFloat> tmp_series_joint(2);
  VectorDouble pdf_series_one, pdf_series_delayed, pdf_series_joint;
  VectorDouble cdf_series_one, cdf_series_delayed, cdf_series_joint;
  VectorFloat time_series_1, time_series_2;
  time_series_1 = input1_a;
  time_series_2 = input2_a;
  Histogram h1, h2, h3;
  double element_to_insert;
  
  // create output space
  //
  if (!output_a.setLength(order_d)) {
    return Error::handle(name(), L"compute", ERR,
			 __FILE__, __LINE__);
  }
  
  if (algorithm_d == CROSS) {
    
    // from order 0 to order find cross mi
    //
    for (long current_order = 0; current_order < order_d; current_order++) {
      
      // holds the length of the time series
      //
      long length_series_1, length_series_2, length_series;
      length_series_1 = time_series_1.length();
      length_series_2 = time_series_2.length();
      
      // holds the two series with modified lengths
      //
      VectorFloat series_one, series_two;
      
      if ((length_series_1 > length_series_2)) {
	length_series = length_series_2;
	
      }
      else {
	length_series = length_series_1;
      }
      series_one.setLength(length_series);
      series_two.setLength(length_series);
      series_one.move(time_series_1, length_series, 0, 0);
      series_two.move(time_series_2, length_series, 0, 0);
      VectorFloat series_one_delayed, series_two_delayed;
      
      // setting dimensions for the histograms
      //
      h1.setDim(1);
      h2.setDim(1);
      h3.setDim(2);
      
      // getting the minimum and maximum in the time series
      //
      min_value_1 = time_series_1.min();
      max_value_1 = time_series_1.max();
      h1.setBins((long) num_bins_d, min_value_1, max_value_1);
      
      // setting the number of bins for the second time series
      //
      min_value_2 = time_series_2.min();
      max_value_2 = time_series_2.max();
      h2.setBins((long) num_bins_d, min_value_2, max_value_2);
      
      // for the tau values from 0 to current_order
      //
      series_one_delayed.move(series_one, length_series - current_order - 1, 0, 0);
      series_two_delayed.move(series_two, length_series - current_order - 1, current_order, 0);      
      // computing pdf for the first time series
      //
      tmp_series_one.assign(series_one_delayed);
      h1.compute(tmp_series_one);
      h1.pdf(pdf_series_one);

      // computing pdf for the second time series
      //
      tmp_series_delayed.assign(series_two_delayed);
      h2.compute(tmp_series_delayed);
      h2.pdf(pdf_series_delayed);
    
      // histogram for two dimensions
      //
      // assigning to vector
      //
      min_value(0) = min_value_1;
      min_value(1) = min_value_2;
      max_value(0) = max_value_1;
      min_value(1) = max_value_2;
      VectorLong num_bins(2);
      num_bins(0) = num_bins_d;
      num_bins(1) = num_bins_d;
      h3.setBins(num_bins, min_value, max_value);  
      tmp_series_joint(0).assign(series_one_delayed);
      tmp_series_joint(1).assign(series_two_delayed);
      h3.compute(tmp_series_joint);
      h3.pdf(pdf_series_joint);
     
      // accessing the two dimensional pdf 
      //
      MatrixDouble pdf_2d(num_bins_d, num_bins_d);
      Vector<VectorLong> bin_indices;
      h3.getBinIndexCoordinates(bin_indices);

      // we need to convert the vector to Matrix
      //
      for (long i = 0; i < bin_indices.length(); i++) {
	long x, y;
	x = bin_indices(i)(0);
	y = bin_indices(i)(1);
	pdf_2d.setValue(y, x, pdf_series_joint(i));
      }

      // finding the cross mutual information for the two given time
      // series
      //
      Double after_log = (double)0;
      Double bin_information = (double)0;
      for (long x = 0; x < num_bins_d; x++) {
	if (pdf_series_delayed(x) != (double)0) {
	  for (long y = 0; y < num_bins_d; y++) {
	    if (pdf_series_one(y) != (double)0) {
	      element_to_insert = pdf_2d.getValue(x, y);
	      if (element_to_insert != (double)0) {
		element_to_insert /= pdf_series_one(y);
		element_to_insert /= pdf_series_delayed(x);
		after_log = Integral::log(element_to_insert);
		bin_information += pdf_2d.getValue(x, y) * after_log;
	      }
	    }
	  }
	}
      }
      output_a(current_order) = bin_information;
    }
    status = true;
  }

  // if the algorithm is not CROSS error out
  //
  else {
    return Error::handle(name(), L"compute",
			 ERR, __FILE__, __LINE__);
  }  
  return status;
}

