/*
   File:        nearest.c
   Authors:     Patrick Markiewicz
   Created:     May 1999
   Description: 

   Copyright 1999, Schenley Park Research
*/

#include "nearest.h"

/* This function computes the mean squared leave-one-out cross-validation
   error that occurs when using the attributes mentioned in in_attnums to
   predict the attribute mentioned in out_attnum.

   The distance between record row1 and record row2 is defined as

    Dist(row1,row2) = _____
                      \
                       \      ( D_i / S_i )^2
                       /
                      /____
                    i in in_attnums

    Where D_i = ds_real_ref(ds,row1,attnum_i) - ds_real_ref(ds,row2,attnum_i)

    Where S_i = ds_att_max(ds,attnum_i) - ds_att_min(ds,attnum_i)

    Where attnum_i = ivec_ref(in_attnums,i) and where 0 <= i < ivec_size(in_attnums)
*/
double loo_nearest_error(datset *ds,ivec *in_attnums,int out_attnum,
                         double max_error)
{
  int i;
  ivec *danger_rows;
  ivec *rows = mk_identity_ivec(ds_num_rows(ds));
  ivec *neighbor_rows = mk_ivec(ivec_size(rows));
  dyv *neighbor_distances = mk_dyv(ivec_size(rows));
  int num_atts=ivec_size(in_attnums);
  dyv *min_bound_dist;
  dyv *ranges = mk_dyv(ds_num_atts(ds));
  dyv *hi = mk_dyv(dyv_size(ranges));
  dyv *lo = mk_dyv(dyv_size(ranges));
  double range = ds_att_max(ds,out_attnum)-ds_att_min(ds,out_attnum);
  double current_error=0.00;
  bool escape=FALSE;


  if(num_atts==0)
    add_to_ivec(in_attnums,out_attnum);
  num_atts=ivec_size(in_attnums);
  min_bound_dist = mk_constant_dyv(ivec_size(rows),num_atts*1.1);

  for(i=0;i<num_atts;i++)
  {
    int att=ivec_ref(in_attnums,i);
    dyv_set(hi,att,ds_att_max(ds,att));
    dyv_set(lo,att,ds_att_min(ds,att));
    dyv_set(ranges,att,dyv_ref(hi,att)-dyv_ref(lo,att));
  }
  dyv_set(ranges,out_attnum,range);

  danger_rows = all_nearest(ds,in_attnums,ranges,max_error,&escape,out_attnum,
                            &current_error,min_bound_dist,lo,hi,rows,
                            neighbor_rows,neighbor_distances);

  free_ivec(danger_rows);
  free_ivec(rows);
  free_ivec(neighbor_rows);
  free_dyv(neighbor_distances);
  free_dyv(ranges);
  free_dyv(min_bound_dist);
  free_dyv(hi);
  free_dyv(lo);


  return current_error;
}

/* This function frees all the vectors allocated in all_nearest. */
void free_all_nearest(ivec *rows,ivec *neighbor_rows,dyv *neighbor_distances,
                      ivec *danger_rows,dyv *bound_dist,dyv *min_bound_dist)
{
  free_ivec(rows);
  free_ivec(neighbor_rows);
  free_dyv(neighbor_distances);

  free_ivec(danger_rows);

  free_dyv(bound_dist);
  free_dyv(min_bound_dist);
}

