/*
   File:        split.c
   Author:      Andrew W. Moore, Dan Pelleg
   Created:     Sun Apr  5 11:49:13 EDT 1998
   Description: splitting and deleting of kmeans-centers
   $Id: split.c,v 1.33 2004/01/20 12:28:53 dpelleg Exp $

   Copyright 1998, Carnegie Mellon University
*/

#include "kmeans.h"
#include "traversal.h"
#include "split.h"
#include "debug.h"
#include "amgr.h"
#include <assert.h>

static const char *const CVSID = "$Id: split.c,v 1.33 2004/01/20 12:28:53 dpelleg Exp $";

/* given dist_info (distortion, #owned for center i in index i),
   return the LOG likelihood of the centers whose indices are in focus 
   we're assuming all_centers had not changed since distinfo was computed
   (and so can use pidex-es from all_centers into distinfo)
*/
static double ctr_likelihood(const vctr *all_centers, const ivec *focus, const dist_info *distinfo);

/* return a vector with n_children supposed to be centers that "split"
   a center currently residing in ctr, given the variance of points owned by ctr */
static dyv_array *mk_position_children(int n_children, const dyv *ctr, double variance);

/* return the difference in BIC scores of parent, children (negative
   means children's is greater).
*/
mk_birth_value_single_t mk_birth_value_single_BIC;

/*
  Return the adjusted Anderson-Darling statistic, as in:
  Greg Hamerly and Charles Elkan: Learning the k in k-means.
  NIPS 2003.
*/
mk_birth_value_single_t mk_birth_value_single_AD;

/* compute and return the adjusted A^2 Andreson-Darling statistic for a
   list of unordered, un-standardized values
*/
double adjusted_anderson_darling(const dyv *vals);

/* compute and return the Andreson-Darling statistic for a sorted list
   numbers which are the CDF values for the set. Note that this value
   may need further correction (see corrected_anderson_darling).
*/
double anderson_darling(const dyv *vals);

static dyv_array *mk_position_children(int n_children, const dyv *ctr, double variance)
{
  dyv_array *centers = mk_empty_dyv_array();
  dyv *nudge = mk_dyv(dyv_size(ctr)), *tmp;

  if(n_children != 2)
	my_error("Don't know how to split into !=2 children");

  /* choose a random vector of size stddev of node points.
	 add and  subtract it to current centroid to get children */
  random_unit_dyv(nudge);
  dyv_scalar_mult(nudge, 1.0*sqrt(variance), nudge);

  tmp = mk_dyv_plus(ctr, nudge);
  add_to_dyv_array(centers, tmp);
  free_dyv(tmp);
  tmp = mk_dyv_subtract(ctr, nudge);
  add_to_dyv_array(centers, tmp);
  free_dyv(tmp);
  free_dyv(nudge);
  return centers;
}

