// file: $isip/class/pr/SupportVectorMachine/svm_05.cc
//

// isip include files
//
#include "SupportVectorMachine.h"
#include <stdlib.h>
#include <File.h>
#include <Console.h>

// method: evaluateOutput
//
// arguments:
//  const VectorFloat& point: (input) first example index
//
// return: result of the evaluation function
//
// this method computes the output of a specific example
//
float SupportVectorMachine::evaluateOutput(VectorFloat& point) {

  // declare local variables
  //
  float value = 0.0;  
  float result = -(float)bias_d;

  // evaluate the output at the example
  //
  for (int i=0; i < max_points_d; i++) {
    kernel_d.compute(value, point, points_d(i));
    result += target_d(i) * alpha_d(i) * value;
  }

  // return the result
  //
  return result;
}

// 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 SupportVectorMachine::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;

    target_d.setLength(total_num_frames);
    points_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
      //
      target_d(curr_num_frames) = 1.0;
      points_d(curr_num_frames++).assign(data(0).getVectorFloat());
    }

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

  // loop over all out-of-class features
  //
  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;

    target_d.setLength(total_num_frames);
    points_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
      //
      target_d(curr_num_frames) = -1.0;
      points_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: init
//
// arguments: none
//
// return: boolean value indicating status
//
// this method initializes the kernel object used during both training
// and evaluation
//
boolean SupportVectorMachine::init() {

  // 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 support vector model to file
//
boolean SupportVectorMachine::writeModel() {

  // declare local variables
  //
  StatisticalModel stat_model(StatisticalModel::SUPPORT_VECTOR_MODEL); 
  SupportVectorModel& svm_model = stat_model.getSupportVectorModel();

  // retrieve the relevant data structures form the model
  //
  Double& bias = svm_model.getBias();  
  Vector<Kernel>& kernels = svm_model.getKernels();
  Vector<Double>& alphas = svm_model.getAlphas();
  Vector<VectorFloat>& supp_vectors = svm_model.getSupportVectors();

  // 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__);
  }

  // determine the number of support vectors
  //
  long num_vectors = 0;
  
  for (long i = 0; i < (long)max_points_d; i++) {
    if ((float)alpha_d(i) > 0.0) {
      num_vectors++;
    }
  }
  
  // setup the support vectors and alphas
  //
  alphas.setLength(num_vectors);  
  supp_vectors.setLength(num_vectors);

  for (long i = 0, num_vectors = 0; i < (long)max_points_d; i++) {
    if ((float)alpha_d(i) > 0.0) {

      // append the support vector
      //
      if (!supp_vectors(num_vectors).assign(points_d(i))) {
	return Error::handle(name(), L"writeModel", Error::ARG, __FILE__, __LINE__);
      }

      // append the alpha
      //
      if (!alphas(num_vectors++).assign(alpha_d(i) * target_d(i))) {
	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: train
//
// arguments: none
//
// return: boolean value indicating status
//
// this method loads the training data and determines the support vectors
// using the Sequential Minimal Optimization algorithm
//
boolean SupportVectorMachine::train() {

  // initialize the random number generator
  //
  srand48(time(NULL));
  
  // initialize the number of traininf examples
  //
  max_points_d = points_d.length();

  // make sure we have a valid number of points
  //
  if ((long)max_points_d < 1) {
    return Error::handle(name(), L"train - zero length training vector", Error::ARG, __FILE__, __LINE__);
  }

  // number of poinst should match the number of targets
  //
  if ((long)max_points_d != target_d.length()) {
    return Error::handle(name(), L"train - number of training examples don't match the number of targets", Error::ARG, __FILE__, __LINE__);
  }

  // initialize the kernel parameters
  //
  if (!init()) {
    return Error::handle(name(), L"run - error in initializing kernel", Error::ARG, __FILE__, __LINE__);
  }
  
  // branch on the algorithm type
  //
  if (algorithm_d == SEQUENTIAL_MINIMAL_OPTIMIZATION) {
    
    // determine the support vectors
    //
    if (!sequentialMinimalOptimization()) {
      return Error::handle(name(), L"train", Error::ARG, __FILE__, __LINE__);
    }
  }

  // unknown algorithm
  //  
  else {
    return Error::handle(name(), L"train - unknown algorithm", Error::ARG, __FILE__, __LINE__);
  }
  
  // exit gracefully
  //
  return true; 
}

// method: sequentialMinimalOptimization
//
// arguments: none
//
// return: boolean value indicating status
//
// References:
//
// J. C. Platt, "Fast Training of Support Vector Machines using
// Sequential Minimal Optimization".  In B. Scholkopf, C. J. C.
// Burges and A. J. Smola, editors, "Advances in K Methods
// - Support Vector Learning", pp 185-208, MIT Press, 1998.
//
boolean SupportVectorMachine::sequentialMinimalOptimization() {

  // declare local variables
  //
  String output;
  
  int numChanged = -1;
  int examineAll = -1;

  // initialize the Lagrange multipliers
  //
  alpha_d.setLength((long)max_points_d);
  
  for (int i=0 ; i < (long)max_points_d; i++) {
    alpha_d(i) = 0.0;
  }
  
  // initialize the bias
  //
  bias_d = 0.0;

  // initialize the error cache
  // Note: at start up all the Lagrange multipliers are zero, hence, the
  // Ei = evaluateOutput(i) - target(i) = - target(i)
  //
  error_cache_d.setLength((long)max_points_d);
  
  for (int i=0; i < (long)max_points_d; i++) {
    error_cache_d(i) = -target_d(i);
  }

  numChanged = 0;
  examineAll = 1;
  
  while ((numChanged > 0) || examineAll) {

    numChanged = 0;
  
    if (examineAll) {
      for (int i=0; i < (long)max_points_d; i++) {
	numChanged += examineExample(i);
      }
    }
    else {
      for (int i=0; i < (long)max_points_d; i++) {
	if (!Integral::almostEqual((float)alpha_d(i), (float)0) &&
	    !Integral::almostEqual((float)alpha_d(i), (float)penalty_d)) {
	  numChanged += examineExample(i);
	}
      }
    }

    if (examineAll == 1) {
      examineAll = 0;
    }
    else if (numChanged == 0) {
      examineAll = 1;
    }       
  }

  // compute the number of bound and non-bound support vectors
  //
  Long non_bound_support = 0;
  Long bound_support = 0;

  for (int i=0; i < (long)max_points_d; i++) {

    // is this a valid support vector?
    //
    if ((float)alpha_d(i) > 0) {

      // increment the number of bound support vectors
      //    
      if ((float)alpha_d(i) < penalty_d) {
	non_bound_support++;
      }
      
      // increment the number of non-bound support vectors
      //    
      else {
	bound_support++;
      }
    }
  }

  if (verbosity_d >= Integral::BRIEF) {
    output.assign(L"\nnumber of training examples: ");
    output.concat(max_points_d);
    output.concat(L"\nnumber of bound support vectors: ");
    output.concat(bound_support);
    output.concat(L"\nnumber of non-bound support vectors: ");
    output.concat(non_bound_support);
    output.concat(L"\nthreshold: ");
    output.concat(bias_d);
    output.concat(L", penalty: ");
    output.concat(penalty_d);
    output.concat(L", tol: ");
    output.concat(tol_d);
    output.concat(L", eps: ");
    output.concat(eps_d);
    Console::put(output);
  }
  
  // exit gracefully
  //
  return true;    
}

// method: takeStep
//
// arguments:
//  int i1: (input) first example to be evaluated
//  int i2: (input) second example to be evaluated
//
// return: integer value indicating status
//
// method that jointly optimizes two Lagrange multipliers and
// maintains the feasibility of the dual QP
//
int SupportVectorMachine::takeStep(int i1, int i2) {

  // if the two examples being jointly optimized are the same - no progress
  //
  if (i1 == i2) {
    return 0;
  }

  // declare and initialize local variables
  //
  float E1 = 0;
  float E2 = 0;
  
  float alpha1 = alpha_d(i1);
  float y1 = target_d(i1);
 
  float alpha2 = alpha_d(i2);
  float y2 = target_d(i2);
  
  // look up the error in the error cache if the corresponding Lagrange
  // multiplier is NOT at a bound otherwise we need to evaluate the current
  // SVM decision function based in the current alpha vector
  //  
  if ((alpha1 > 0) && (alpha1 < penalty_d)) {
    E1 = error_cache_d(i1);
  }
  else {
    E1 = evaluateOutput(points_d(i1)) - y1;
  }
  
  if ((alpha2 > 0) && (alpha2 < penalty_d)) {
    E2 = error_cache_d(i2);
  }
  else {
    E2 = evaluateOutput(points_d(i2)) - y2;
  }  

  float s = y1 * y2;
  
  // compute the upper and lower constraints, L and H, on multiplier alpha2
  //
  float L = 0.0;
  float H = 0.0;  
  float Lobj = 0.0;
  float Hobj = 0.0;
    
  if (y1 != y2) {
    L = Integral::max(0, alpha2 - alpha1);
    H = Integral::min(penalty_d, alpha2 - alpha1 + penalty_d);    
  }
  else {
    L = Integral::max(0, alpha1 + alpha2 - penalty_d);
    H = Integral::min(penalty_d, alpha1 + alpha2);    
  }

  // if the lower and upper constraints are the same - no progress
  //
  if (L == H) {
    return 0;
  }

  // recompute the Lagrange multiplier for example i2
  //
  float k11 = 0.0;
  kernel_d.compute(k11, points_d(i1), points_d(i1));

  float k12 = 0.0;
  kernel_d.compute(k12, points_d(i1), points_d(i2));

  float k22 = 0.0;
  kernel_d.compute(k22, points_d(i2), points_d(i2));
  
  float eta = 2 * k12 - k11 - k22;
  float a2 = 0;

  if (eta < 0) {

    a2 = alpha2 - y2 * (E1 - E2) / eta;

    // constrain a2 to lie between L and H
    //
    if (a2 < L) {
      a2 = L;
    }
    else if (a2 > H) {
      a2 = H;
    }
  }

  // under unusual circumstances eta will not be negative in which case
  // the objective function should be evaluated at each end of the line
  // segment using only those terms that depend on alpha2
  //
  else {

    float c1 = eta / 2;
    float c2 = y2 * (E1 - E2) - eta * alpha2;
    
    Lobj = c1 * L * L + c2 * L;
    Hobj = c1 * H * H + c2 * H;
    
    if (Lobj > (Hobj + eps_d)) {
      a2 = L;
    }  
    else if (Lobj < (Hobj - eps_d)) {
      a2 = H;
    }
    else {
      a2 = alpha2;
    }
  }

  if (fabs(a2 - alpha2) < (eps_d * (a2 + alpha2 + eps_d))) {
    return 0;
  }

  // recompute the Lagrange multiplier for example i1
  //
  float bias_new = 0.0;
  float a1 = alpha1 + s * (alpha2 - a2);

  // check the bounds of the Lagrange multipliers
  //
  if (a1 < 0) {
    a2 += s * a1;
    a1 = 0.0;
  }
  else if (a1 > penalty_d) {
    a2 += s * (a1 - penalty_d);
    a1 = penalty_d;
  }
  
  // the threshold b1 is valid when the new alpha1 is not at the bounds,
  // because it forces the output of the SVM to be y1 when the input is x1
  //
  float b1 = E1
    + y1 * (a1 - alpha1) * k11
    + y2 * (a2 - alpha2) * k12
    + (float)bias_d;

  if ((a1 > 0) && (a1 < penalty_d)) {
    bias_new = b1;
  }
  
  // the threshold b2 is valid when the new alpha2 is not at the bounds,
  // because it forces the output of the SVM to be y2 when the input is x2
  //
  else {
    
    float b2 = E2
      + y1 * (a1 - alpha1) * k12
      + y2 * (a2 - alpha2) * k22
      + (float)bias_d;

    if ((a2 > 0) && (a2 < penalty_d)) {
      bias_new = b2;
    }
    else {

      // when both new Lagrange multipliers are at bounds and is L is not equal
      // to H, then the interval [b1 b2] contain all threshold values that are
      // consistent with the KKT condition. In this case SMO, chooses the
      // threshold to be half way between b1 and b2
      //
      bias_new = (b1 + b2) / 2;
    }
  }

  // save the bias difference and update the error cache and set the new bias
  //
  float bias_delta = bias_new - (float)bias_d;
  bias_d = bias_new;

  // update the error chache
  //
  float w1 = y1 * (a1 - alpha1);
  float w2 = y2 * (a2 - alpha2);  

  // when a joint optimization occurs, the stored errors for all non-bound
  // multipliers (alpha) that are NOT involved in optimization are updated
  //
  float value1 = 0.0;
  float value2 = 0.0;
  
  for (int i=0; i < (long)max_points_d; i++) {

    kernel_d.compute(value1, points_d(i1), points_d(i));
    kernel_d.compute(value2, points_d(i2), points_d(i));    
    
    error_cache_d(i) += w1 * value1 + w2 * value2 - bias_delta;
  }
  
  error_cache_d(i1) = 0.0;
  error_cache_d(i2) = 0.0;
  
  // store a1 and a2 in the alpha array
  //
  alpha_d(i1) = a1;
  alpha_d(i2) = a2;

  // return the progress made
  //
  return 1;  
}

// method: loadRawFeatures
//
// arguments:
//  Filename& file_name: (input) input features
//
// return: integer value indicating status
//
// method load the features from file in RAW format:
//
//  [feature vector 1] [target 1]
//  [feature vector 2] [target 2]
//  ...
//  [feature vector N] [target N]
//
boolean SupportVectorMachine::loadRawFeatures(Filename& file_name_a) {

  // declare local variables
  //
  File pfile;

  Char chr;
  String buf;
  String buffer;
  Float val;
  
  long pos = 0;
  long count = 0;
  long num_frames = 0;
  long num_tokens = 0;
  long num_features = -1;
  
  // open the file description
  //
  if (!pfile.open(file_name_a, File::READ_ONLY, File::TEXT)) {
    return Error::handle(name(), L"loadRawFeatures", Error::ARG, __FILE__, __LINE__);
  }

  // set the default capacity
  //
  points_d.setCapacity(DEF_CAPACITY);
  target_d.setCapacity(DEF_CAPACITY);
  
  // read the contents of the parameter file line-by-line
  //  
  while (pfile.get(buffer, Sof::BUFFER_SIZE)) {

    // get the first char
    //
    buffer.trim();    
    chr = buffer(0);

    // make sure that the line is valid
    //    
    if (!buffer.eq(L"") && !chr.eq(L'#')) {

      num_frames++;
      num_tokens = buffer.countTokens(L" ");
      num_features = num_tokens - 1;

      pos = 0;
      count = 0;

      points_d.setLength(num_frames);
      points_d(num_frames - 1).setLength(num_features);      
      target_d.setLength(num_frames);
      
      while (buffer.tokenize(buf, pos)) {

	count++;
	buf.trim();
	
	if (count <= num_features) {
	  val.assign(buf);
	  points_d(num_frames - 1)(count - 1).assign(val);
	}
	else {
	  val.assign(buf);
	  target_d(num_frames - 1).assign(val);
	}
      }
    }
  }
  
  // close the file descriptor
  //
  pfile.close();
  
  // exit gracefully
  //
  return true;
}

// method: examineExample
//
// arguments:
//  int i1: (input) example to be evaluated
//
// return: integer value indicating status
//
// method to examine a specific Lagrange multiplier and check if
// it violates the KKT condition
//
int SupportVectorMachine::examineExample(int i2) {

  // declare local variables
  //
  float E1 = 0;
  float E2 = 0;
  
  float y2 = target_d(i2);  
  float alpha2 = alpha_d(i2);
  
  // look up the error in the error cache for the corresponding Lagrange
  //
  if ((alpha2 > 0) && (alpha2 < penalty_d)) {  
    E2 = error_cache_d(i2);
  }
  else {
    E2 = evaluateOutput(points_d(i2)) - y2;
  }
  
  float r2 = E2 * y2;

  // KKT conditions:
  // r2 >= 1  and alpha2 == 0     (well classified)
  // r2 == 1  and 0 < alpha2 < C  (support vectors at margins)
  // r2 <= 1  and alpha2 == C     (support vectors between margins)
  //
  // the KKT conditions are necessary and sufficient conditions for
  // convergence. For, example in the classification case the above
  // criteria must be checked for the 1-norm soft margin optimization
  //
  // the choice of the first heuristic provides the outer loop of SMO.
  // the outer loop iterates over the entire data set and determines
  // if an example violates the KKT condition. when and example violates
  // the KKT condition it is selected for joint optimization with a
  // second example (which is found using the second heuristic).
  //
  // the first choice concentrates on the CPU time taken on examples
  // that most likely violate KKT (non-bound subset) and verifies that
  // the KKT conditions are fulfilled within espilon.
  //
  if (((r2 < -tol_d) && (alpha2 < penalty_d)) ||
      ((r2 > tol_d) && (alpha2 > 0))) {

    // once the first Lagrange multiplier is choosen SMO chooses the
    // second Lagrange multiplier to maximize the size of the step
    // taken during joint optimization. SMO approximates the step
    // size via |E1 - E2| because the kernel evaluateion are expensive
    //
    int i1 = -1;    
    float step_size = 0;
    float max_step_size = 0;    

    // find the example that gives the largest step size (non-bound example)
    //
    for (int i=0; i < (long)max_points_d; i++) {
      if (((float)alpha_d(i) > 0) && ((float)alpha_d(i) < penalty_d)) {
	E1 = error_cache_d(i);
	step_size = fabs(E2 - E1);
	if (step_size > max_step_size) {
	  i1 = i;
	  max_step_size = step_size;
	}
      }
    }

    // if a good i1 is found then we begin the joint optimization
    //
    if (i1 >= 0) {
      if (takeStep(i2, i1)) {
	return 1;
      }
    }
    
    // under unusual circumstances SMO cannot make progress using the
    // second heuristic, hence, it uses a hierarchy of second choices:
    // (A) if the second heuristic does not make progress then SMO
    //     searches the non-bound examples for one that does
    //
    int i = -1;
    int rand_i = -1;
    
    for (rand_i = (int)(drand48() * (long)max_points_d), i = rand_i;
	 i < (long)max_points_d + rand_i; i++) {

      i1 = i % (long)max_points_d;
      
      if (((float)alpha_d(i1) > 0) && ((float)alpha_d(i1) < penalty_d)) {

	// if a good i1 is found then we begin the joint optimization
	//
	if (takeStep(i2, i1)) {
	  return 1;
	}
      }
    }
    
    // (B) if the non-bound examples do not make progress then SMO
    //     searches the entire data set for one that does
    //
    i = -1;
    rand_i = -1;
    
    for (rand_i = (int)(drand48() * (long)max_points_d), i = rand_i;
	 i < (long)max_points_d + rand_i; i++) {
      
      i1 = i % (long)max_points_d;

      // if a good i1 is found then we begin the joint optimization
      //      
      if (takeStep(i2, i1)) {
	return 1;
      }
    }
  }

  // no progress was made
  //
  return 0;  
}
