// file: $isip_ifc/class/stat/RelevanceVectorModel/rvm_08.cc
// version: $Id: rvm_08.cc 10423 2006-02-09 00:52:30Z raghavan $
//

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

// method: getLogLikelihood
//
// arguments:
//  const VectorFloat& input: (input) test vector
//
// return: float value giving the log-likelihood of the data given the model
//
// this method gives the log likelihood of the test vector.
// log-likelihood will be computed as base 10 logarithm of distance.
//
float RelevanceVectorModel::getLogLikelihood(const VectorFloat& input_a) {

  // check for initialization
  //
  if ((!is_valid_d) && (!init())) {
    Error::handle(name(), L"getLogLikelihood", Error::ARG,
		  __FILE__, __LINE__);
  }

  static VectorFloat sv;  
  static VectorFloat g; // gradient of sigma(y) w.r.t the weight vector

  // declare local variables
  //
  float score = 1.0;
  float kernel_score = 0.0;
  long N = weights_d.length();
  long dim_hess = inv_hessian_d.getNumRows();
  
  if (algorithm_d == SINGLE_KERNEL &&
      implementation_d == LOGISTIC_SIGMOID_LINK) {
    
    // check degenerate case
    //
    if (dim_hess == 0) {
      
      // apply the logistic sigmoid link function
      //
      score = 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 = bias_d;
    
    // evaluate the weighted combination from (2) for all relevance vectors
    //
    for (long i = 0; i < N; i++) {
      
      // compute the kernel function
      //
      sv.assign(vectors_d(i));
      kernels_d(0).compute(kernel_score, input_a, sv);
      
      g(j++) = kernel_score;
      score += (float)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 = 1.0 / (1.0 + Integral::exp(-(kappa * score)));
  }

  // unknown algorithm implementation
  //
  else {
    return Error::handle(name(), L"getLogLikelihood", Error::ARG, __FILE__, __LINE__);
  }
  
  // exit gracefully
  //
  return Integral::log(score);
}

// method: accumulate
//
// arguments:
//  VectorFloat& data: (input) feature vector
//
// return: boolean value indicating status
//
// this method accumulates the model parameters using the input features
//
boolean RelevanceVectorModel::accumulate(VectorFloat& data_a) {
  
  return Error::handle(name(), L"accumulate", Error::NOT_IMPLEM,
		       __FILE__, __LINE__);
}

// method: initialize
//
// arguments:
//  VectorFloat& param: (input) initialization parameters
//
// return: boolean value indicating status
//
// this method initializes the model parameters using the accumulated features
//
boolean RelevanceVectorModel::initialize(VectorFloat& param_a) {

  return Error::handle(name(), L"initialize", Error::NOT_IMPLEM,
		       __FILE__, __LINE__);

}

// method: update
//
// arguments:
//  VectorFloat& varfloor: (input) variance floor
//  long min_count: (input) minimum model count
//
// return: a boolean value indicating status
//
// this method updates the statistical model parameters using the
// accumulated statistics during training
//
boolean RelevanceVectorModel::update(VectorFloat& varfloor_a, long min_count_a) {

  return Error::handle(name(), L"update", Error::NOT_IMPLEM,
		       __FILE__, __LINE__);
}

// method: accumulate
//
// arguments:
//  VectorDouble& param: (input) training parameters
//  VectorFloat& data: (input) observations
//  boolean precomp: (input) flag that indicate if data is precomputed
//
// return: a boolean value indicating status
//
// this method accumulates the statistics for the model which are
// needed to update the model parameters during training
//
boolean RelevanceVectorModel::accumulate(VectorDouble& param_a,
					 VectorFloat& data_a,
					 boolean precomp_a) {

  return Error::handle(name(), L"accumulate", Error::NOT_IMPLEM,
		       __FILE__, __LINE__);
}

// method: getDistanceProb
//
// arguments:
//  const VectorFloat& input: (input) test vector
// return: probability score
//
// 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].
//
float RelevanceVectorModel::getDistanceProb(const VectorFloat& input_a) {
  
  static VectorFloat g; // gradient of sigma(y) w.r.t the weight vector
  
  
  // declare local variables
  //
  float score;
  float kernel_score = 0.0;
  long N = weights_d.length();
  long dim_hess = inv_hessian_d.getNumRows();
  VectorFloat input;
  input.assign(input_a);
  
  // check degenerate case
  //
  if (dim_hess == 0) {
    
    // apply the logistic sigmoid link function
    //
    score = 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 = bias_d;

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

    // compute the kernel function
    //
    if (algorithm_d == MULTIPLE_KERNEL) {
      kernels_d(i).compute(kernel_score, (VectorFloat&)input_a, vectors_d(i));
    }
    else if (algorithm_d == SINGLE_KERNEL) {
      kernels_d(0).compute(kernel_score, input, vectors_d(i));
    }
    else {
      return Error::handle(name(), L"getDistanceProb",
			   Error::NOT_IMPLEM, __FILE__, __LINE__);
    }
    g(j++) = kernel_score;
    score += 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 = 1.0 / (1.0 + Integral::exp(-(kappa * score)));
  
  // exit gracefully
  //
  return score;
}