dyv *mk_birth_value(simple_kdtree *kd,
					vctr *centers, const kmeans_parm *orig_parm)
{
  kmeans_parm parm;

  const int ndims = centers->ndims;
  int n_orig_centers = vctr_live_size(centers);
  int i;
  kmeans_state kmstate;
  dyv *ret = 0;

  nodestats parent;
  nodestats child;

  traversal_stats stats;

  ivec *parent_centers_nonconst = mk_live_ctrs(centers);
  const ivec *parent_centers = parent_centers_nonconst;

  mk_birth_value_single_t *bval = NULL;

  switch (orig_parm->split_statistic) {
  case SCORE_BIC:
    bval = mk_birth_value_single_BIC;
    break;
  case SCORE_AD:
    bval = mk_birth_value_single_AD;
    break;
  default:
    my_errorf("What is this split statistic? %d", orig_parm->split_statistic);
  }

  parent = nodestats_mk_stats(1+vctr_max_pidx(centers), ndims, orig_parm->maintain_memb);

  stats.update_point = nodestats_update_point;
  stats.update_bulk = nodestats_update_bulk;
  stats.prune = distortion_prune;
  stats.mk_stat_snapshot = nodestats_mk_snapshot;
  stats.mk_cache_entry = nodestats_mk_cache_entry;
  stats.apply_cache = nodestats_apply_cache;
  stats.free_cached_stats = nodestats_free_cache;
  stats.centers = centers;
  stats.stat = parent;

  kmstate.kdtree = kd;
  kmstate.current_centers = centers;
  kmstate.phase = PHS_UNPHASED;
  kmstate.b_cutoff = 1.0;		/* unused */

  /* get distortion for parent centers */
  cached_bl_traversal(&kmstate, parent_centers, &stats, orig_parm);

  /* pick a placement for the new child centers */
  if(DEBUG_FLAG(D_DRAWPOINTS))
	ag_on("");

  for(i=0; i<n_orig_centers; i++) {
	int j;
	/* physical index into virtual center i */
	int parent_idx = ivec_ref(parent_centers, i);
	int parent_pidx = vctr_pidx(centers, parent_idx);

	dyv_array *child_ctrs = NULL;
	bool need_to_free_child_ctrs = FALSE;
	/* see if node had children in the past
	   if so, it must be that a previous attempt to split resulted in a no-split decision
	   [otherwise, this is the "child" from prev iter so it has no children]
	   we will resurrect the children in this case because we want the cache to
	   hit
	*/ 
	if(has_dead_children(centers, parent_idx)) {
	  resurrect_children(centers, parent_idx);
	  if(DEBUG_FLAG(D_DRAWPOINTS)) {
		ivec *children = mk_vctr_children(centers, parent_idx);
		int i;
		child_ctrs = mk_empty_dyv_array();
		need_to_free_child_ctrs = TRUE;
		for(i=0; i<VCTR_NCHILDREN; i++)
		  add_to_dyv_array(child_ctrs, vctr_ref(centers, ivec_ref(children, i)));
		free_ivec(children);
	  }
	} else {					/* no children in the past --- create new */
	  child_ctrs = mk_position_children(N_CHILDREN, 
										vctr_ref(centers, parent_idx),
										dyv_ref(parent.distortion, parent_pidx)/ivec_ref(parent.nums, parent_pidx));
	  need_to_free_child_ctrs = TRUE;
	  /* add child centers to list of centres (some may not survive) */
	  vctr_give_birth(centers, parent_idx, child_ctrs);
	}
	if(DEBUG_FLAG(D_DRAWPOINTS)) {
	  for(j=0; j<N_CHILDREN; j++) {
		dyv *parent = vctr_ref(centers, parent_idx);
		dyv *child = dyv_array_ref(child_ctrs, j);
		ag_set_pen_color(center_color(parent_idx));
		ag_disc(dyv_ref(parent,0), dyv_ref(parent,1), 5.0);
		ag_disc(dyv_ref(child,0), dyv_ref(child,1), 4.2);
		ag_set_pen_color(AG_BLACK);
		ag_line(dyv_ref(parent,0),dyv_ref(parent,1),
				dyv_ref(child,0),dyv_ref(child,1));
	  }
	}
	if(need_to_free_child_ctrs)
	  free_dyv_array(child_ctrs);
  }

  if(DEBUG_FLAG(D_DRAWPOINTS) && DEBUG_FLAG(D_INTERACTIVE)) {
	DBG_PRINTF(D_INTERACTIVE, ("Decided on tentative centers, now will k-mean them...\n"));
	wait_for_key();
  }

  /* run phased kmeans on new (parent+children) centers to convergence */
  kmstate.phase = PHS_PARENTS;
  kmstate.phase1_ctrs = parent_centers;
  memcpy(&parm, orig_parm, sizeof(*orig_parm));
  parm.mtd = orig_parm->usecache == CACHE_NONE ? MTD_BLACKLIST_NOCACHE : MTD_BLACKLIST;
  parm.cutoff_factor = 0.0;
  parm.max_iter = 30;			/* BUGBUG should be 10? 5? 30? data-dependant? */
  parm.max_secs = 30 * (int) parm.max_iter;	/* BUGBUG should depend on dimensinality? data size? */
  kmeans(&kmstate, &parm);
  if(DEBUG_FLAG(D_INTERACTIVE))
	DBG_PRINTF(D_INTERACTIVE, ("Done k-means for tentative centers\n"));

  /* get statistics for all children nodes */
  child = nodestats_mk_stats(1+vctr_max_pidx(centers), ndims, parm.maintain_memb);
  stats.stat = child;
  cached_bl_traversal(&kmstate, parent_centers, &stats, &parm);

  /* for each (parent,children) pair, ask are datapoints happier
	 to belong to parent or to children */
  {
	ret = mk_dyv(n_orig_centers);
	for(i=0; i<n_orig_centers; i++) {
	  int parent_idx = ivec_ref(parent_centers, i);
	  ivec *children = mk_vctr_children(centers, parent_idx);
      double val = bval(parent_idx, children,
                        &parent, &child,
                        centers, orig_parm->data_list);
	  dyv_set(ret, i, val);
	  if(DEBUG_FLAG(D_SHOW_BVALUE) && DEBUG_FLAG(D_DRAWPOINTS)) { /* BUGBUG doesn't show string for some reason */
		dyv *parent_coords = vctr_ref(centers, parent_idx);
		char tmp[128];
		sprintf(tmp, "BVALUE %6.2f", val);
		ag_set_pen_color(AG_BLACK);
		ag_print(dyv_ref(parent_coords, 0), dyv_ref(parent_coords, 1), tmp);
	  }
	  free_ivec(children);
	}
  }

  free_ivec(parent_centers_nonconst); parent_centers = 0;
  nodestats_free_stats(&parent);
  nodestats_free_stats(&child);

  return ret;
}

