Edinburgh Speech Tools  2.4-release
 All Classes Functions Variables Typedefs Enumerations Enumerator Friends Pages
viterbi_main.cc
1 /*************************************************************************/
2 /* */
3 /* Centre for Speech Technology Research */
4 /* University of Edinburgh, UK */
5 /* Copyright (c) 1995,1996 */
6 /* All Rights Reserved. */
7 /* */
8 /* Permission is hereby granted, free of charge, to use and distribute */
9 /* this software and its documentation without restriction, including */
10 /* without limitation the rights to use, copy, modify, merge, publish, */
11 /* distribute, sublicense, and/or sell copies of this work, and to */
12 /* permit persons to whom this work is furnished to do so, subject to */
13 /* the following conditions: */
14 /* 1. The code must retain the above copyright notice, this list of */
15 /* conditions and the following disclaimer. */
16 /* 2. Any modifications must be clearly marked as such. */
17 /* 3. Original authors' names are not deleted. */
18 /* 4. The authors' names are not used to endorse or promote products */
19 /* derived from this software without specific prior written */
20 /* permission. */
21 /* */
22 /* THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK */
23 /* DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING */
24 /* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT */
25 /* SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE */
26 /* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES */
27 /* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN */
28 /* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, */
29 /* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF */
30 /* THIS SOFTWARE. */
31 /* */
32 /*************************************************************************/
33 /* Authors: Alan W Black and Simon King */
34 /* Date : January 1997 */
35 /*-----------------------------------------------------------------------*/
36 /* A simple use of the Viterbi decoder */
37 /* */
38 /*=======================================================================*/
39 
40 #include <cstdlib>
41 #include <cstdio>
42 #include <cmath>
43 #include "EST.h"
44 
45 EST_read_status load_TList_of_StrVector(EST_TList<EST_StrVector> &w,
46  const EST_String &filename,
47  const int vec_len);
48 
49 static void print_results(EST_Relation &wstream);
50 static bool do_search(EST_Relation &wstream);
51 static EST_VTPath *vit_npath(EST_VTPath *p,EST_VTCandidate *c,EST_Features &f);
52 static EST_VTCandidate *vit_candlist(EST_Item *s,EST_Features &f);
53 static void top_n_candidates(EST_VTCandidate* &all_c);
54 static void load_vocab(const EST_String &vfile);
55 
56 static void add_word(EST_Relation &w, const EST_String &word, int pos);
57 
58 static void load_wstream(const EST_String &filename,
59  const EST_String &vfile,
60  EST_Relation &w,
61  EST_Track &obs);
62 
63 static void load_given(const EST_String &filename,
64  const int ngram_order);
65 
66 static double find_gram_prob(EST_VTPath *p,int *state);
67 
68 // special stuff for non-sliding window ngrams
69 static double find_extra_gram_prob(EST_VTPath *p,int *state, int time);
70 static void get_history(EST_StrVector &history, EST_VTPath *p);
71 static void fill_window(EST_StrVector &window,EST_StrVector &history,
72  EST_VTPath *p,const int time);
73 static int is_a_special(const EST_String &s, int &val);
74 static int max_history=0;
75 
76 static EST_Ngrammar ngram;
77 static EST_String pstring = SENTENCE_START_MARKER;
78 static EST_String ppstring = SENTENCE_END_MARKER;
79 static float lm_scale = 1.0;
80 static float ob_scale = 1.0;
81 static float ob_scale2 = 1.0;
82 
83 // pruning beam widths
84 static float beam=-1;
85 static float ob_beam=-1;
86 static int n_beam = -1;
87 
88 static bool trace_on = FALSE;
89 
90 // always logs
91 static double ob_log_prob_floor = SAFE_LOG_ZERO;
92 static double ob_log_prob_floor2 = SAFE_LOG_ZERO;
93 static double lm_log_prob_floor = SAFE_LOG_ZERO;
94 
95 int btest_debug = FALSE;
96 static EST_String out_file = "";
97 static EST_StrList vocab;
98 static EST_Track observations;
99 static EST_Track observations2;
100 static EST_TList<EST_StrVector> given; // to do : convert to array for speed
101 int using_given=FALSE;
102 
103 // default is that obs are already logs
104 int take_logs = FALSE;
105 int num_obs = 1;
106 
107 
108 
109 
110 /** @name <command>viterbi</command> <emphasis>Combine n-gram model and likelihoods to estimate posterior probabilities</emphasis>
111  * @id viterbi-manual
112  * @toc
113  */
114 
115 //@{
116 
117 /**@name Synopsis
118  */
119 //@{
120 
121 //@synopsis
122 
123 /**
124 viterbi is a simple time-synchronous Viterbi decoder. It finds the
125 most likely sequence of items drawn from a fixed vocabulary, given
126 frame-by-frame observation probabilities for each item in that
127 vocabulary, and a ngram grammar. Possible uses include:
128 
129 <itemizedlist>
130 <listitem><para>Simple speech recogniser back end</para></listitem>
131 </itemizedlist>
132 
133 viterbi can optionally use two sets of frame-by-frame observation
134 probabilities in a weighted-sum fashion. Also, the ngram language model
135 is not restricted to the conventional sliding window type in which the
136 previous n-1 items are the ngram context. Items in the ngram context
137 at each frame may be given. In this case, the user must provide a file
138 containing the ngram context: one (n-1) tuple per line. To include
139 items from the partial Viterbi path so far (i.e. found at recognition
140 time, not given) the special notation <-N> is used where N indicates
141 the distance back to the item required. For example <-1> would
142 indicate the item on the partial Viterbi path at the last frame. See
143 \Ref{Examples}.
144 
145 <formalpara>
146 <para><title>Pruning</title></para>
147 
148 <para>
149 Three types of pruning are available to reduce the size of the search
150 space and therefore speed up the search:
151 
152 <itemizedlist>
153 <listitem><para>Observation pruning</para></listitem>
154 <listitem><para>Top-N pruning at each frame</para></listitem>
155 <listitem><para>Fixed width beam pruning</para></listitem>
156 </itemizedlist>
157 
158 </para>
159 </formalpara>
160 
161 */
162 
163 //@}
164 
165 /**@name Options
166  */
167 //@{
168 
169 //@options
170 
171 
172 //@}
173 
174 int main(int argc, char **argv)
175 {
176  EST_StrList files;
177  EST_Option al;
178  EST_Relation wstream;
179  double floor; // a temporary
180 
181  parse_command_line(argc, argv,
182  EST_String("[observations file] -o [output file]\n")+
183  "Summary: find the most likely path through a sequence of\n"+
184  " observations, constrained by a language model.\n"+
185  "-ngram <string> Grammar file, required\n"+
186  "-given <string> ngram left contexts, per frame\n"+
187  "-vocab <string> File with names of vocabulary, this\n"+
188  " must be same number as width of observations, required\n"+
189  "-ob_type <string> Observation type : likelihood .... and change doc\"probs\" or \"logs\" (default is \"logs\")\n"+
190  "\nFloor values and scaling (scaling is applied after floor value)\n"+
191  "-lm_floor <float> LM floor probability\n"+
192  "-lm_scale <float> LM scale factor factor (applied to log prob)\n"+
193  "-ob_floor <float> Observations floor probability\n"+
194  "-ob_scale <float> Observation scale factor (applied to prob or log prob, depending on -ob_type)\n\n"+
195  "-prev_tag <string>\n"+
196  " tag before sentence start\n"+
197  "-prev_prev_tag <string>\n"+
198  " all words before 'prev_tag'\n"+
199  "-last_tag <string>\n"+
200  " after sentence end\n"+
201  "-default_tags use default tags of "+SENTENCE_START_MARKER+","
202  SENTENCE_END_MARKER+" and "+SENTENCE_END_MARKER+"\n"+
203  " respectively\n\n"+
204 
205  "-observes2 <string> second observations (overlays first, ob_type must be same)\n"+
206  "-ob_floor2 <float> \n"+
207  "-ob_scale2 <float> \n\n"+
208  "-ob_prune <float> observation pruning beam width (log) probability\n"+
209  "-n_prune <int> top-n pruning of observations\n"+
210  "-prune <float> pruning beam width (log) probability\n"+
211  "-trace show details of search as it proceeds\n",
212  files, al);
213 
214  out_file = al.present("-o") ? al.val("-o") : (EST_String)"-";
215 
216  if (files.length() != 1)
217  {
218  cerr << argv[0];
219  cerr << ": you must give exactly one observations file on the command line";
220  cerr << endl;
221  cerr << "(use -observes2 for optional second observations)" << endl;
222  exit(-1);
223  }
224 
225  if (al.present("-ngram"))
226  {
227  ngram.load(al.val("-ngram"));
228  }
229  else
230  {
231  cerr << argv[0] << ": no ngram specified" << endl;
232  exit(-1);
233  }
234 
235  if(!al.present("-vocab"))
236  {
237  cerr << "You must provide a vocabulary file !" << endl;
238  exit(-1);
239  }
240 
241  load_wstream(files.first(),al.val("-vocab"),wstream,observations);
242  if (al.present("-observes2"))
243  {
244  load_wstream(al.val("-observes2"),al.val("-vocab"),wstream,observations2);
245  num_obs = 2;
246  }
247 
248  if (al.present("-given"))
249  {
250  load_given(al.val("-given"),ngram.order());
251  using_given=TRUE;
252  }
253 
254  if (al.present("-lm_scale"))
255  lm_scale = al.fval("-lm_scale");
256  else
257  lm_scale = 1.0;
258 
259  if (al.present("-ob_scale"))
260  ob_scale = al.fval("-ob_scale");
261  else
262  ob_scale = 1.0;
263 
264  if (al.present("-ob_scale2"))
265  ob_scale2 = al.fval("-ob_scale2");
266  else
267  ob_scale2 = 1.0;
268 
269  if (al.present("-prev_tag"))
270  pstring = al.val("-prev_tag");
271  if (al.present("-prev_prev_tag"))
272  ppstring = al.val("-prev_prev_tag");
273 
274  // pruning
275  if (al.present("-prune"))
276  beam = al.fval("-prune");
277  else
278  beam = -1; // no pruning
279 
280  if (al.present("-ob_prune"))
281  ob_beam = al.fval("-ob_prune");
282  else
283  ob_beam = -1; // no pruning
284 
285  if (al.present("-n_prune"))
286  {
287  n_beam = al.ival("-n_prune");
288  if(n_beam <= 0)
289  {
290  cerr << "WARNING : " << n_beam;
291  cerr << " is not a reasonable value for -n_prune !" << endl;
292  }
293  }
294  else
295  n_beam = -1; // no pruning
296 
297 
298 
299  if (al.present("-trace"))
300  trace_on = TRUE;
301 
302  // language model floor
303  if (al.present("-lm_floor"))
304  {
305  floor = al.fval("-lm_floor");
306  if(floor < 0)
307  {
308  cerr << "Error : LM floor probability is negative !" << endl;
309  exit(-1);
310  }
311  else if(floor > 1)
312  {
313  cerr << "Error : LM floor probability > 1 " << endl;
314  exit(-1);
315  }
316  lm_log_prob_floor = safe_log(floor);
317  }
318 
319  // observations floor
320  if (al.present("-ob_floor"))
321  {
322  floor = al.fval("-ob_floor");
323  if(floor < 0)
324  {
325  cerr << "Error : Observation floor probability is negative !" << endl;
326  exit(-1);
327  }
328  else if(floor > 1)
329  {
330  cerr << "Error : Observation floor probability > 1 " << endl;
331  exit(-1);
332  }
333  ob_log_prob_floor = safe_log(floor);
334  }
335 
336  if (al.present("-ob_floor2"))
337  {
338  floor = al.fval("-ob_floor2");
339  if(floor < 0)
340  {
341  cerr << "Error : Observation2 floor probability is negative !" << endl;
342  exit(-1);
343  }
344  else if(floor > 1)
345  {
346  cerr << "Error : Observation2 floor probability > 1 " << endl;
347  exit(-1);
348  }
349  ob_log_prob_floor2 = safe_log(floor);
350  }
351 
352 
353  if (al.present("-ob_type"))
354  {
355  if(al.val("-ob_type") == "logs")
356  take_logs = false;
357  else if(al.val("-ob_type") == "probs")
358  take_logs = true;
359  else
360  {
361  cerr << "\"" << al.val("-ob_type")
362  << "\" is not a valid ob_type : try \"logs\" or \"probs\"" << endl;
363  exit(-1);
364  }
365  }
366 
367  if(do_search(wstream))
368  print_results(wstream);
369  else
370  cerr << "No path could be found." << endl;
371 
372  return 0;
373 }
374 
375 static void print_results(EST_Relation &wstream)
376 {
377  EST_Item *s;
378  float pscore;
379  EST_String predict;
380  FILE *fd;
381 
382  if (out_file == "-")
383  fd = stdout;
384  else if ((fd = fopen(out_file,"wb")) == NULL)
385  {
386  cerr << "can't open \"" << out_file << "\" for output" << endl;
387  exit(-1);
388  }
389 
390  for (s=wstream.head(); s != 0 ; s=s->next())
391  {
392  predict = s->f("best").string();
393  pscore = s->f("best_score");
394  fprintf(fd,"%s %f\n",(const char *)predict,pscore);
395  }
396 
397  if (out_file != "")
398  fclose(fd);
399 
400 }
401 
402 static bool do_search(EST_Relation &wstream)
403 {
404  // Apply Ngram to matrix of probs
405  int states;
406 
407  states = ngram.num_states();
408  EST_Viterbi_Decoder vc(vit_candlist,vit_npath,states);
409 
410  vc.initialise(&wstream);
411 
412  if((beam > 0) || (ob_beam > 0))
413  vc.set_pruning_parameters(beam,ob_beam);
414 
415  if(trace_on)
416  {
417  vc.turn_on_trace();
418  cerr << "Starting Viterbi search..." << endl;
419  }
420 
421  vc.search();
422 
423  return vc.result("best"); // adds fields to w with best values
424 
425 }
426 
427 static void load_wstream(const EST_String &filename,
428  const EST_String &vfile,
429  EST_Relation &w,
430  EST_Track &obs)
431 {
432  // Load in vocab and probs into Stream (this isn't general)
433  EST_String word, pos;
434  int i=-1;
435 
436  if(vocab.empty())
437  load_vocab(vfile);
438 
439  if (obs.load(filename,0.10) != 0)
440  {
441  cerr << "can't find observations file \"" << filename << "\"" << endl;
442  exit(-1);
443  }
444 
445  if (vocab.length() != obs.num_channels())
446  {
447  cerr << "Number in vocab (" << vocab.length() <<
448  ") not equal to observation's width (" <<
449  obs.num_channels() << ")" << endl;
450  exit(-1);
451  }
452 
453  if(w.empty())
454  for (i=0; i < obs.num_frames(); i++)
455  add_word(w,itoString(i),i);
456 }
457 
458 
459 static void load_given(const EST_String &filename,
460  const int ngram_order)
461 {
462 
463  EST_String word, pos;
464  EST_Litem *p;
465  int i,j;
466 
467  if (load_TList_of_StrVector(given,filename,ngram_order-1) != 0)
468  {
469  cerr << "can't load given file \"" << filename << "\"" << endl;
470  exit(-1);
471  }
472 
473  // set max history
474  for (p = given.head(); p; p = p->next())
475  {
476  for(i=0;i<given(p).length();i++)
477  if( is_a_special( given(p)(i), j) && (-j > max_history))
478  max_history = -j;
479 
480  }
481 
482 }
483 
484 static void load_vocab(const EST_String &vfile)
485 {
486  // Load vocabulary (strings)
487  EST_TokenStream ts;
488 
489  if (ts.open(vfile) == -1)
490  {
491  cerr << "can't find vocab file \"" << vfile << "\"" << endl;
492  exit(-1);
493  }
494 
495  while (!ts.eof())
496  if (ts.peek() != "")
497  vocab.append(ts.get().string());
498 
499  ts.close();
500 }
501 
502 static void add_word(EST_Relation &w, const EST_String &word, int pos)
503 {
504  EST_Item *item = w.append();
505 
506  item->set_name(word);
507  item->set("pos",pos);
508 }
509 
510 static EST_VTCandidate *vit_candlist(EST_Item *s,EST_Features &f)
511 {
512  // Return a list of new candidates from this point
513  double prob=1.0,prob2=1.0;
514  int i;
515  EST_Litem *p;
516  int observe;
517  EST_VTCandidate *all_c = 0;
518  EST_VTCandidate *c;
519  (void)f;
520 
521  observe = s->f("pos"); // index for observations TRACK
522  for (i=0,p=vocab.head(); i < observations.num_channels(); i++,p=p->next())
523  {
524  c = new EST_VTCandidate;
525  c->name = vocab(p); // to be more efficient this could be the index
526  prob = observations.a(observe,i);
527  if(num_obs == 2)
528  prob2 = observations2.a(observe,i);
529 
530  if(take_logs)
531  {
532  prob = safe_log10(prob);
533  if (prob < ob_log_prob_floor)
534  prob = ob_log_prob_floor;
535 
536  if(num_obs == 2)
537  {
538  prob2 = safe_log10(prob2);
539  if (prob2 < ob_log_prob_floor2)
540  prob2 = ob_log_prob_floor2;
541  }
542  }
543  else // already in logs
544  {
545  if (prob < ob_log_prob_floor)
546  prob = ob_log_prob_floor;
547  if ((num_obs == 2) && (prob2 < ob_log_prob_floor2))
548  prob2 = ob_log_prob_floor2;
549  }
550 
551  prob *= ob_scale;
552  prob2 *= ob_scale2;
553 
554  if(num_obs == 2)
555  c->score = prob + prob2;
556  else
557  c->score = prob;
558 
559  c->next = all_c;
560  c->s = s;
561  all_c = c;
562  }
563 
564  if(n_beam > 0)
565  {
566  // N.B. this might be very time-consuming
567  top_n_candidates(all_c);
568  }
569 
570  return all_c;
571 }
572 
573 static EST_VTPath *vit_npath(EST_VTPath *p,EST_VTCandidate *c,
574  EST_Features &f)
575 {
576  // Build a (potential) new path link from this previous path and
577  // This candidate
578  EST_VTPath *np = new EST_VTPath;
579  double lprob,prob;
580  EST_String prev,ttt;
581  (void)f;
582 
583  np->c = c;
584  np->from = p;
585 
586  // are we using extra info ?
587  if(using_given)
588  // time of candidate is
589  // c->s->f("pos");
590  prob = find_extra_gram_prob(np,&np->state,c->s->f("pos"));
591  else
592  prob = find_gram_prob(np,&np->state);
593 
594  lprob = safe_log10(prob);
595  if (lprob < lm_log_prob_floor)
596  lprob = lm_log_prob_floor;
597 
598  lprob *= lm_scale;
599 
600  np->f.set("lscore",(c->score+lprob)); // simonk : changed prob to lprob
601  if (p==0)
602  np->score = (c->score+lprob);
603  else
604  np->score = (c->score+lprob) + p->score;
605 
606  return np;
607 }
608 
609 static double find_gram_prob(EST_VTPath *p,int *state)
610 {
611  // Look up transition probability from *state for name.
612  // Return probability and update state
613  double prob=0.0,nprob;
614  int i,f=FALSE;
615  EST_VTPath *pp;
616 
617  EST_StrVector window(ngram.order());
618  for (pp=p->from,i=ngram.order()-2; i >= 0; i--)
619  {
620  if (pp != 0)
621  {
622  window[i] = pp->c->name.string();
623  pp = pp->from;
624  }
625  else if (f)
626  window[i] = ppstring;
627  else
628  {
629  window[i] = pstring;
630  f = TRUE;
631  }
632  }
633  window[ngram.order()-1] = p->c->name.string();
634  const EST_DiscreteProbDistribution &pd = ngram.prob_dist(window);
635  if (pd.samples() == 0)
636  prob = 0;
637  else
638  prob = (double)pd.probability(p->c->name.string());
639 
640  for (i=0; i < ngram.order()-1; i++)
641  window[i] = window(i+1);
642  ngram.predict(window,&nprob,state);
643 
644  return prob;
645 }
646 
647 
648 static double find_extra_gram_prob(EST_VTPath *p,int *state,int time)
649 {
650 
651  int i;
652  double prob=0.0,nprob;
653  EST_StrVector window(ngram.order());
654  EST_StrVector history(max_history);
655 
656  get_history(history,p);
657 
658  fill_window(window,history,p,time);
659 
660  /*
661  cerr << "Looking up ngram ";
662  for(i=0;i<window.num_points();i++)
663  cerr << window(i) << " ";
664  cerr << endl;
665  */
666 
667  const EST_DiscreteProbDistribution &pd = ngram.prob_dist(window);
668  if (pd.samples() == 0)
669  prob = 0;
670  else
671  prob = (double)pd.probability(p->c->name.string());
672 
673  // shift history, adding latest item at 'end' (0)
674  if(max_history>0)
675  {
676  for(i=history.length()-1;i>0;i--)
677  history[i] = history(i-1);
678  history[0] = p->c->name.string();
679  }
680 
681  fill_window(window,history,p,time+1);
682  ngram.predict(window,&nprob,state);
683 
684  //cerr << endl << endl;
685 
686  return prob;
687 
688 }
689 
690 static void get_history(EST_StrVector &history, EST_VTPath *p)
691 {
692 
693  EST_VTPath *pp;
694  int i,f=FALSE;
695  for (pp=p->from,i=0; i < history.length(); i++)
696  {
697 
698  if (pp != 0)
699  {
700  history[i] = pp->c->name.string();
701  pp = pp->from;
702  }
703  else if (f)
704  history[i] = ppstring;
705  else
706  {
707  history[i] = pstring;
708  f = TRUE;
709  }
710  }
711 
712 }
713 
714 static void fill_window(EST_StrVector &window,EST_StrVector &history,
715  EST_VTPath *p,const int time)
716 {
717  // Look up transition probability from *state for name.
718  // Return probability and update state
719  int i,j;
720  EST_String s;
721 
722  // can we even do it?
723  if( time >= given.length() )
724  return;
725 
726  // format should be run-time defined, but try this for now
727  // first n-1 things in window come from 'given'
728  // last one is predictee
729 
730  // also want vocab and grammar mismatch allowed !!!!!!
731 
732  // predictee
733  window[ngram.order()-1] = p->c->name.string();
734 
735  // given info for this time
736  EST_StrVector *this_g = &(given.nth(time)); // inefficient to count down a list
737 
738 
739  for(i=0;i<ngram.order()-1;i++)
740  {
741 
742  if( is_a_special( (*this_g)(i), j))
743  window[i] = history(-1-j); // j=-1 -> (0) j=-2 -> (1) etc.
744  else
745  window[i] = (*this_g)(i);
746  }
747 }
748 
749 
750 
751 static int is_a_special(const EST_String &s, int &val)
752 {
753 
754  // special is "<int>"
755 
756  EST_String tmp;
757  if(s.contains("<") && s.contains(">"))
758  {
759  tmp = s.after("<");
760  tmp = tmp.before(">");
761  val = atoi(tmp);
762  //cerr << "special " << tmp << "=" << val << endl;
763  return TRUE;
764  }
765  return FALSE;
766 }
767 
768 static void top_n_candidates(EST_VTCandidate* &all_c)
769 {
770  // keep the n most likely candidates
771  // avoiding a full sort of the (potentially long) list
772 
773  EST_VTCandidate *top_c=NULL,*p,*q,*this_best, *prev_to_best;
774  int i;
775  if(n_beam < 1)
776  return; // do nothing
777 
778  // here we assume big is always good
779  //if(!big_is_good)
780  //score_multiplier = -1;
781 
782  for(i=0;i<n_beam;i++)
783  {
784 
785  // head of the list is best guess
786  this_best=all_c;
787  prev_to_best=NULL;
788 
789  // find best candidate in all_c
790  q=NULL;;
791  for(p=all_c;p!= NULL;q=p,p=p->next)
792  {
793  //cerr << "item : " << p->score << endl;
794  if(p->score > this_best->score)
795  {
796  this_best = p;
797  prev_to_best=q;
798  }
799  }
800 
801  if(this_best == NULL)
802  break; // give up now - must have run out of candidates
803 
804  // move best candidate over to new list
805  if(prev_to_best == NULL)
806  // best was head of list
807  all_c = this_best->next;
808  else
809  // best was not head of all_c
810  prev_to_best->next = this_best->next;
811 
812  this_best->next = top_c;
813  top_c = this_best;
814  }
815 
816  delete all_c;
817  all_c = top_c;
818 
819 /*
820  cerr << "Here are the top " << n_beam << " candidates" << endl;
821  for(p=all_c;p != NULL;p=p->next)
822  cerr << p->score << endl;
823 */
824 }
825 
826 
827 /**@name Examples
828 
829 Example 'given' file (items f and g are in the vocabulary), the ngram
830 is a 4-gram.
831 
832 <para>
833 <screen>
834 <-2> g g
835 <-1> g f
836 <-1> f g
837 <-2> g g
838 <-3> g g
839 <-1> g f
840 </screen>
841 </para>
842 
843 */
844 //@{
845 //@}
846 
847 
848 
849 //@}