// file: $isip_ifc/class/pr/RelevanceVectorMachine/rvm_05.cc
// version: $Id: rvm_05.cc 10552 2006-04-14 22:05:14Z raghavan $
//

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

// method: evaluate
//
// arguments:
//  float& score: (output) probability score
//  const VectorFloat& input: (input) test vector
//
// return: boolean value indicating status
//
// this method computes the probability that the input vector was produced by
// the model represented by this RVM classifier. This comes from an evaluation
// of the logistic sigmoid link function, sigma(y): pp. 218, section 3 in [1].
//
boolean RelevanceVectorMachine::evaluate(float& score_a,
					 const VectorFloat& input_a) {

  static VectorFloat g; // gradient of sigma(y) w.r.t the weight vector
  
  // declare local variables
  //
  float kernel_score = 0.0;
  long N = weights_d.length();
  long dim_hess = inv_hessian_d.getNumRows();

  // check degenerate case
  //
  if (dim_hess == 0) {

    // apply the logistic sigmoid link function
    //
    score_a = 0.5;
    return true;
  }
  
  // set the gradient length
  //
  long j = 0;
  g.setLength(dim_hess);

  // if the bias is not pruned, then we need to compute that gradient as well
  //
  if (dim_hess != N) {
    g(j++) = 1;
  }
  
  // evaluate the linear combination: y(x; w), equation (2) in [1]
  //

  // initialize the score to the bias term from (2) in [1].
  //
  score_a = bias_d;

  // evaluate the weighted combination from (2) for all relevance vectors
  //
  for (long i = 0; i < N; i++) {

    // compute the kernel function
    //
    kernel_d.compute(kernel_score, input_a, vectors_d(i));
    g(j++) = kernel_score;
    score_a += weights_d(i) * kernel_score;
  }

  // compute s^2
  //
  float s_squared;
  inv_hessian_d.quadratic(s_squared, g);
  
  float kappa = 1.0 / Integral::sqrt(1 + Integral::PI * s_squared / 8.0);
  
  // apply the logistic sigmoid link function
  //
  score_a = 1.0 / (1.0 + Integral::exp(-(kappa * score_a)));
  
  // exit gracefully
  //
  return true;
}