double mk_birth_value_single_BIC(int parent_idx, const ivec *children,
                                 const nodestats *parent_stats,
                                 const nodestats *children_stats,
                                 const vctr *centers,
                                 dyvp_array *data_list)
{
  double ret;

  ivec *singleton = mk_ivec(1);	/* to store single index */

  dist_info parent_dist, child_dist;
  double children_lklihood, parent_lklihood;

  parent_dist.distortion = parent_stats->distortion;
  parent_dist.nums = parent_stats->nums;
  child_dist.distortion = children_stats->distortion;
  child_dist.nums = children_stats->nums;
  children_lklihood = ctr_likelihood(centers, children, &child_dist);
  ivec_set(singleton, 0, parent_idx);
  parent_lklihood = ctr_likelihood(centers, singleton, &parent_dist);
  ret = parent_lklihood - children_lklihood;
  DBG_PRINTF(D_SHOW_BVALUE, ("BIC %6.2f (%6.2f)", parent_lklihood, children_lklihood));
  free_ivec(singleton);
  return ret;
}

double mk_birth_value_single_AD(int parent_idx, const ivec *children,
                                const nodestats *parent_stats,
                                const nodestats *children_stats,
                                const vctr *centers,
                                dyvp_array *data_list)
{
  double ret;
  double v_mag_sqd;
  /* we project all points that belong to the parent onto
     the segment that connects the children */
  const int parent_pidx = vctr_pidx(centers, parent_idx);
  ivec *parent_pts = ivec_array_ref(parent_stats->memb, parent_pidx);
  dyv *vals = mk_dyv(ivec_size(parent_pts));
  dyv_array *child_ctrs = mk_dyv_array(2);
  int i;

  dyv *v;

  /* sum up the two children */
  for(i=0; i<2; i++) {
	const int current_ctr = ivec_ref(children, i);
    const int current_ctr_pidx = vctr_pidx(centers, current_ctr);
    dyv *ctr = mk_copy_dyv(dyv_array_ref(children_stats->sums, current_ctr_pidx));
    dyv_scalar_mult(ctr, 1.0/ivec_ref(children_stats->nums, current_ctr_pidx), ctr);
    dyv_array_set(child_ctrs, i, ctr);
    free_dyv(ctr);
  }

  v = mk_dyv_subtract(dyv_array_ref(child_ctrs, 0), dyv_array_ref(child_ctrs, 1));
  /* now v is the segment connecting the centers */
  v_mag_sqd = dyv_scalar_product(v, v);

  /* for each point x compute the projection:
     <x, v> / ||v||^2
  */
  for(i=0; i<ivec_size(parent_pts); i++) {
    const int pt_idx = ivec_ref(parent_pts, i);
    const dyv *pt = dyvp_array_ref(data_list, pt_idx);
    dyv_set(vals, i, dyv_scalar_product(pt, v)/v_mag_sqd);
  }

  /* compute the Andreson-Darling statistic */
  ret = adjusted_anderson_darling(vals);
  /* we negate it so lower means more likely to split */
  ret *= -1;
  free_dyv(vals);
  free_dyv(v);
  free_dyv_array(child_ctrs);

  DBG_PRINTF(D_SHOW_BVALUE, ("AD %6.2f\n", ret));
  return ret;
}