/* Inputs: ds, in_attnums, ranges, hi, lo, rows
   Outputs (updated by this call): neighbor_rows, neighbor_distances, danger_rows

   ds is a datset (defined in ds.h)
   in_attnums are the attributes being used to defined nearness
   dyv_ref(ranges,i) = ds_att_max(ds,attnum_i) - ds_att_min(ds,attnum_i)
                      where attnum_i = ivec_ref(in_attnums,i) and 0 <= i < num_dims
                      where num_dims = ivec_size(in_attnums) = dyv_size(ranges)

   Call a record RELEVANT if its index is included in the "rows" ivec.
   Thus, if we were working on records 0, 4, 5 and 9 then rows would be
      (0,4,5,9)

   For each RELEVANT record, this function finds the nearest RELEVANT record
   using the distance metric

    Dist(row1,row2) = _____
                      \
                       \      ( D_i / S_i )^2
                       /
                      /____
                    i in in_attnums

    Where D_i = ds_real_ref(ds,row1,attnum_i) - ds_real_ref(ds,row2,attnum_i)

    Where S_i = ds_att_max(ds,attnum_i) - ds_att_min(ds,attnum_i)
              = dyv_ref(ranges,i)

   On exit, if the k'th element of "rows" is "n", then
            the k'th element of "neighbor_rows" will be set to the 
            nearest relevant neighbor of n.
            and the k'th element of neighbor_distances will be set to 
            Dist(n,nearest relevant neighbor of n)

    (Exception to the above...if there is only one element in "rows" on entry,
     then on exit neighbor_rows[0] will be set to -1 as a warning that we have
     not found a nearest neighbor to rows[0] and thus rows[0] is an automatic
     danger point for the calling function)

   hi and lo are dynamic vectors which store a better estimate of the higher and lower
   bounds of a given estimate than simply the current midpoint, but quicker than calculating
   the actual higher and lower bounds for a given bounding box.   */