// method: loadFeatures
//
// arguments:
//  Sdb& in_sdb: (input) in-class signal database to run on
//  Sdb& out_sdb: (input) out-of-class signal database to run on
//
// return: boolean value indicating status
//
// this method loads the feature vectors used in the optimization process
//
boolean RelevanceVectorMachine::loadFeatures(Sdb& in_sdb_a, Sdb& out_sdb_a) {

  // declare local variables
  //
  String output;
  FeatureFile feature_file;
  Filename input_file_name;
  Vector<AlgorithmData> data;
  String identifier;
  Filename input_ID;
  
  long curr_num_frames = 0;    
  long total_num_frames = 0;  
  long current_file_num = 0;
  
  // setup the algorithm data structure for reading
  //
  data.setLength(1);
  data(0).makeVectorFloat();
  
  // load the audio database
  //
  if (verbosity_d >= Integral::BRIEF) {
    Console::increaseIndention();
    output.assign(L"\nloading audio database: ");
    output.concat(audio_db_file_d);
    Console::put(output);
    Console::decreaseIndention();
  }
  
  Filename audio_db_file;
  audio_db_file.assign(audio_db_file_d);
  if (!audio_db_d.open(audio_db_file)) {
    audio_db_file_d.debug(L"audio_db_file_d");
    return Error::handle(name(), L"loadFeatures - error opening audio database file", Error::ARG, __FILE__, __LINE__);
  }
  
  // loop over all in-class features
  //
  for (in_sdb_a.gotoFirst(); in_sdb_a.getName(input_ID);
       in_sdb_a.gotoNext()) {

    current_file_num++;
    
    // get audio file path
    //
    identifier.assign(input_ID);
    if (!audio_db_d.getRecord(identifier, input_file_name)) {
      identifier.debug(L"identifier");
      return Error::handle(name(), L"loadFeatures - unable to find audio file for identifier", Error::ARG, __FILE__, __LINE__);	    
    }
    
    if (verbosity_d >= Integral::BRIEF) {
      Console::increaseIndention();
      output.assign(L"\nprocessing file ");
      output.concat(current_file_num);
      output.concat(L" (");
      output.concat(identifier);
      output.concat(L")");
      output.concat(L": ");
      output.concat(input_file_name);
      Console::put(output);
      Console::decreaseIndention();
    }
  
    // read the features
    //
    if (!feature_file.open(input_file_name)) {
      input_file_name.debug(L"input_file_name");      
      return Error::handle(name(), L"loadFeatures - error reading feature file", Error::ARG, __FILE__, __LINE__);
    }

    // reset the capacity
    //
    long num_frames = feature_file.getNumFrames();
    total_num_frames += num_frames;
    in_class_data_d.setLength(total_num_frames);
    
    // loop over all features in the feature file
    //
    for (long i = 0; i < num_frames; i++) {
      
      // read the feature
      //
      feature_file.getBufferedData(data, i, 1);

      // set the feature
      //
      in_class_data_d(curr_num_frames++).assign(data(0).getVectorFloat());
    }

    // close the file descriptor
    //
    feature_file.close();        
  }

  // loop over all out-of-class features
  //
  curr_num_frames = 0;    
  total_num_frames = 0;  
  current_file_num = 0;
  
  for (out_sdb_a.gotoFirst(); out_sdb_a.getName(input_ID);
       out_sdb_a.gotoNext()) {

    current_file_num++;    

    // get audio file path
    //
    identifier.assign(input_ID);
    if (!audio_db_d.getRecord(identifier, input_file_name)) {
      identifier.debug(L"identifier");
      return Error::handle(name(), L"loadFeatures - unable to find audio file for identifier", Error::ARG, __FILE__, __LINE__);	    
    }

    if (verbosity_d >= Integral::BRIEF) {
      Console::increaseIndention();
      output.assign(L"\nprocessing file ");
      output.concat(current_file_num);
      output.concat(L" (");
      output.concat(identifier);
      output.concat(L")");
      output.concat(L": ");
      output.concat(input_file_name);
      Console::put(output);
      Console::decreaseIndention();
    }
    
    // read the features
    //
    if (!feature_file.open(input_file_name)) {
      input_file_name.debug(L"input_file_name");            
      return Error::handle(name(), L"loadFeatures - error reading feature file", Error::ARG, __FILE__, __LINE__);
    }

    // reset the capacity
    //
    long num_frames = feature_file.getNumFrames();
    total_num_frames += num_frames;
    out_class_data_d.setLength(total_num_frames);
    
    // loop over all features in the feature file
    //
    for (long i = 0; i < num_frames; i++) {

      // read the feature
      //
      feature_file.getBufferedData(data, i, 1);
      
      // set the feature
      //
      out_class_data_d(curr_num_frames++).assign(data(0).getVectorFloat());
    }
    
    // close the file descriptor
    //
    feature_file.close();    
  }
  
  // close the audio database
  //
  if (!audio_db_d.close()) {
    return Error::handle(name(), L"loadFeatures", Error::ARG, __FILE__, __LINE__);
  }
  
  // exit gracefully
  //
  return true;  
}

// method: normalizeStdNorm
//
// arguments: None.
//
// return: boolean value indicating status
//
// this method does standard normalization on the data
//
boolean RelevanceVectorMachine::normalizeStdNorm() {

  // declare local variables
  //
  long in_class_length = in_class_data_d.length();
  long out_class_length = out_class_data_d.length();
  long total_length = in_class_length + out_class_length;

  // normalize in standard normal mode
  //
  VectorFloat mean((long)(Integral::max(in_class_data_d(0).length(),
					out_class_data_d(0).length())));
  VectorFloat stddev((long)(Integral::max(in_class_data_d(0).length(),
					   out_class_data_d(0).length())));
  mean.assign(0.0);
  stddev.assign(0.0);
  
  VectorFloat tmp_vec(mean);
  
  // compute the mean and stddev over all of the data
  //
  for (long i = 0; i < in_class_length; i++) {
    mean.add(in_class_data_d(i));
    tmp_vec.square(in_class_data_d(i));
    stddev.add(tmp_vec);
  }
  for (long i = 0; i < out_class_length; i++) {
    mean.add(out_class_data_d(i));
    tmp_vec.square(out_class_data_d(i));
    stddev.add(tmp_vec);
  }
  
  mean.div((float)total_length);     // E[X]
  stddev.div((float)total_length);   // E[X^2]
  tmp_vec.square(mean);               // E[X]^2
  stddev.sub(tmp_vec);                // Var[X] = E[X^2] - E[X]^2
  stddev.sqrt();                      // stddev[X] = sqrt(Var[X])
  
  // loop over all data and normalize
  //
  for (long i = 0; i < in_class_length; i++) {
    in_class_data_d(i).sub(mean);
    in_class_data_d(i).div(stddev);
  }
  for (long i = 0; i < out_class_length; i++) {
    out_class_data_d(i).sub(mean);
    out_class_data_d(i).div(stddev);
  }
  
  // exit gracefully
  //
  return true;
}