double adjusted_anderson_darling(const dyv *vals)
{
  /* convert the values to have zero mean and unit variance */
  dyv *x = mk_copy_dyv(vals);
  const int n = dyv_size(vals);
  double sdev, A_sqd;
  int i;
  ivec *sort_idx;
  dyv *z;

  dyv_scalar_add(x, -dyv_mean(x), x); /* zero mean */
  sdev = dyv_sdev(x);
  assert(sdev >= 0);
  if(sdev > 0) {
    dyv_scalar_mult(x, 1.0/sdev, x); /* unit variance */
  }

  /* generate a list of sorted indices */
  sort_idx = mk_indices_of_sorted_dyv(x);

  /* compute z, the respective normal CDF values of x */
  z = mk_dyv(n);

  for(i=0; i<n; i++) {
    dyv_set(z, i,
            am_gauss_cdf(dyv_ref(x, ivec_ref(sort_idx, i))));
  }

  /* compute the Andreson-Darling statistic for standardized values */
  A_sqd = anderson_darling(z);

  /* apply correction for transformed values */
  A_sqd *= (1 + 4.0/n - 25.0/(n*n));

  free_dyv(x);
  free_ivec(sort_idx);
  free_dyv(z);
  return A_sqd;
}

double anderson_darling(const dyv *vals)
{
  int i;
  double ret = 0;
  const int n=dyv_size(vals);
  for(i=0; i<n; i++) {
    const int real_i = i+1;     /* counts from 1 to n */
    ret += (2*(real_i) -1)*(log(dyv_ref(vals, i)) + log(1 - dyv_ref(vals, n-1-i)));
  }
  ret /= -n;
  ret -= n;
  return ret;
}