ivec *all_nearest(datset *ds,ivec *in_attnums,dyv *ranges,
                  double max_error,bool *escape,int out_attnum,
                  double *current_error,dyv *min_bound_dist,
                  dyv *lo,dyv *hi,ivec *rows,
                  ivec *neighbor_rows,dyv *neighbor_distances)
{

  if(*escape)
  { 
    ivec *danger_rows=mk_ivec(0);
    return danger_rows;
  }
  if(ivec_size(rows)==1)
  { 
    ivec *danger_rows = mk_ivec(1);
    ivec_set(danger_rows,0,0);
    ivec_set(neighbor_rows,0,-1);
    return danger_rows;
    
  }
  else
  {

    double temp_bound;

    ivec *left_rows = mk_ivec(0);
    ivec *left_neighbor_rows;
    dyv *left_neighbor_distances;
    ivec *left_danger_rows = mk_ivec(0);

    dyv *left_bound_dist = mk_dyv(0);
    dyv *left_min_bound_dist = mk_dyv(0);

    ivec *right_rows = mk_ivec(0);
    ivec *right_neighbor_rows;
    dyv *right_neighbor_distances;

    ivec *right_danger_rows = mk_ivec(0);
  
    dyv *right_bound_dist = mk_dyv(0);
    dyv *right_min_bound_dist = mk_dyv(0);

    ivec *danger_rows=mk_ivec(0);
    int attribute;
    double mid;
    double lower_bound;
    double upper_bound;

    compute_bounding_box(ds,in_attnums,ranges,&attribute,&mid,lo,hi);

    if(dyv_ref(hi,attribute)==dyv_ref(lo,attribute))
    {
      int i=0;
      double dist;
      ivec_set(neighbor_rows,i,ivec_ref(rows,ivec_size(rows)-1));
      dyv_set(neighbor_distances,i,0);

      for(i=1;i<ivec_size(rows);i++)
      {
        int row1=ivec_ref(rows,i);
        ivec_set(neighbor_rows,i,ivec_ref(rows,i-1));
        dyv_set(neighbor_distances,i,0);
      
        dist= (ds_real_ref(ds,row1,out_attnum)-ds_real_ref(ds,ivec_ref(neighbor_rows,i),out_attnum))/
                      dyv_ref(ranges,out_attnum);
        *current_error += dist*dist;
  
        if(*current_error >=max_error)
          *escape=TRUE;
      }

      free_ivec(left_rows);
      free_dyv(left_bound_dist);
      free_dyv(left_min_bound_dist);
      free_ivec(left_danger_rows);
      free_ivec(right_rows);
      free_dyv(right_bound_dist);
      free_dyv(right_min_bound_dist);
      free_ivec(right_danger_rows);
    

      return danger_rows;
    }

    split_in_two(ds,ranges,rows,out_attnum,min_bound_dist,attribute,mid,
                 &lower_bound,&upper_bound,left_rows,right_rows,
                 left_min_bound_dist,left_bound_dist,
                 right_min_bound_dist,right_bound_dist);

    left_neighbor_rows = mk_ivec(ivec_size(left_rows));
    left_neighbor_distances = mk_dyv(ivec_size(left_rows));
    right_neighbor_rows = mk_ivec(ivec_size(right_rows));
    right_neighbor_distances = mk_dyv(ivec_size(right_rows));

    if(ivec_size(left_rows)>0)
    {
      temp_bound = dyv_ref(hi,attribute);
      dyv_set(hi,attribute,lower_bound);
      free_ivec(left_danger_rows);
      left_danger_rows = all_nearest(ds,in_attnums,ranges,max_error,escape,out_attnum,
                                     current_error,left_min_bound_dist,
                                     lo,hi,left_rows,
                                     left_neighbor_rows,left_neighbor_distances);
      dyv_set(hi,attribute,temp_bound);

      if(*escape)
      {
        free_all_nearest(left_rows,left_neighbor_rows,left_neighbor_distances,
                         left_danger_rows,left_bound_dist,left_min_bound_dist);
        free_all_nearest(right_rows,right_neighbor_rows,right_neighbor_distances,
                         right_danger_rows,right_bound_dist,right_min_bound_dist);
        return danger_rows;
      }

    }

    if(ivec_size(right_rows)>0)
    {
      temp_bound = dyv_ref(lo,attribute);
      dyv_set(lo,attribute,upper_bound);
      free_ivec(right_danger_rows);
      right_danger_rows = all_nearest(ds,in_attnums,ranges,max_error,escape,out_attnum,
                                      current_error,right_min_bound_dist,
                                      lo,hi,right_rows,
                                      right_neighbor_rows,right_neighbor_distances);
      dyv_set(lo,attribute,temp_bound);

      if(*escape)
      {
        
        free_all_nearest(left_rows,left_neighbor_rows,left_neighbor_distances,
                         left_danger_rows,left_bound_dist,left_min_bound_dist);
        free_all_nearest(right_rows,right_neighbor_rows,right_neighbor_distances,
                         right_danger_rows,right_bound_dist,right_min_bound_dist);
        return danger_rows;
      }
    }
    
    if(ivec_size(right_rows)>0)
    {
      compute_nearest_neighbor(ds,in_attnums,ranges,max_error,escape,out_attnum,current_error,
                               upper_bound,attribute,left_bound_dist,
                               left_danger_rows,left_rows,left_neighbor_rows,
                               left_neighbor_distances,
                               right_rows);
      if(*escape)
      {
        free_all_nearest(left_rows,left_neighbor_rows,left_neighbor_distances,
                         left_danger_rows,left_bound_dist,left_min_bound_dist);
        free_all_nearest(right_rows,right_neighbor_rows,right_neighbor_distances,
                         right_danger_rows,right_bound_dist,right_min_bound_dist);
        return danger_rows;
      }
    }

    if(ivec_size(left_rows)>0)
    {

      compute_nearest_neighbor(ds,in_attnums,ranges,max_error,escape,out_attnum,current_error,
                               upper_bound,attribute,right_bound_dist,
                               right_danger_rows,right_rows,right_neighbor_rows,
                               right_neighbor_distances,
                               left_rows);
      if(*escape)
      {
        free_all_nearest(left_rows,left_neighbor_rows,left_neighbor_distances,
                         left_danger_rows,left_bound_dist,left_min_bound_dist);
        free_all_nearest(right_rows,right_neighbor_rows,right_neighbor_distances,
                         right_danger_rows,right_bound_dist,right_min_bound_dist);
        return danger_rows;
      }
    }

    free_ivec(danger_rows);
    danger_rows = mk_ivec(ivec_size(right_danger_rows)+ivec_size(left_danger_rows));
    
    fast_merge_nearest_neighbor(rows,neighbor_rows,neighbor_distances,
                           left_rows,left_neighbor_rows,
                           left_neighbor_distances,
                           right_rows,right_neighbor_rows,
                           right_neighbor_distances,danger_rows,
                           left_danger_rows,right_danger_rows);

    free_all_nearest(left_rows,left_neighbor_rows,left_neighbor_distances,
                     left_danger_rows,left_bound_dist,left_min_bound_dist);
    free_all_nearest(right_rows,right_neighbor_rows,right_neighbor_distances,
                     right_danger_rows,right_bound_dist,right_min_bound_dist);
    
    return danger_rows;

  }

}