// method: normalizeUnitRange
//
// arguments: None.
//
// return: boolean value indicating status
//
// this method does standard normalization on the data
//
boolean RelevanceVectorMachine::normalizeUnitRange() {

  // declare local variables
  //
  long in_class_length = in_class_data_d.length();
  long out_class_length = out_class_data_d.length();
  
  VectorFloat max_val((long)(Integral::max(in_class_data_d(0).length(),
					    out_class_data_d(0).length())));
  VectorFloat min_val((long)(Integral::max(in_class_data_d(0).length(),
					    out_class_data_d(0).length())));
  min_val.assign(MAX_DATA_VALUE);
  max_val.assign(MIN_DATA_VALUE);
  
  // compute the max and min over all of the data
  //
  for (long i = 0; i < in_class_length; i++) {
      for (long j = 0; j < in_class_data_d(i).length(); j++) {
	if (in_class_data_d(i)(j) > max_val(j)) {
	  max_val(j) = in_class_data_d(i)(j);
	}
	if (in_class_data_d(i)(j) < min_val(j)) {
	  min_val(j) = in_class_data_d(i)(j);
	}
      }
    }
    for (long i = 0; i < out_class_length; i++) {
      for (long j = 0; j < out_class_data_d(i).length(); j++) {
	if (out_class_data_d(i)(j) > max_val(j)) {
	  max_val(j) = out_class_data_d(i)(j);
	}
	if (out_class_data_d(i)(j) < min_val(j)) {
	  min_val(j) = out_class_data_d(i)(j);
	}
      }
    }

    // loop over all data and normalize
    //
    for (long i = 0; i < in_class_length; i++) {
      for (long j = 0; j < in_class_data_d(i).length(); j++) {
	in_class_data_d(i)(j) = 2 * (in_class_data_d(i)(j) - min_val(j)) / (max_val(j) - min_val(j)) - 1;
      }
    }
    for (long i = 0; i < out_class_length; i++) {
      for (long j = 0; j < out_class_data_d(i).length(); j++) {
	out_class_data_d(i)(j) = 2 * (out_class_data_d(i)(j) - min_val(j)) / (max_val(j) - min_val(j)) - 1;
      }
    }

    // exit gracefully
    //
    return true;
}

// method: normalizeUnitVector
//
// arguments: None.
//
// return: boolean value indicating status
//
// this method does standard normalization on the data
//
boolean RelevanceVectorMachine::normalizeUnitVector() {

  // declare local variables
  //
  long in_class_length = in_class_data_d.length();
  long out_class_length = out_class_data_d.length();
    
  // loop over the within-class data
  //
    for (long i = 0; i < in_class_length; i++) {
      float max_val = in_class_data_d(i).max();
      float min_val = in_class_data_d(i).min();
      for (long j = 0; j < in_class_data_d(i).length(); j++) {
	in_class_data_d(i)(j) = 2 * (in_class_data_d(i)(j) - min_val) / (max_val - min_val) - 1;
      }
    }

    // loop over the out-of-class data
    //
    for (long i = 0; i < out_class_length; i++) {
      float max_val = out_class_data_d(i).max();
      float min_val = out_class_data_d(i).min();
      for (long j = 0; j < out_class_data_d(i).length(); j++) {
	out_class_data_d(i)(j) = 2 * (out_class_data_d(i)(j) - min_val) / (max_val - min_val) - 1;
      }
    }
    
    // exit gracefully
    //
    return true;
}