void split_centers(simple_kdtree *kd, vctr *centers, const kmeans_parm *parm)
{
  dyv *birth_value;
  int n_orig_centers = vctr_live_size(centers);
  int i;
  ivec *parent_centers = mk_live_ctrs(centers);
  double cutoff;

  DBG_PRINTF(D_PASSES, ("Entering split_centers, %d orig centers\n", n_orig_centers));

  /* do nothing if not as many points as split centers */
  if(N_CHILDREN*n_orig_centers >= kd->num_points) {
	free_ivec(parent_centers);
	return;
  }

  /* get the difference between value of parent and children */
  birth_value = mk_birth_value(kd, centers, parm);

  /* for each (parent,children) pair, ask are datapoints happier
	 to belong to parent or to children */

  {
	/* sort entries in birth_value */
	ivec *sorted_bval = mk_indices_of_sorted_dyv(birth_value);
    int cutoff_idx;

    if(dyv_ref(birth_value, ivec_ref(sorted_bval, 0)) < parm->split_cutoff) {
      /* if there is at least one decision in favour of children, go for all
         children-better splits */
      cutoff = parm->split_cutoff;
    } else {
      /* everybody prefers parents: split the 1/2 worst-scoring parents */
      if(parm->split_fraction_if_all_bad == 0) {
        /* choose something below the minimum */
        cutoff = dyv_ref(birth_value,
                         ivec_ref(sorted_bval, 0)) - 10.0;
      } else {
        cutoff = dyv_ref(birth_value,
                         ivec_ref(sorted_bval, (int) (dyv_size(birth_value)*parm->split_fraction_if_all_bad)));
      }
    }
 
	/* adjust cutoff so there won't be more centers than allowed */
	/* find least index which whose value is more than cutoff */
	cutoff_idx = find_index_in_sorted_dyv(birth_value, sorted_bval, cutoff);
    /* A return value of -1 from find_index_in_sorted_dyv means none could
       be found. So the "cutoff index" is the biggest one in the vector. */
    if(cutoff_idx < 0)
      cutoff_idx = dyv_size(birth_value)-1;

	if(DEBUG_FLAG(D_SHOW_BVALUE)) {
	  fprintf_dyv(stderr, "bval", birth_value, "\n");
	  fprintf_ivec(stderr, "sbval", sorted_bval, "\n");
	}

	/* adjust: each splitting center adds N_CHILDREN - 1 centers */
	if(cutoff_idx > ((int)parm->max_ctrs - n_orig_centers)/(N_CHILDREN - 1))
	  cutoff_idx = ((int)parm->max_ctrs - n_orig_centers)/(N_CHILDREN - 1);
	/* convert back into a cutoff value */
	if(cutoff_idx > 0)
	  cutoff = dyv_ref(birth_value, ivec_ref(sorted_bval, cutoff_idx));
	else if(n_orig_centers == 1) { /* force single center to split */
	  cutoff_idx = 0;
	  cutoff = dyv_ref(birth_value, 0) + 1;
	}
	free_ivec(sorted_bval);
  }

  for(i=0; i<n_orig_centers; i++) {
	int parent_idx = ivec_ref(parent_centers, i);
	DBG_PRINTF(D_SHOW_BVALUE, ("Center %d bvalue %g\n", parent_idx, dyv_ref(birth_value, i)));
	if(dyv_ref(birth_value, i) < cutoff) {
	  /* prefer children -- kill parent */
	  remove_from_vctr(centers, parent_idx, FALSE);
	} else {
	  /* prefer parent -- kill children */
	  remove_children_from_vctr(centers, parent_idx);
	}
  }

  DBG_PRINTF(D_SHOW_BVALUE, ("----------\n"));


  if(DEBUG_FLAG(D_DRAWPOINTS)) {
	int i;
	ag_on(""); /* BUGBUG: draw all points now? */
	for(i=get_first_live_vctr(centers); is_valid_vctr(centers, i); i=get_next_live_vctr(centers, i)) {
	  dyv *center_i = vctr_ref(centers, i);
	  ag_set_pen_color(center_color(vctr_pidx(centers, i)));
	  ag_disc(dyv_ref(center_i,0),dyv_ref(center_i,1),5.0);
	}
	if(DEBUG_FLAG(D_INTERACTIVE)) {
	  DBG_PRINTF(D_INTERACTIVE, ("These are the surviving centers...\n"));
	  wait_for_key();
	}
	ag_off();
  }
  DBG_PRINTF(D_PASSES, ("Exiting split_centers, now %d centers\n", vctr_live_size(centers)));
  free_ivec(parent_centers);
  free_dyv_NaN_ok(birth_value);
}

