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

   Copyright 1999, Schenley Park Research
*/

#ifndef NEAREST_H
#define NEAREST_H

#include "dsut.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)

    PRECONDITION: This assumes that all attributes menbtioned in in_attnums and
                  out_attnum are all real-valued.
*/
double loo_nearest_error(datset *ds,ivec *in_attnums,int out_attnum,
                         double max_error);

/* Frees the data structures used 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);

/* 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.

   This function returns a ivec pointer which is an ivec containing the indexes of the
   rows ivec which are still danger points. It is used in compute_nearest_neighbor to reduce
   the number of rows which are looped through.  
   
   Max_error is used for short circuiting when calculating the attributes 
   which produce the lowest error (and hence strongest relationship).

   Escape is used to return all the functions after max_error has been reached.  
   Current_error holds the running total of the error so far.
   
   Min_bound_dist stores the minimum distance to the bounding box 
   for each row so as to determine which points are still danger points.

   Hi and lo store an estimated bounding box of the current call.

   If fast_merge_nearest_neighbor is called, the rows are permuted, but the program
   runs faster.  With merge_nearest_neighbor, the rows keep the original order, but it
   is much slower.

*/
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);

/* 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);

/* 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);

/* 
   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);

/* 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);

/* Calculates the distance between two rows */
double Dist(datset *ds,dyv *ranges,ivec *in_attnums,int row1,int row2);

/* 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);

/* 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);

/* 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 leavge-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);

/* 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);
#endif /* NEAREST_H */