// method: initKernel
//
// arguments: none
//
// return: boolean value indicating status
//
// this method initializes the kernel object used during both training
// and evaluation
//
boolean RelevanceVectorMachine::initKernel() {
  
  // declare local variables
  //
  VectorFloat constants;  
  boolean status = false;
  
  // linear kernel
  //
  if (implementation_d == LINEAR) {
    status = kernel_d.setAlgorithm(Kernel::LINEAR);
    status &= kernel_d.setConstants(constants);
  }

  // polynomial kernel
  //  
  else if (implementation_d == POLYNOMIAL) {
    status = kernel_d.setAlgorithm(Kernel::POLYNOMIAL);
    constants.setLength(1);
    constants(0) = polynomial_degree_d;
    status &= kernel_d.setConstants(constants);
  }
  
  // rbf kernel
  //  
  else if (implementation_d == RBF) {
    status = kernel_d.setAlgorithm(Kernel::RBF);
    constants.setLength(1);
    constants(0) = rbf_gamma_d;
    status &= kernel_d.setConstants(constants);
  }
  
  // sigmoid kernel
  //  
  else if (implementation_d == SIGMOID) {
    status = kernel_d.setAlgorithm(Kernel::RBF);
    constants.setLength(2);
    constants(0) = sigmoid_kappa_d;
    constants(1) = sigmoid_delta_d;    
    status &= kernel_d.setConstants(constants);
  }
  
  // unknown implementation
  //  
  else {
    return Error::handle(name(), L"init - unknown implementation", Error::ARG, __FILE__, __LINE__);
  }
  
  // exit gracefully
  //
  return status;  
}


// method: writeModel
//
// arguments: none
//
// return: boolean value indicating status
//
// this method writes the relevance vector model to file
//
boolean RelevanceVectorMachine::writeModel() {
  
  // declare local variables
  //
  StatisticalModel stat_model(StatisticalModel::RELEVANCE_VECTOR_MODEL); 
  RelevanceVectorModel& rvm_model = stat_model.getRelevanceVectorModel();

  bias_d.debug(L"bias");

  // retrieve the relevant data structures form the model
  //
  Float& bias = rvm_model.getBias();
  Vector<Kernel>& kernels = rvm_model.getKernels();
  VectorFloat& weights = rvm_model.getWeights();
  MatrixFloat& inv_hessian = rvm_model.getInvHessian();
  VectorByte& targets = rvm_model.getTargets();
  Vector<VectorFloat>& relev_vectors = rvm_model.getRelevanceVectors();
  
  // open the output file
  //
  Sof sof_file;
  
  if (output_type_d == TEXT) {
    if (!sof_file.open(output_file_d, File::WRITE_ONLY, File::TEXT)) {
      return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
    }
  }

  else {
    if (!sof_file.open(output_file_d, File::WRITE_ONLY, File::BINARY)) {
      return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
    }
  }

  // setup the threshold
  //
  if (!bias.assign(bias_d)) {
    return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
  }

  // setup the kernel
  //
  kernels.setLength(1);
  
  if (!kernels(0).assign(kernel_d)) {
    return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
  }

  // setup the weights
  //
  if (!weights.assign(weights_d)) {
    return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
  }

  // setup the inverse hessian
  //
  if (!inv_hessian.assign(inv_hessian_d)) {
    return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
  }

  // setup the targets
  //
  if (!targets.assign(targets_d)) {
    return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
  }
  
  // setup the targets
  //
  if (!relev_vectors.assign(vectors_d)) {
    return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
  }
  
  // write the statistical model
  //
  if (!stat_model.write(sof_file, (long)0)) {
    return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
  }
  
  // close the file descriptor
  //
  sof_file.close();
  
  // exit gracefully
  //
  return true;
}

// method: evaluate
//
// arguments: None
//
// return: boolean value indicating status
//
// this method computes the probability that the input vector was produced by
// the model represented by this RVM classifier. This comes from an evaluation
// of the logistic sigmoid link function, sigma(y): pp. 218, section 3 in [1].
//
boolean RelevanceVectorMachine::evaluate() {
  
  // initialization
  //
  VectorByte hyp;
  long num_vecs = in_class_data_d.length();
  hyp.setLength(num_vecs);
  
  // classify
  //
  for (long i = 0; i < num_vecs; i++) {
    
    float score = 0.0;
    evaluate(score, in_class_data_d(i));
    
    String out(L"score for vector ");
    out.concat(i);
    out.concat(L" = ");
    out.concat(score);
    Console::put(out);
    
    byte test_class = 1;
    if (score < 0.5) {
      test_class = 0;
    }
    
    hyp(i) = test_class;
  }
  
  // exit gracefully
  //
  return true;
}