double bic_score(simple_kdtree *kd, vctr *centers, const kmeans_parm *parm,
                 const ivec *ctrlist)
{
  dist_info distinfo = mk_kmeans_distinfo(kd, centers, parm);
  /* if no centers were specified, use them all */
  ivec *ctrs = 0;
  double ret;
  if(!ctrlist)
    ctrs = mk_live_ctrs(centers);

  ret = ctr_likelihood(centers, ctrlist ? ctrlist : ctrs, &distinfo);
  free_dyv(distinfo.distortion);
  free_ivec(distinfo.nums);
  if(!ctrlist)
    free_ivec(ctrs);
  return ret;
}

static double ctr_likelihood(const vctr *centers, const ivec *focus, const dist_info *distinfo)
{
  double ret = 0.0;
  int i;
  int num_dims = dyv_size(vctr_ref(centers, get_first_live_vctr(centers)));
  int num_ctrs = ivec_size(focus);
  int owned;					/* total #pts owned by ctrs */
  double loglik;				/* log-likelikhood of data given ctrs */
  int num_parameters = (num_ctrs - 1) + /* probabilities */
	num_ctrs * num_dims +		/* means */
	num_ctrs;					/* variance parms */

  DBG_PRINTF(D_SHOW_LKLIHOOD, ("Starting ctr_likelihood...\n"));

  for(i=0, loglik=0, owned=0; i<num_ctrs; i++) {
	const int current_ctr = ivec_ref(focus, i);
	/* compute log-likelihood for this ctr */
	/* dist_curr: sum([x_i - u_i]^2) */
	const int current_ctr_pidx = vctr_pidx(centers, current_ctr);
	double dist_curr = dyv_ref(distinfo->distortion, current_ctr_pidx);
	int owned_curr = ivec_ref(distinfo->nums, current_ctr_pidx);
	/* if center owns just one point, then likelihood of point is 1*/
	/* and so log-likelihood is zero */
	if(owned_curr > 1) {
	  double variance_curr = dist_curr/(owned_curr-1.0); /* BUGBUG: should be s/1.0/num_ctrs/?  */
	  double loglik_curr = -owned_curr/2.0*log(2*M_PI) 
		- owned_curr*num_dims/2.0*log(variance_curr)
		- (owned_curr-1)/2.0
		+ owned_curr*log((double) owned_curr);
	  DBG_PRINTF(D_SHOW_LKLIHOOD,
				 ("loglik_curr %6.3g\n", loglik_curr));
	  /* add loglik to running total */
	  loglik += loglik_curr;
	}
	owned += owned_curr;
	DBG_PRINTF(D_SHOW_LKLIHOOD,
			   ("item %2d(=%2d), dist %6.3g owned %d loglik %6.3g\n",
				i, current_ctr, dist_curr, owned, loglik));
  }
  loglik -= owned*log((double) owned);	/* account for effect of class probs */

  /* BIC = log-likelihood - ((# of parameters / 2) * log(# of observations)) */
  
  ret = loglik - num_parameters/2.0*log((double) owned);
  DBG_PRINTF(D_SHOW_LKLIHOOD,
			 ("loglik %6.3g\n", loglik));

  DBG_PRINTF(D_SHOW_LKLIHOOD,
			 ("BIC score %6.3g\n",
			  ret));
  return ret;
}

int find_index_in_sorted_dyv(const dyv *dyv, const ivec *sorted_dyv, double val)
{
  int ret;
  const ivec *my_sorted_dyv = (sorted_dyv == NULL) ? mk_indices_of_sorted_dyv(dyv) : sorted_dyv;

  ret = find_index_in_sorted_dyv_recursive(dyv, my_sorted_dyv, val, 0, ivec_size(my_sorted_dyv)-1);

  if(sorted_dyv == NULL)
	free_ivec((ivec *)my_sorted_dyv);

  return ret;
}