/* Inputs: ds, in_attnums,lo,hi
   Outputs: attribute,mid

   Determine the widest dimension in the bounding box and determine
   midpoint of that dimension.  */
void compute_bounding_box(datset *ds,ivec *in_attnums,dyv *ranges,
                          int *attribute,double *mid,dyv *lo,dyv *hi)
{

  int num_atts=ivec_size(in_attnums);
  int i;
  double wide=-1.00;

  for(i=0;i<num_atts;i++)
  {
    int att=ivec_ref(in_attnums,i);
    double width=(dyv_ref(hi,att)-dyv_ref(lo,att))/dyv_ref(ranges,att);

    if(width>wide)
    {

      wide=width;
      *mid=(dyv_ref(lo,att)+(dyv_ref(hi,att)-dyv_ref(lo,att))/2);
      *attribute=att;
    }

  }
}

/* Input: ds,rows,attribute,midpoint
   Output: left_rows,right_rows;
   
   This function divides the rows into two partitions based on
   the midpoint in a bounding_box.

   It also updates the lower_bound and the upper_bound which are the
   rows closest to the midpoint on either side of the midpoint.  These numbers
   are then used for the next estimate of the lo and hi vectors.  */
void split_in_two(datset *ds,dyv *ranges,
                  ivec *rows,int out_attnum,dyv *min_bound_dist,
                  int attribute,double midpoint,
                  double *lower_bound,double *upper_bound,ivec *left_rows,ivec *right_rows,
                  dyv *left_min_bound_dist,dyv *left_bound_dist,
                  dyv *right_min_bound_dist,dyv *right_bound_dist)
{

  int i;
  int lower_row,upper_row;
  double lower=-1.1;
  double upper=1.1;

  for(i=0;i<ivec_size(rows);i++)
  {
    int row=ivec_ref(rows,i);
    double data=ds_real_ref(ds,row,attribute);
    double bound_dist = (data-midpoint)/dyv_ref(ranges,attribute);

    if(bound_dist >= lower && bound_dist <=0.0)
    {
      lower=bound_dist;
      lower_row = row;
    }
    if(bound_dist <= upper && bound_dist >=0.0)
    {
      upper=bound_dist;
      upper_row = row;
    }

    bound_dist *=bound_dist;


    if(data < midpoint)
    {
      add_to_ivec(left_rows,row);
      if(bound_dist < dyv_ref(min_bound_dist,i))
        add_to_dyv(left_min_bound_dist,bound_dist);
      else add_to_dyv(left_min_bound_dist,dyv_ref(min_bound_dist,i));
      add_to_dyv(left_bound_dist,dyv_ref(min_bound_dist,i));
    }
    else
    {
      add_to_ivec(right_rows,row);
      if(bound_dist < dyv_ref(min_bound_dist,i))
        add_to_dyv(right_min_bound_dist,bound_dist);
      else add_to_dyv(right_min_bound_dist,dyv_ref(min_bound_dist,i));
      add_to_dyv(right_bound_dist,dyv_ref(min_bound_dist,i));

    }
  }
  if(lower == -1.1)
    lower_row = upper_row;
  else if(upper == 1.1)
    upper_row = lower_row;

  *lower_bound = ds_real_ref(ds,lower_row,attribute);
  *upper_bound = ds_real_ref(ds,upper_row,attribute);

}

/* Input: ds,in_attnums, rows
   Output: neighbor_rows,neighbor_distances

   Loops through all rows in the danger_rows ivec and updates its distance if
   necessary.  If the point is no longer a danger point, it updates the 
   current_error. Also checks if the error is more than max_error and sets escape
   to TRUE if this is the case.*/
