// file: $isip_ifc/class/algo/CorrelationIntegral/corrint_05.cc
// version: $Id: corrint_05.cc 10607 2006-08-20 23:07:13Z ss754 $
//

// isip include files
//
#include "CorrelationIntegral.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 calls computation method with appropriate input and
// output data types
//
boolean CorrelationIntegral::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;

  frame_index_d++;
  
  // 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);

    // call AlgorithmData::makeMatrixFloat to force the output vector for
    // this channel to be a MatrixFloat, call AlgorithmData::getVectorFloat
    // on the input for this channel to check that the input is
    // already a VectorFloat and return that vector.
    //
    if (input_a(c)(0).getDataType() == AlgorithmData::MATRIX_FLOAT) {
      
      res &= compute(output_a(c).makeVectorFloat(),
		     input_a(c)(0).getMatrixFloat(),
		     input_a(c)(0).getCoefType(), c);
    }

    // error unknown coefficient type
    //
    else {
      return Error::handle(name(), L"apply", ERR_UNCTYP,
			   __FILE__, __LINE__);
    }
    
    // set the coefficient type for the output
    //    
    output_a(c).setCoefType(AlgorithmData::CORRELATION_INTEGRAL);
  }

  // possibly display the data
  //
  display(output_a, input_a, name());
    
  // finish the debugging output
  //
  displayFinish(this);

  // exit gracefully
  //
  return res;
}

// method: compute
//
// arguments:
//  VectorFloat& output: (output) CorrelationIntegral matrix
//  const MatrixFloat& input: (input) input RPS matrix
//  AlgorithmData::COEF_TYPE input_coef_type: (input) type of input
//  long index: (input) channel number
//
// return: a boolean value indicating status
//
// this method computes the Correlation Integral from a signal
//
boolean CorrelationIntegral::compute(VectorFloat& output_a,
			    const MatrixFloat& input_a,
			    AlgorithmData::COEF_TYPE input_coef_type_a,
			    long index_a) {
  MatrixFloat rps(input_a);
  
  if (algorithm_d == NORMAL) {
    
    // Spacing Mode: Linear Epsilon Spacing
    //
    if (spacing_mode_d == LINEAR_EPSILON_SPACING) {
      
      setEpsilon(epsilon_min_d, epsilon_max_d, epsilon_res_d);
      
      long num_rows = rps.getNumRows();

      // if matrix is from RPS block then delete last (continuity flag)
      // column
      //
      if (input_coef_type_a == AlgorithmData::RPS) {
	long num_columns = rps.getNumColumns();
	rps.setDimensions(num_rows, num_columns - 1, true);
      }
      
      if (num_rows > (long)0) {
	computeCorrelationIntegral(output_a, rps);
      }
    }
  
    // Spacing Mode: User Specified Epsilon Spacing
    //
    else if (spacing_mode_d == USER_SPECIFIED_EPSILON_SPACING) {
      
      long vec_len = epsilon_d.length();
      
      if (vec_len == 0) {
	return Error::handle(name(), L"compute",
			     ERR_SETEPS, __FILE__, __LINE__);
      }

      // Find the minimum and maximum values of epsilon and epsilon res
      // from the epsilon vector provided by the user
      //
      setEpsilonRes((long)vec_len);
      setEpsilonMin((float)epsilon_d(0));
      setEpsilonMax((float)epsilon_d(vec_len - 1));
      
      long num_rows = rps.getNumRows();
      
      // if matrix is from RPS block then delete last (continuity flag)
      // column
      //
      if (input_coef_type_a == AlgorithmData::RPS) {
	long num_columns = rps.getNumColumns();
	rps.setDimensions(num_rows, num_columns - 1, true);
      }
      
      if (num_rows > (long)0) {
	computeCorrelationIntegral(output_a, rps);	
      }   
    }  
  }
  else {
    return Error::handle(name(), L"compute", ERR_UNKALG, __FILE__, __LINE__);
  }    
  
  // exit gracefully
  //  
  return true;  
}