int find_index_in_sorted_dyv_recursive(const dyv *dyv, const ivec *sorted_dyv,
									   double val, int lo, int hi)
{
  int mid;
#ifndef AMFAST
  if(lo > hi)
	my_error("lo>hi in find_index_in_dyv_recursive");
#endif /* AMFAST */
  if(lo == hi) {
	if(dyv_ref(dyv, ivec_ref(sorted_dyv, lo)) > val)
	  return lo;
	else
	  if(lo < dyv_size(dyv)-1)
		return lo+1;
	  else
		return -1;
  }
  /* hi > lo */
  mid = (hi+lo)/2;
  if(dyv_ref(dyv, ivec_ref(sorted_dyv, mid)) < val) /* in (mid+1..hi) */
	return find_index_in_sorted_dyv_recursive(dyv, sorted_dyv, val, mid+1, hi); 
  /* must be in lo..mid */
  return find_index_in_sorted_dyv_recursive(dyv, sorted_dyv, val, lo, mid); 
}

void delete_ctr(simple_kdtree *kd, vctr *centers, const kmeans_parm *orig_parm,
                bool only_delete_if_improves_bic)
{
  kmeans_parm parm;
  ivec *orig_ctrs = mk_live_ctrs(centers);

  double bicscore_all_ctrs = 0.0;

  /* for each center, pull it out, compute BIC score, record best one */

  double best_score = -HUGE_VAL;
  int best_scorer = -1;      /* vctr # */
  int skip;

  ivec *tentative_ctrs = mk_ivec(0);

  if(only_delete_if_improves_bic) {
    /* record the score before we started playing with things */
    bicscore_all_ctrs = bic_score(kd, centers, orig_parm, NULL);
    DBG_PRINTF(D_DELETE_CTRS, ("Not ignoring any center, the BIC is %g\n",
                               bicscore_all_ctrs));
  }

  if(ivec_size(orig_ctrs) <= 1) /* not much to do here */
    return;

  /* set up a parm record, that doesn't alter the cache */
  memcpy(&parm, orig_parm, sizeof(*orig_parm));
  parm.usecache = CACHE_RD_ONLY;

  for(skip=0; skip<ivec_size(orig_ctrs); skip++) {
    double bicscore;
    /* make a list of all live centers, except for the one we're skipping */
    /* The invariant for this list is:
       It contains the first <skip> elements of orig_ctrs, in order,
       (up to index <skip>, non-inclusive)
       followed by the elements in indices <skip+1> an onwards */
    if(skip == 0) {
      /* first time around -- create the list */
      int j;
      for(j=skip+1; j<ivec_size(orig_ctrs); j++) { /* start from _second_ element */
        add_to_ivec(tentative_ctrs, ivec_ref(orig_ctrs, j));
      }
    } else {
      /* The element previously pointed by <skip> goes in, and
       replaces the element in the <skip-1> index*/
      ivec_set(tentative_ctrs, skip-1, ivec_ref(orig_ctrs, skip-1));
    }

    /* get assignment of points for current set of centers, 
       and the BIC for this assignment */
    bicscore = bic_score(kd, centers, &parm, tentative_ctrs);

    DBG_PRINTF(D_DELETE_CTRS, ("Ignoring ctr #%d, the BIC is %g\n",
                               ivec_ref(orig_ctrs, skip), bicscore));

    /* record if it's the best seen */
    if(bicscore > best_score) {
      best_score = bicscore;
      best_scorer = ivec_ref(orig_ctrs, skip);
    }
  }

#ifndef AMFAST
  if(best_scorer == -1)
	my_error("best_scorer == -1");
#endif /* AMFAST */

  /* now get rid of the center who scored "best" */
  if(!only_delete_if_improves_bic || best_score > bicscore_all_ctrs) {
    DBG_PRINTF(D_DELETE_CTRS, ("Removing ctr #%d\n", best_scorer));
    remove_from_vctr(centers, best_scorer, TRUE);
  } else {
    DBG_PRINTF(D_DELETE_CTRS, ("Not removing any centers, but ctr #%d was my candidate\n", best_scorer));
  }

  /* free dynamically-created lists */
  free_ivec(tentative_ctrs);
  free_ivec(orig_ctrs);
}