void compute_nearest_neighbor(datset *ds,ivec *in_attnums,dyv *ranges,double max_error,
                              bool *escape,int out_attnum,double *current_error,
                              double mid,int attribute,
                              dyv *min_bound_dist,ivec *danger_rows,ivec *rows,
                              ivec *neighbor_rows,dyv *neighbor_distances,ivec *other_rows)
{
  int i;

  int num_rows=ivec_size(danger_rows);

  for(i=0;i<num_rows;i++)
  {
    int neighbor_row = ivec_ref(neighbor_rows,ivec_ref(danger_rows,i));
    double old_dist = dyv_ref(neighbor_distances,ivec_ref(danger_rows,i));

    double new_dist;
    int neighbor;
    int row1=ivec_ref(rows,ivec_ref(danger_rows,i));
    double bound_dist;

    bound_dist= (ds_real_ref(ds,row1,attribute)-mid)/dyv_ref(ranges,attribute);
    bound_dist *= bound_dist;
    if(*current_error>=max_error)
    {
      *escape=TRUE;
      return;

    }
    if(neighbor_row==-1)
    {
      compute_min_distance(ds,in_attnums,ranges,other_rows,row1,
                           &neighbor,&new_dist);     
      ivec_set(neighbor_rows,ivec_ref(danger_rows,i),neighbor);
      dyv_set(neighbor_distances,ivec_ref(danger_rows,i),new_dist);
      old_dist=new_dist;
    }
    else if(old_dist > bound_dist)
    {
      compute_min_distance(ds,in_attnums,ranges,other_rows,row1,
                           &neighbor,&new_dist);     

      if((new_dist < old_dist))
      {
        ivec_set(neighbor_rows,ivec_ref(danger_rows,i),neighbor);
        dyv_set(neighbor_distances,ivec_ref(danger_rows,i),new_dist);
        old_dist=new_dist;
      }
    }
        
    if(old_dist <= dyv_ref(min_bound_dist,ivec_ref(danger_rows,i)))
    {
      double dist= (ds_real_ref(ds,row1,out_attnum)-
                    ds_real_ref(ds,ivec_ref(neighbor_rows,ivec_ref(danger_rows,i)),out_attnum))/
                    dyv_ref(ranges,out_attnum);
      *current_error += dist*dist;
      ivec_remove(danger_rows,i);
      num_rows--;
      i--;
    }
  }
 
}

/* 
   Input ds,in_attnums,ranges,rows,rowcount
   Output neighbor, distance

   Finds the smallest distance between row1 and the rows in the rows ivec.  */
void compute_min_distance(datset *ds,ivec *in_attnums,dyv *ranges,ivec *rows,
                          int row1,
                          int *neighbor,double *distance)
{

  int i,row2;
  int num_rows=ivec_size(rows);
  int num_atts=ivec_size(in_attnums);
  *distance =(double)num_atts;

  for(i=0;i<num_rows-1;i++)
  {
    double sum;
    row2=ivec_ref(rows,i);

    i+=(row1==row2);
    row2=ivec_ref(rows,i);
    sum = Dist(ds,ranges,in_attnums,row1,row2);

    if(sum <= *distance)
    {
      *distance=sum;
      *neighbor=row2;
    }
  }
  
  row2=ivec_ref(rows,num_rows-1);

  if(row1!=row2)
  {
    double sum=Dist(ds,ranges,in_attnums,row1,row2);

    if(sum <= *distance)
    {
      *distance=sum;
      *neighbor=row2;
    }

  }
}

/* Returns the distance between two rows */
double Dist(datset *ds,dyv *ranges,ivec *in_attnums,int row1,int row2)
{
  int j;
  double sum = 0.00;
  int num_atts = ivec_size(in_attnums);

  for(j = 0;j<num_atts;j++)
  {
    int att=ivec_ref(in_attnums,j);
    double dist;
    if(dyv_ref(ranges,att))
      dist=(ds_real_ref(ds,row1,att)-ds_real_ref(ds,row2,att))/dyv_ref(ranges,att);
    else
      dist=0;

    dist*=dist;
    sum+=dist;
  }
  return sum;

}