// method: computeCorrelationIntegral
// 
// arguments:
//  VectorFloat& correlation_integral: (output) correlation integral matrix
//  const MatrixFloat& rps: (input) RPS matrix
//
//  return: a boolean value indicating status
//
// this method computes the CorrelationIntegral from an input RPS matrix
//
boolean CorrelationIntegral::computeCorrelationIntegral(VectorFloat&
							correlation_integral_a,
							const MatrixFloat& rps_a) {

  // Checking for potential problems before beginning
  //
  if (epsilon_min_d == (float) 0) {
    return Error::handle(name(), L"computeCorrelationIntegral", ERR_EPSMIN, __FILE__, __LINE__);
  }

  if (epsilon_max_d == (float) 0) {
    return Error::handle(name(), L"computeCorrelationIntegral", ERR_EPSMAX, __FILE__, __LINE__);
  }
  
  if (epsilon_res_d == (long) 0) {
    return Error::handle(name(), L"computeCorrelationIntegral", ERR_EPSRES, __FILE__, __LINE__);
  }

  if (epsilon_max_d < epsilon_min_d) {
    return Error::handle(name(), L"computeCorrelationIntegral", ERR_EPS, __FILE__, __LINE__);
  }
  
  long i, j;
  long rps_dim = rps_a.getNumColumns();
  
  // Number of points on the atttractor's trajectory
  //
  long N = rps_a.getNumRows();

  // Distance between two points, and the radius of the neighborhood
  //
  float dist, epsilon;  
  float corrint;
  VectorFloat temp1, temp2, diff;
  temp1.setLength(rps_dim);
  temp2.setLength(rps_dim);
  diff.setLength(rps_dim);
  correlation_integral_a.setLength(epsilon_res_d);

  // Loop from Epsilon_min through Epsilon_max to compute the correlation
  // integral for each neighborhood radius, epsilon.
  //
  for (long eps_index = 0 ; eps_index < epsilon_res_d ; eps_index++) {
    
    long count = 0;
    
    // The radius of the hypersphere
    //
    if (epsilon_res_d == (long) 1) {
      epsilon = epsilon_min_d;
    }
    else {
      epsilon = epsilon_d(eps_index);
    }
    
    for (i = 0 ; i < N - theiler_correction_d ; i++) {
      for (j = i + theiler_correction_d ; j < N ; j++) {      
	rps_a.getRow(temp1, i);
	rps_a.getRow(temp2, j);
	diff.sub(temp1, temp2);
	
	// Distance between the i-j'th points on the attractor's trajectory
	//
	dist = diff.norm();  
	
	// Check if the distance of points in the search space is
	// less than epsilon
	//
	if (dist < epsilon) {
	  count++;
	}	   
      }
    }
    corrint = ((float)2 * count) / ((float)(N - theiler_correction_d)
				    * (N - theiler_correction_d - 1) );
    
    // Performing the appropriate scaling
    //
    float log_corrint;
    if (scaling_d == LOGSCALE) {
      log_corrint = (float) Integral::log(corrint);
      if (!isinf(log_corrint))
	correlation_integral_a(eps_index) = log_corrint;
      else
	correlation_integral_a(eps_index) = DEF_NEG_INF;
    }
    else if (scaling_d == LINSCALE) {
      correlation_integral_a(eps_index) = corrint;
    }
    else if (scaling_d == LOG2SCALE) {
      log_corrint = (float) Integral::log2(corrint);
      if (!isinf(log_corrint))
	correlation_integral_a(eps_index) = log_corrint;
      else
	correlation_integral_a(eps_index) = DEF_NEG_INF;
    }
    else if (scaling_d == LOG10SCALE) {
      log_corrint = (float) Integral::log10(corrint);
      if (!isinf(log_corrint))
	correlation_integral_a(eps_index) = log_corrint;
      else
	correlation_integral_a(eps_index) = DEF_NEG_INF;
    }      
  }
    
  // exit gracefully
  //
  return true;
}

