Because residue codebooks use very coarse quantization, I needed to
authorMonty <xiphmont@xiph.org>
Mon, 21 Feb 2000 01:13:02 +0000 (01:13 +0000)
committerMonty <xiphmont@xiph.org>
Mon, 21 Feb 2000 01:13:02 +0000 (01:13 +0000)
beef up the trainer for residuals to not constantly quantize cells to
the same midpoint (causing all hell to break loose).

Monty

svn path=/trunk/vorbis/; revision=264

vq/bookutil.c
vq/bookutil.h
vq/genericdata.c
vq/lspdata.c
vq/residuedata.c
vq/train.c
vq/vqext.h
vq/vqgen.c
vq/vqgen.h
vq/vqsplit.c

index cf21804..8976c3d 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: utility functions for loading .vqh and .vqd files
- last mod: $Id: bookutil.c,v 1.10 2000/02/16 16:18:33 xiphmont Exp $
+ last mod: $Id: bookutil.c,v 1.11 2000/02/21 01:12:53 xiphmont Exp $
 
  ********************************************************************/
 
@@ -298,11 +298,16 @@ codebook *codebook_load(char *filename){
   return(b);
 }
 
+
+/* the new version!  we have ptr0[0] distinct trees in the auxiliary
+   encoding structure.  Find the best match in each one, choosing the
+   best global match */
+
 int codebook_entry(codebook *b,double *val){
   const static_codebook *c=b->c;
   encode_aux *t=c->encode_tree;
-  int ptr=0,k;
+  int trees=t->ptr0[0];
+
   /*{
     brute force 
     double this,best=_dist(c->dim,val,b->valuelist);
@@ -317,28 +322,46 @@ int codebook_entry(codebook *b,double *val){
   }*/
   
   double *n=alloca(c->dim*sizeof(double));
-  
-  while(1){
-    double C=0.;
-    double *p=b->valuelist+t->p[ptr];
-    double *q=b->valuelist+t->q[ptr];
-    
-    for(k=0;k<c->dim;k++){
-      n[k]=p[k]-q[k];
-      C-=(p[k]+q[k])*n[k];
+  double bestmetric;
+  double best=-1;
+  while(trees>0){
+    int ptr=t->ptr0[--trees],k;
+
+    while(1){
+      double C=0.;
+      double *p=b->valuelist+t->p[ptr];
+      double *q=b->valuelist+t->q[ptr];
+      
+      for(k=0;k<c->dim;k++){
+       n[k]=p[k]-q[k];
+       C-=(p[k]+q[k])*n[k];
+      }
+      C/=2.;
+      
+      for(k=0;k<c->dim;k++)
+       C+=n[k]*val[k];
+      
+      if(C>0.) /* in A */
+       ptr= -t->ptr0[ptr];
+      else     /* in B */
+       ptr= -t->ptr1[ptr];
+      if(ptr<=0)break;
+    }
+
+    {
+      double dist=0.;
+      for(k=0;k<c->dim;k++){
+       double one=val[k]-(b->valuelist-ptr*c->dim)[k];
+       dist+=one*one;
+      }
+      if(best==-1 || dist<bestmetric){
+       best=-ptr;
+       bestmetric=dist;
+      }
     }
-    C/=2.;
-    
-    for(k=0;k<c->dim;k++)
-      C+=n[k]*val[k];
-    
-    if(C>0.) /* in A */
-      ptr= -t->ptr0[ptr];
-    else     /* in B */
-      ptr= -t->ptr1[ptr];
-    if(ptr<=0)break;
   }
-  return(-ptr);
+
+  return(best);
 }
 
 /* 24 bit float (not IEEE; nonnormalized mantissa +
@@ -399,3 +422,51 @@ void spinnit(char *s,int n){
   }
 }
 
+void build_tree_from_lengths(int vals, long *hist, long *lengths){
+  int i,j;
+  long *membership=malloc(vals*sizeof(long));
+      
+  for(i=0;i<vals;i++)membership[i]=i;
+
+  /* find codeword lengths */
+  /* much more elegant means exist.  Brute force n^2, minimum thought */
+  for(i=vals;i>1;i--){
+    int first=-1,second=-1;
+    long least=-1;
+       
+    spinnit("building... ",i);
+    
+    /* find the two nodes to join */
+    for(j=0;j<vals;j++)
+      if(least==-1 || hist[j]<least){
+       least=hist[j];
+       first=membership[j];
+      }
+    least=-1;
+    for(j=0;j<vals;j++)
+      if((least==-1 || hist[j]<least) && membership[j]!=first){
+       least=hist[j];
+       second=membership[j];
+      }
+    if(first==-1 || second==-1){
+      fprintf(stderr,"huffman fault; no free branch\n");
+      exit(1);
+    }
+    
+    /* join them */
+    least=hist[first]+hist[second];
+    for(j=0;j<vals;j++)
+      if(membership[j]==first || membership[j]==second){
+       membership[j]=first;
+       hist[j]=least;
+       lengths[j]++;
+      }
+  }
+  for(i=0;i<vals-1;i++)
+    if(membership[i]!=membership[i+1]){
+      fprintf(stderr,"huffman fault; failed to build single tree\n");
+      exit(1);
+    }
+  
+  free(membership);
+}
index 55c787c..cc5f4bc 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: utility functions for loading .vqh and .vqd files
- last mod: $Id: bookutil.h,v 1.3 2000/01/10 10:42:02 xiphmont Exp $
+ last mod: $Id: bookutil.h,v 1.4 2000/02/21 01:12:54 xiphmont Exp $
 
  ********************************************************************/
 
@@ -39,6 +39,7 @@ extern int       codebook_entry(codebook *b,double *val);
 extern void spinnit(char *s,int n);
 extern long float24_pack(double val);
 extern double float24_unpack(long val);
+extern void build_tree_from_lengths(int vals, long *hist, long *lengths);
 
 #endif
 
index 37416fb..08a31bb 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: generic euclidian distance metric for VQ codebooks
- last mod: $Id: genericdata.c,v 1.3 2000/02/16 16:18:35 xiphmont Exp $
+ last mod: $Id: genericdata.c,v 1.4 2000/02/21 01:12:55 xiphmont Exp $
 
  ********************************************************************/
 
@@ -26,7 +26,6 @@ char *vqext_booktype="GENERICdata";
 int vqext_aux=0;                
 quant_meta q={0,0,0,0};          /* non sequence data; each scalar 
                                    independent */
-double vqext_mindist=0.;
 
 void vqext_quantize(vqgen *v,quant_meta *q){
   vqgen_quantize(v,q);
index bd839b8..a377eb7 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: metrics and quantization code for LSP VQ codebooks
- last mod: $Id: lspdata.c,v 1.10 2000/02/16 16:18:36 xiphmont Exp $
+ last mod: $Id: lspdata.c,v 1.11 2000/02/21 01:12:56 xiphmont Exp $
 
  ********************************************************************/
 
@@ -25,7 +25,6 @@
 char *vqext_booktype="LSPdata";  
 quant_meta q={0,0,0,1};          /* set sequence data */
 int vqext_aux=1;
-double vqext_mindist=0.;
 
 void vqext_quantize(vqgen *v,quant_meta *q){
   vqgen_quantize(v,q);
index 2c75f68..a1b68a9 100644 (file)
  ********************************************************************
 
  function: metrics and quantization code for residue VQ codebooks
- last mod: $Id: residuedata.c,v 1.1 2000/02/16 16:18:38 xiphmont Exp $
+ last mod: $Id: residuedata.c,v 1.2 2000/02/21 01:12:57 xiphmont Exp $
 
  ********************************************************************/
 
 #include <stdlib.h>
 #include <math.h>
 #include <stdio.h>
+#include <string.h>
 #include "vqgen.h"
 #include "bookutil.h"
 #include "vqext.h"
@@ -27,7 +28,7 @@ char *vqext_booktype="RESdata";
 quant_meta q={0,0,0,0};          /* set sequence data */
 int vqext_aux=0;
 
-double vqext_mindist=.7; /* minimum desired cell radius */
+static double *quant_save=NULL;
 
 /* LSP training metric.  We weight error proportional to distance
    *between* LSP vector values.  The idea of this metric is not to set
@@ -40,19 +41,10 @@ double *vqext_weight(vqgen *v,double *p){
 }
 
 /* quantize aligned on unit boundaries */
-void vqext_quantize(vqgen *v,quant_meta *q){
-  int i,j,k;
-  double min,max,delta=1.;
-  int vals=(1<<q->quant);
-
-  min=max=_now(v,0)[0];
-  for(i=0;i<v->entries;i++)
-    for(j=0;j<v->elements;j++){
-      if(max<_now(v,i)[j])max=_now(v,i)[j];
-      if(min>_now(v,i)[j])min=_now(v,i)[j];
-    }
 
-  /* roll the delta */
+static double _delta(double min,double max,int bits){
+  double delta=1.;
+  int vals=(1<<bits);
   while(1){
     double qmax=rint(max/delta);
     double qmin=rint(min/delta);
@@ -64,19 +56,53 @@ void vqext_quantize(vqgen *v,quant_meta *q){
       delta*=.5;
     }else{
       min=qmin*delta;
-      q->min=float24_pack(qmin*delta);
-      q->delta=float24_pack(delta);
       break;
     }
   }
+  return(delta);
+}
 
-  for(j=0;j<v->entries;j++){
-    for(k=0;k<v->elements;k++){
+void vqext_quantize(vqgen *v,quant_meta *q){
+  int i,j,k;
+  long dim=v->elements;
+  long n=v->entries;
+  double min,vals,delta=1.;
+  double *test=alloca(sizeof(double)*dim);
+  int moved=0;
+
+  min=-((1<<q->quant)/2-1);
+  vals=1<<q->quant;
+  q->min=float24_pack(rint(min/delta)*delta);
+  q->delta=float24_pack(delta);
+  
+  /* allow movement only to unoccupied coordinates on the coarse grid */
+  for(j=0;j<n;j++){
+    for(k=0;k<dim;k++){
       double val=_now(v,j)[k];
-      double now=rint((val-min)/delta);
-      _now(v,j)[k]=now;
+      val=rint((val-min)/delta);
+      if(val<0)val=0;
+      if(val>=vals)val=vals-1;
+      test[k]=val;
+    }
+    /* allow move only if unoccupied */
+    if(quant_save){
+      for(k=0;k<n;k++)
+       if(j!=k && memcmp(test,quant_save+dim*k,dim*sizeof(double))==0)
+         break;
+      if(k==n){
+       if(memcmp(test,quant_save+dim*j,dim*sizeof(double)))    
+         moved++;
+       memcpy(quant_save+dim*j,test,sizeof(double)*dim);
+      }
+    }else{
+      memcpy(_now(v,j),test,sizeof(double)*dim);
     }
   }
+
+  if(quant_save){
+    memcpy(_now(v,0),quant_save,sizeof(double)*dim*n);
+    fprintf(stderr,"cells shifted this iteration: %d\n",moved);
+  }
 }
 
 /* this should probably go to a x^6/4 error metric */
@@ -96,5 +122,59 @@ void vqext_addpoint_adj(vqgen *v,double *b,int start,int dim,int cols){
   vqgen_addpoint(v,b+start,NULL);
 }
 
+/* need to reseed because of the coarse quantization we tend to use on
+   residuals (which causes lots & lots of dupes) */
 void vqext_preprocess(vqgen *v){
+  long i,j,k,l,min,max;
+  double *test=alloca(sizeof(double)*v->elements);
+
+  vqext_quantize(v,&q);
+  vqgen_unquantize(v,&q);
+
+  /* if there are any dupes, reseed */
+  for(k=0;k<v->entries;k++){
+    for(l=0;l<k;l++){
+      if(memcmp(_now(v,k),_now(v,l),sizeof(double)*v->elements)==0)
+       break;
+    }
+    if(l<k)break;
+  }
+
+  if(k<v->entries){
+    fprintf(stderr,"reseeding with quantization....\n");
+    min=-((1<<q.quant)/2-1);
+    max=min+(1<<q.quant)-1;
+
+    /* seed the inputs to input points, but points on unit boundaries,
+     ignoring quantbits for now, making sure each seed is unique */
+    
+    for(i=0,j=0;i<v->points && j<v->entries;i++){
+      for(k=0;k<v->elements;k++){
+       test[k]=rint(_point(v,i)[k]);
+       if(test[k]<min)test[k]=min;
+       if(test[k]>max)test[k]=max;
+      }
+      
+      for(l=0;l<j;l++){
+       for(k=0;k<v->elements;k++)
+         if(test[k]!=_now(v,l)[k])
+           break;
+       if(k==v->elements)break;
+      }
+      if(l==j){
+       memcpy(_now(v,j),test,v->elements*sizeof(double));
+       j++;
+      }
+    }
+    
+    if(j<v->elements){
+      fprintf(stderr,"Not enough unique entries after prequantization\n");
+      exit(1);
+    }
+  }  
+  vqext_quantize(v,&q);
+  quant_save=malloc(sizeof(double)*v->elements*v->entries);
+  memcpy(quant_save,_now(v,0),sizeof(double)*v->elements*v->entries);
+  vqgen_unquantize(v,&q);
+
 }
index 2f99c93..a30ec1a 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: utility main for training codebooks
- last mod: $Id: train.c,v 1.15 2000/02/16 16:18:38 xiphmont Exp $
+ last mod: $Id: train.c,v 1.16 2000/02/21 01:12:58 xiphmont Exp $
 
  ********************************************************************/
 
@@ -52,7 +52,8 @@ static void usage(void){
          "options: -p[arams]     <entries,dim,quant>\n"
          "         -s[ubvector]  <start[,num]>\n"
          "         -e[rror]      <desired_error>\n"
-         "         -i[terations] <maxiterations>\n\n"
+         "         -i[terations] <maxiterations>\n"
+         "         -d[istance]   desired minimum cell radius from midpoint\n\n"
          "examples:\n"
          "   train a new codebook to 1%% tolerance on datafile 'foo':\n"
          "      xxxvqtrain book -p 256,6,8 -e .01 foo\n"
@@ -75,7 +76,7 @@ int main(int argc,char *argv[]){
 
   int entries=-1,dim=-1;
   int start=0,num=-1;
-  double desired=.05;
+  double desired=.05,mindist=0.;
   int iter=1000;
 
   FILE *out=NULL;
@@ -128,7 +129,7 @@ int main(int argc,char *argv[]){
        exit(1);
       }
       
-      vqgen_init(&v,dim,vqext_aux,entries,vqext_mindist,
+      vqgen_init(&v,dim,vqext_aux,entries,mindist,
                 vqext_metric,vqext_weight);
       init=1;
       
@@ -204,6 +205,10 @@ int main(int argc,char *argv[]){
        if(sscanf(argv[1],"%lf",&desired)!=1)
          goto syner;
        break;
+      case 'd':
+       if(sscanf(argv[1],"%lf",&mindist)!=1)
+         goto syner;
+       break;
       case 'i':
        if(sscanf(argv[1],"%d",&iter)!=1)
          goto syner;
@@ -224,7 +229,7 @@ int main(int argc,char *argv[]){
          fprintf(stderr,"-p required when training a new set\n");
          exit(1);
        }
-       vqgen_init(&v,dim,vqext_aux,entries,vqext_mindist,
+       vqgen_init(&v,dim,vqext_aux,entries,mindist,
                   vqext_metric,vqext_weight);
        init=1;
       }
index ba3c2b7..986a32a 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: prototypes for extermal metrics specific to data type
- last mod: $Id: vqext.h,v 1.7 2000/02/16 16:18:39 xiphmont Exp $
+ last mod: $Id: vqext.h,v 1.8 2000/02/21 01:12:59 xiphmont Exp $
 
  ********************************************************************/
 
@@ -24,7 +24,6 @@
 extern char *vqext_booktype;
 extern quant_meta q;
 extern int vqext_aux;
-extern double vqext_mindist;
 
 extern double vqext_metric(vqgen *v,double *e, double *p);
 extern double *vqext_weight(vqgen *v,double *p);
index 4f042da..858c84d 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: train a VQ codebook 
- last mod: $Id: vqgen.c,v 1.29 2000/02/16 16:18:40 xiphmont Exp $
+ last mod: $Id: vqgen.c,v 1.30 2000/02/21 01:13:00 xiphmont Exp $
 
  ********************************************************************/
 
@@ -135,7 +135,7 @@ void vqgen_cellmetric(vqgen *v){
 #endif
   }
 
-  fprintf(stderr,"cell diameter: %.03g::%.03g::%.03g (%d unused/%d dup)\n",
+  fprintf(stderr,"cell diameter: %.03g::%.03g::%.03g (%ld unused/%ld dup)\n",
          min,mean/acc,max,unused,dup);
 
 #ifdef NOISY
index 9c78c7d..738484b 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: build a VQ codebook 
- last mod: $Id: vqgen.h,v 1.11 2000/02/16 16:18:41 xiphmont Exp $
+ last mod: $Id: vqgen.h,v 1.12 2000/02/21 01:13:01 xiphmont Exp $
 
  ********************************************************************/
 
@@ -71,6 +71,7 @@ extern void vqgen_addpoint(vqgen *v, double *p,double *aux);
 extern double vqgen_iterate(vqgen *v);
 extern void vqgen_unquantize(vqgen *v,quant_meta *q);
 extern void vqgen_quantize(vqgen *v,quant_meta *q);
+extern void vqgen_cellmetric(vqgen *v);
 
 #endif
 
index 1a28df4..f9cc02b 100644 (file)
@@ -12,7 +12,7 @@
  ********************************************************************
 
  function: build a VQ codebook and the encoding decision 'tree'
- last mod: $Id: vqsplit.c,v 1.16 2000/02/16 16:18:42 xiphmont Exp $
+ last mod: $Id: vqsplit.c,v 1.17 2000/02/21 01:13:02 xiphmont Exp $
 
  ********************************************************************/
 
@@ -391,8 +391,6 @@ static int _node_eq(encode_aux *v, long a, long b){
 }
 
 void vqsp_book(vqgen *v, codebook *b, long *quantlist){
-  long *entryindex=malloc(sizeof(long)*v->entries);
-  long *pointindex=malloc(sizeof(long)*v->points);
   long i,j;
   static_codebook *c=(static_codebook *)b->c;
   encode_aux *t;
@@ -454,79 +452,151 @@ void vqsp_book(vqgen *v, codebook *b, long *quantlist){
     }
   }
 
-  fprintf(stderr,"Building a book with %d unique entries...\n",v->entries);
+  fprintf(stderr,"Building a book with %ld unique entries...\n",v->entries);
+
+  /* We don't always generate a single tree; it tends to be large.  We
+     trade off by splitting the cell and point sets into n totally
+     non-overlapping subsets (where the decision tree tends to have
+     substantial overlap in hyperplane splits) and generating a tree
+     for each.  Because the tree depth is O(n) o(lg(n)), the smaller
+     data set will get us much better storage usage for worst cases
+     (and we will run toward worst case) */
 
-  for(i=0;i<v->entries;i++)entryindex[i]=i;
-  for(i=0;i<v->points;i++)pointindex[i]=i;
-  
-  c->dim=v->elements;
-  c->entries=v->entries;
-  c->lengthlist=calloc(c->entries,sizeof(long));
-
-  t->alloc=4096;
-  t->ptr0=malloc(sizeof(long)*t->alloc);
-  t->ptr1=malloc(sizeof(long)*t->alloc);
-  t->p=malloc(sizeof(long)*t->alloc);
-  t->q=malloc(sizeof(long)*t->alloc);
-  
-  /* generate the encoding decision heirarchy */
   {
-    long pointsofar=0;
-    fprintf(stderr,"Total leaves: %d            \n",
-           lp_split(v,b,entryindex,v->entries, 
-                    pointindex,v->points,0,&pointsofar));
-  }
+    /* split data set... */
+    int trees=v->entries/(32+log(v->entries)/log(2)*2)+1; /* a guess */
+    long *entrymap=malloc(v->entries*sizeof(long *));
+    long **entryindex=malloc(trees*sizeof(long *));
+    long *entries=calloc(trees,sizeof(long));
+    long *pointmap=malloc(v->points*sizeof(long));
+    long desired=(v->entries/trees)+1;
+    int booknum;
+    long pointssofar=0;
+      
+    for(i=0;i<trees;i++)entryindex[i]=calloc(desired,sizeof(long));
 
-  free(entryindex);
-  free(pointindex);
+    fprintf(stderr,"Prepartitioning into %d trees...\n",trees);
 
-  fprintf(stderr,"Paring and rerouting redundant branches:\n");
-  /* The tree is likely big and redundant.  Pare and reroute branches */
-  {
-    int changedflag=1;
+    /* map points to cells */
+    for(i=0;i<v->points;i++){
+      double *ppt=_point(v,i);
+      double firstmetric=_dist(v,_now(v,0),ppt);
+      long   firstentry=0;
+      
+      if(!(i&0xff))spinnit("parititoning... ",v->points-i);
 
-    while(changedflag){
-      changedflag=0;
+      for(j=0;j<v->entries;j++){
+       double thismetric=_dist(v,_now(v,j),ppt);
+       if(thismetric<firstmetric){
+         firstmetric=thismetric;
+         firstentry=j;
+       }
+      }
+      pointmap[i]=firstentry;
+    }
 
-      fprintf(stderr,"\t...");
-      /* span the tree node by node; list unique decision nodes and
-        short circuit redundant branches */
+    /* this could be more effective; long, narrow slices are probably
+       better than these porous blobs */
 
-      for(i=0;i<t->aux;){
-       int k;
+    for(j=0;j<v->entries;j++){
+      int choice=-1;
+      for(i=0;i<trees;i++){
+       if(entries[i]<desired){ /* still has open space */
+         choice=i;
+         break;
+       }
+      }
 
-       /* check list of unique decisions */
-       for(j=0;j<i;j++)
-         if(_node_eq(t,i,j))break;
+      entryindex[choice][entries[choice]++]=j;
+      entrymap[j]=choice;
+    }
 
-       if(j<i){
-         /* a redundant entry; find all higher nodes referencing it and
-             short circuit them to the previously noted unique entry */
-         changedflag=1;
-         for(k=0;k<t->aux;k++){
-           if(t->ptr0[k]==-i)t->ptr0[k]=-j;
-           if(t->ptr1[k]==-i)t->ptr1[k]=-j;
-         }
+    
+    /* we overload the static codebook ptrs just a bit...  the first
+       <tree> ptr0s just point to the starting point in the struct for
+       that tree.  The first ptr0 points to the first tree at offset
+       <tree>, and thus can be used as the tree count.  Clever but
+       totally opaque :-( . */
+    
+    t->alloc=4096;
+    t->ptr0=malloc(sizeof(long)*t->alloc);
+    t->ptr1=malloc(sizeof(long)*t->alloc);
+    t->p=malloc(sizeof(long)*t->alloc);
+    t->q=malloc(sizeof(long)*t->alloc);
+    t->aux=trees; 
+    c->dim=v->elements;
+    c->entries=v->entries;
+    c->lengthlist=calloc(c->entries,sizeof(long));
+    
+    for(booknum=0;booknum<trees;booknum++){
+      long points=0;
+      long *pointindex;
+
+      t->ptr0[booknum]=t->aux;
+      for(i=0;i<v->points;i++)
+       if(entrymap[pointmap[i]]==booknum)points++;
+      pointindex=malloc(points*sizeof(long));
+      points=0;
+      for(i=0;i<v->points;i++)
+       if(entrymap[pointmap[i]]==booknum)pointindex[points++]=i;
+
+      /* generate the encoding decision heirarchy */
+      fprintf(stderr,"Leaves added: %d              \n",
+             lp_split(v,b,entryindex[booknum],entries[booknum],
+                      pointindex,points,0,&pointssofar));
+      
+      free(pointindex);
+      fprintf(stderr,"Paring/rerouting redundant branches... ");
 
-         /* Now, we need to fill in the hole from this redundant
-             entry in the listing.  Insert the last entry in the list.
-             Fix the forward pointers to that last entry */
-         t->aux--;
-         t->ptr0[i]=t->ptr0[t->aux];
-         t->ptr1[i]=t->ptr1[t->aux];
-         t->p[i]=t->p[t->aux];
-         t->q[i]=t->q[t->aux];
-         for(k=0;k<t->aux;k++){
-           if(t->ptr0[k]==-t->aux)t->ptr0[k]=-i;
-           if(t->ptr1[k]==-t->aux)t->ptr1[k]=-i;
+      /* The tree is likely big and redundant.  Pare and reroute branches */
+      {
+       int changedflag=1;
+       
+       while(changedflag){
+         changedflag=0;
+         
+         /* span the tree node by node; list unique decision nodes and
+            short circuit redundant branches */
+         
+         for(i=t->ptr0[booknum];i<t->aux;){
+           int k;
+
+           /* check list of unique decisions */
+           for(j=t->ptr0[booknum];j<i;j++)
+             if(_node_eq(t,i,j))break;
+
+           if(j<i){
+             /* a redundant entry; find all higher nodes referencing it and
+                short circuit them to the previously noted unique entry */
+             changedflag=1;
+             for(k=0;k<t->aux;k++){
+               if(t->ptr0[k]==-i)t->ptr0[k]=-j;
+               if(t->ptr1[k]==-i)t->ptr1[k]=-j;
+             }
+
+             /* Now, we need to fill in the hole from this redundant
+                entry in the listing.  Insert the last entry in the list.
+                Fix the forward pointers to that last entry */
+             t->aux--;
+             t->ptr0[i]=t->ptr0[t->aux];
+             t->ptr1[i]=t->ptr1[t->aux];
+             t->p[i]=t->p[t->aux];
+             t->q[i]=t->q[t->aux];
+             for(k=0;k<t->aux;k++){
+               if(t->ptr0[k]==-t->aux)t->ptr0[k]=-i;
+               if(t->ptr1[k]==-t->aux)t->ptr1[k]=-i;
+             }
+             /* hole plugged */
+             
+           }else
+             i++;
          }
-         /* hole plugged */
-
-       }else
-         i++;
+         
+         fprintf(stderr,"\rParing/rerouting redundant branches... "
+                 "%ld remaining   ",t->aux);
+       }
+       fprintf(stderr,"\n");
       }
-
-      fprintf(stderr," %ld remaining\n",t->aux);
     }
   }
 
@@ -534,64 +604,18 @@ void vqsp_book(vqgen *v, codebook *b, long *quantlist){
      probability count */
   {
     long *probability=malloc(c->entries*sizeof(long));
-    long *membership=malloc(c->entries*sizeof(long));
-
     for(i=0;i<c->entries;i++)probability[i]=1; /* trivial guard */
-
     b->valuelist=v->entrylist; /* temporary for vqenc_entry; replaced later */
-    for(i=0;i<v->points;i++){
-      int ret=codebook_entry(b,v->pointlist+i*v->elements);
-      probability[ret]++;
-    }
-    for(i=0;i<v->entries;i++)membership[i]=i;
-
-    /* find codeword lengths */
-    /* much more elegant means exist.  Brute force n^2, minimum thought */
-    for(i=v->entries;i>1;i--){
-      int first=-1,second=-1;
-      long least=v->points+1;
-
-      /* find the two nodes to join */
-      for(j=0;j<v->entries;j++)
-       if(probability[j]<least){
-         least=probability[j];
-         first=membership[j];
-       }
-      least=v->points+1;
-      for(j=0;j<v->entries;j++)
-       if(probability[j]<least && membership[j]!=first){
-         least=probability[j];
-         second=membership[j];
-       }
-      if(first==-1 || second==-1){
-       fprintf(stderr,"huffman fault; no free branch\n");
-       exit(1);
-      }
 
-      /* join them */
-      least=probability[first]+probability[second];
-      for(j=0;j<v->entries;j++)
-       if(membership[j]==first || membership[j]==second){
-         membership[j]=first;
-         probability[j]=least;
-         c->lengthlist[j]++;
-       }
-    }
-    for(i=0;i<v->entries-1;i++)
-      if(membership[i]!=membership[i+1]){
-       fprintf(stderr,"huffman fault; failed to build single tree\n");
-       exit(1);
-      }
-
-    /* unneccessary metric */
-    memset(probability,0,sizeof(long)*v->entries);
     for(i=0;i<v->points;i++){
       int ret=codebook_entry(b,v->pointlist+i*v->elements);
       probability[ret]++;
+      if(!(i&0xff))spinnit("counting hits... ",v->points-i);
     }
+
+    build_tree_from_lengths(c->entries,probability,c->lengthlist);
     
     free(probability);
-    free(membership);
   }
 
   /* Sort the entries by codeword length, short to long (eases
@@ -608,7 +632,9 @@ void vqsp_book(vqgen *v, codebook *b, long *quantlist){
     /* rearrange storage; ptr0/1 first as it needs a reverse index */
     /* n and c stay unchanged */
     for(i=0;i<c->entries;i++)revindex[index[i]]=i;
-    for(i=0;i<t->aux;i++){
+    for(i=t->ptr0[0];i<t->aux;i++){
+      if(!(i&0x3f))spinnit("sorting... ",t->aux-i);
+
       if(t->ptr0[i]>=0)t->ptr0[i]=revindex[t->ptr0[i]];
       if(t->ptr1[i]>=0)t->ptr1[i]=revindex[t->ptr1[i]];
       t->p[i]=revindex[t->p[i]];
@@ -632,6 +658,6 @@ void vqsp_book(vqgen *v, codebook *b, long *quantlist){
     free(wordlen);
   }
 
-  fprintf(stderr,"Done.\n\n");
+  fprintf(stderr,"Done.                            \n\n");
 }