/* Merges the two branches in order */
void merge_nearest_neighbor(ivec *rows,ivec *neighbor_rows,dyv *neighbor_distances,
                            ivec *left_rows,ivec *left_neighbor_rows,
                            dyv *left_neighbor_distances,
                            ivec *right_rows,ivec *right_neighbor_rows,
                            dyv *right_neighbor_distances,ivec *danger_rows,
                            ivec *left_danger_rows,ivec *right_danger_rows)
{
  
  int i;
  int right=0,left=0;
  int right_danger,left_danger;
  int left_danger_index=0,right_danger_index=0;
  int right_size=ivec_size(right_rows);
  int left_size=ivec_size(left_rows);
  int right_danger_size=ivec_size(right_danger_rows);
  int left_danger_size=ivec_size(left_danger_rows);
  int danger=0;

  int num_rows=ivec_size(neighbor_rows);

  int left_row,right_row;

  if(left_danger_size)
    left_danger=ivec_ref(left_danger_rows,left_danger_index);
  else
    left_danger=-1;

  if(right_danger_size)
    right_danger=ivec_ref(right_danger_rows,right_danger_index);
  else
    right_danger=-1;

  if(left_size)
    left_row=ivec_ref(left_rows,left);
  else
    left_row=left_size;
  if(right_size)
    right_row=ivec_ref(right_rows,right);
  else
    right_row=right_size;
  
  for(i=0;i<num_rows;i++)
  {

    if((left_row < right_row || right==right_size)&&(left!=left_size))
    {
      ivec_set(rows,i,ivec_ref(left_rows,left));
      ivec_set(neighbor_rows,i,ivec_ref(left_neighbor_rows,left));
      dyv_set(neighbor_distances,i,dyv_ref(left_neighbor_distances,left));

      if(left == left_danger)
      {
        ivec_set(danger_rows,danger,i);
        danger++;
        left_danger_index++;

        if(left_danger_index==left_danger_size)
          left_danger=-1;
        else
          left_danger=ivec_ref(left_danger_rows,left_danger_index);

      }
      left++;

 
      if(left == left_size)
        left_row=left_size;
      else
        left_row=ivec_ref(left_rows,left);
    }
    else
    {
      ivec_set(rows,i,ivec_ref(right_rows,right));
      ivec_set(neighbor_rows,i,ivec_ref(right_neighbor_rows,right));
      dyv_set(neighbor_distances,i,dyv_ref(right_neighbor_distances,right));

      if(right == right_danger)
      {
        ivec_set(danger_rows,danger,i);
        danger++;
        right_danger_index++;
        if(right_danger_index==right_danger_size)
          right_danger=-1;
        else
          right_danger=ivec_ref(right_danger_rows,right_danger_index);
      }

      right++;
      if(right == right_size)
        right_row=right_size;
      else
        right_row=ivec_ref(right_rows,right);

    }
  }

}

/* Fast merge which actually just concatenates one branch to the other */
void fast_merge_nearest_neighbor(ivec *rows,ivec *neighbor_rows,dyv *neighbor_distances,
                            ivec *left_rows,ivec *left_neighbor_rows,
                            dyv *left_neighbor_distances,
                            ivec *right_rows,ivec *right_neighbor_rows,
                            dyv *right_neighbor_distances,ivec *danger_rows,
                            ivec *left_danger_rows,ivec *right_danger_rows)
{
  int row_index;
  int left_size=ivec_size(left_rows);
  int right_size=ivec_size(right_rows);
  int left_danger_size=ivec_size(left_danger_rows);
  int right_danger_size=ivec_size(right_danger_rows);
  

  for(row_index=0;row_index<left_danger_size;row_index++)
  {
    ivec_set(rows,row_index,ivec_ref(left_rows,row_index));
    ivec_set(neighbor_rows,row_index,ivec_ref(left_neighbor_rows,row_index));
    dyv_set(neighbor_distances,row_index,dyv_ref(left_neighbor_distances,row_index));
    ivec_set(danger_rows,row_index,ivec_ref(left_danger_rows,row_index));
  }
  for(;row_index<left_size;row_index++)
  {
    ivec_set(rows,row_index,ivec_ref(left_rows,row_index));
    ivec_set(neighbor_rows,row_index,ivec_ref(left_neighbor_rows,row_index));
    dyv_set(neighbor_distances,row_index,dyv_ref(left_neighbor_distances,row_index));
  }


  for(row_index=0;row_index<right_danger_size;row_index++)
  {
    ivec_set(rows,row_index+left_size,ivec_ref(right_rows,row_index));
    ivec_set(neighbor_rows,row_index+left_size,ivec_ref(right_neighbor_rows,row_index));
    dyv_set(neighbor_distances,row_index+left_size,dyv_ref(right_neighbor_distances,row_index));
    ivec_set(danger_rows,row_index+left_danger_size,ivec_ref(right_danger_rows,row_index)+left_size);
  }
  for(;row_index<right_size;row_index++)
  {
    ivec_set(rows,row_index+left_size,ivec_ref(right_rows,row_index));
    ivec_set(neighbor_rows,row_index+left_size,ivec_ref(right_neighbor_rows,row_index));
    dyv_set(neighbor_distances,row_index+left_size,dyv_ref(right_neighbor_distances,row_index));
  }

}

/* For each k such that 0 <= k <= maxatts, finds a good approximation
   to the best-set-of-features-of-size-k for predicting out_attnum,
   according to leave-one-out cross-validation. Prints out its finding
   in some kind of neat-and-tidy table to stdout. */
void run_real_features(datset *ds,ivec *in_attnums,int out_attnum,int maxatts)
{
  ivec *features=mk_ivec(0);
  dyv *errors=mk_dyv(0);
  double variance = real_square(ds_att_sdev(ds,out_attnum));

  printf("Attribute:   %% Variance explained when the given attribute\n");
  printf("             is combined with those above it to predict the leave\n");
  printf("             one out cross-validation error for '%s'\n",ds_attnum_to_name(ds,out_attnum));

  compute_best_subset(ds,in_attnums,out_attnum,maxatts,
                      features,errors,variance);
  free_dyv(errors);
  free_ivec(features);
}


/* Recursively computes the smallest error by adding the attribute with the smallest
   error to current_features, adds the error to the current_errors, and removing the
   attribute from the in_attnums array. */
void compute_best_subset(datset *ds,ivec *in_attnums,int out_attnum,int atts,
                         ivec *current_features,dyv *current_errors,double variance)
{

  if(atts)
  {

    ivec *new_in_attnums = mk_copy_ivec(in_attnums);
    int num_atts = ivec_size(in_attnums);
    int att_index=0;
    int min_att_index=0;
    int min_att = ivec_ref(in_attnums,att_index);
    double loo_error,min_error;

    add_to_ivec(current_features,min_att);
    min_error = loo_nearest_error(ds,current_features,out_attnum,ds_num_rows(ds));
    add_to_dyv(current_errors,min_error);

    for(att_index=1;att_index<num_atts;att_index++)
    {
      int att = ivec_ref(in_attnums,att_index);
      ivec_set(current_features,ivec_size(current_features)-1,att);
      loo_error = loo_nearest_error(ds,current_features,out_attnum,min_error);

      if(loo_error < min_error)
      {
        min_error = loo_error;
        min_att = att;
        min_att_index = att_index;
      }
    }
    ivec_set(current_features,ivec_size(current_features)-1,min_att);
    dyv_set(current_errors,dyv_size(current_errors)-1,min_error);

    ivec_remove(new_in_attnums,min_att_index);

    compute_best_subset(ds,new_in_attnums,out_attnum,atts-1,
                        current_features,current_errors,variance);
    free_ivec(new_in_attnums);

    printf("%s ",ds_attnum_to_name(ds,ivec_ref(current_features,atts-1)));
    printf("%g\n",100*(1-dyv_ref(current_errors,atts-1)/variance));
  }
}
