1 /* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* ====================================================================
3 * Copyright (c) 1999-2004 Carnegie Mellon University. All rights
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
19 * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
20 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
23 * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 * ====================================================================
36 * fsg_search.c -- Search structures for FSM decoding.
38 * **********************************************
39 * CMU ARPA Speech Project
41 * Copyright (c) 2004 Carnegie Mellon University.
42 * ALL RIGHTS RESERVED.
43 * **********************************************
47 * 18-Feb-2004 M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon
56 /* SphinxBase headers. */
57 #include <sphinxbase/err.h>
58 #include <sphinxbase/ckd_alloc.h>
59 #include <sphinxbase/strfuncs.h>
60 #include <sphinxbase/cmd_ln.h>
63 #include "pocketsphinx_internal.h"
64 #include "ps_lattice_internal.h"
65 #include "fsg_search_internal.h"
66 #include "fsg_history.h"
67 #include "fsg_lextree.h"
69 /* Turn this on for detailed debugging dump */
71 #define __FSG_DBG_CHAN__ 0
73 static ps_seg_t *fsg_search_seg_iter(ps_search_t *search, int32 *out_score);
74 static ps_lattice_t *fsg_search_lattice(ps_search_t *search);
75 static int fsg_search_prob(ps_search_t *search);
77 static ps_searchfuncs_t fsg_funcs = {
79 /* start: */ fsg_search_start,
80 /* step: */ fsg_search_step,
81 /* finish: */ fsg_search_finish,
82 /* reinit: */ fsg_search_reinit,
83 /* free: */ fsg_search_free,
84 /* lattice: */ fsg_search_lattice,
85 /* hyp: */ fsg_search_hyp,
86 /* prob: */ fsg_search_prob,
87 /* seg_iter: */ fsg_search_seg_iter,
91 fsg_search_init(cmd_ln_t *config,
99 fsgs = ckd_calloc(1, sizeof(*fsgs));
100 ps_search_init(ps_search_base(fsgs), &fsg_funcs, config, acmod, dict, d2p);
102 /* Initialize HMM context. */
103 fsgs->hmmctx = hmm_context_init(bin_mdef_n_emit_state(acmod->mdef),
104 acmod->tmat->tp, NULL, acmod->mdef->sseq);
105 if (fsgs->hmmctx == NULL) {
106 ps_search_free(ps_search_base(fsgs));
110 /* Intialize the search history object */
111 fsgs->history = fsg_history_init(NULL, dict);
114 /* Initialize FSG table. */
115 fsgs->fsgs = hash_table_new(5, HASH_CASE_YES);
117 /* Get search pruning parameters */
118 fsgs->beam_factor = 1.0f;
119 fsgs->beam = fsgs->beam_orig
120 = (int32) logmath_log(acmod->lmath, cmd_ln_float64_r(config, "-beam"))
122 fsgs->pbeam = fsgs->pbeam_orig
123 = (int32) logmath_log(acmod->lmath, cmd_ln_float64_r(config, "-pbeam"))
125 fsgs->wbeam = fsgs->wbeam_orig
126 = (int32) logmath_log(acmod->lmath, cmd_ln_float64_r(config, "-wbeam"))
129 /* LM related weights/penalties */
130 fsgs->lw = cmd_ln_float32_r(config, "-lw");
131 fsgs->pip = (int32) (logmath_log(acmod->lmath, cmd_ln_float32_r(config, "-pip"))
134 fsgs->wip = (int32) (logmath_log(acmod->lmath, cmd_ln_float32_r(config, "-wip"))
138 /* Best path search (and confidence annotation)? */
139 if (cmd_ln_boolean_r(config, "-bestpath"))
140 fsgs->bestpath = TRUE;
142 /* Acoustic score scale for posterior probabilities. */
143 fsgs->ascale = 1.0 / cmd_ln_float32_r(config, "-ascale");
145 E_INFO("FSG(beam: %d, pbeam: %d, wbeam: %d; wip: %d, pip: %d)\n",
146 fsgs->beam_orig, fsgs->pbeam_orig, fsgs->wbeam_orig,
147 fsgs->wip, fsgs->pip);
149 /* Load an FSG if one was specified in config */
150 if ((path = cmd_ln_str_r(config, "-fsg"))) {
153 if ((fsg = fsg_model_readfile(path, acmod->lmath, fsgs->lw)) == NULL)
155 if (fsg_set_add(fsgs, fsg_model_name(fsg), fsg) != fsg) {
159 if (fsg_set_select(fsgs, fsg_model_name(fsg)) == NULL)
161 if (fsg_search_reinit(ps_search_base(fsgs),
162 ps_search_dict(fsgs),
163 ps_search_dict2pid(fsgs)) < 0)
166 /* Or load a JSGF grammar */
167 else if ((path = cmd_ln_str_r(config, "-jsgf"))) {
172 if ((fsgs->jsgf = jsgf_parse_file(path, NULL)) == NULL)
176 /* Take the -toprule if specified. */
177 if ((toprule = cmd_ln_str_r(config, "-toprule"))) {
179 anglerule = string_join("<", toprule, ">", NULL);
180 rule = jsgf_get_rule(fsgs->jsgf, anglerule);
183 E_ERROR("Start rule %s not found\n", toprule);
187 /* Otherwise, take the first public rule. */
189 jsgf_rule_iter_t *itor;
191 for (itor = jsgf_rule_iter(fsgs->jsgf); itor;
192 itor = jsgf_rule_iter_next(itor)) {
193 rule = jsgf_rule_iter_rule(itor);
194 if (jsgf_rule_public(rule))
198 E_ERROR("No public rules found in %s\n", path);
202 fsg = jsgf_build_fsg(fsgs->jsgf, rule, acmod->lmath, fsgs->lw);
203 if (fsg_set_add(fsgs, fsg_model_name(fsg), fsg) != fsg) {
207 if (fsg_set_select(fsgs, fsg_model_name(fsg)) == NULL)
209 if (fsg_search_reinit(ps_search_base(fsgs),
210 ps_search_dict(fsgs),
211 ps_search_dict2pid(fsgs)) < 0)
215 return ps_search_base(fsgs);
218 fsg_search_free(ps_search_base(fsgs));
223 fsg_search_free(ps_search_t *search)
225 fsg_search_t *fsgs = (fsg_search_t *)search;
228 ps_search_deinit(search);
230 jsgf_grammar_free(fsgs->jsgf);
231 fsg_lextree_free(fsgs->lextree);
233 fsg_history_reset(fsgs->history);
234 fsg_history_set_fsg(fsgs->history, NULL, NULL);
235 fsg_history_free(fsgs->history);
238 for (itor = hash_table_iter(fsgs->fsgs);
239 itor; itor = hash_table_iter_next(itor)) {
240 fsg_model_t *fsg = (fsg_model_t *) hash_entry_val(itor->ent);
243 hash_table_free(fsgs->fsgs);
245 hmm_context_free(fsgs->hmmctx);
250 fsg_search_reinit(ps_search_t *search, dict_t *dict, dict2pid_t *d2p)
252 fsg_search_t *fsgs = (fsg_search_t *)search;
254 /* Free the old lextree */
256 fsg_lextree_free(fsgs->lextree);
258 /* Free old dict2pid, dict */
259 ps_search_base_reinit(search, dict, d2p);
261 /* Nothing to update */
262 if (fsgs->fsg == NULL)
265 /* Update the number of words (not used by this module though). */
266 search->n_words = dict_size(dict);
268 /* Allocate new lextree for the given FSG */
269 fsgs->lextree = fsg_lextree_init(fsgs->fsg, dict, d2p,
270 ps_search_acmod(fsgs)->mdef,
271 fsgs->hmmctx, fsgs->wip, fsgs->pip);
273 /* Inform the history module of the new fsg */
274 fsg_history_set_fsg(fsgs->history, fsgs->fsg, dict);
281 fsg_search_add_silences(fsg_search_t *fsgs, fsg_model_t *fsg)
287 dict = ps_search_dict(fsgs);
289 * NOTE: Unlike N-Gram search, we do not use explicit start and
290 * end symbols. This is because the start and end nodes are
291 * defined in the grammar. We do add silence/filler self-loops to
292 * all states in order to allow for silence between words and at
293 * the beginning and end of utterances.
295 * This has some implications for word graph generation, namely,
296 * that there can (and usually will) be multiple start and end
297 * states in the word graph. We therefore do add explicit start
298 * and end nodes to the graph.
300 /* Add silence self-loops to all states. */
301 fsg_model_add_silence(fsg, "<sil>", -1,
302 cmd_ln_float32_r(ps_search_config(fsgs), "-silprob"));
304 /* Add self-loops for all other fillers. */
305 for (wid = dict_filler_start(dict); wid < dict_filler_end(dict); ++wid) {
306 char const *word = dict_wordstr(dict, wid);
307 if (wid == dict_startwid(dict) || wid == dict_finishwid(dict))
309 fsg_model_add_silence(fsg, word, -1,
310 cmd_ln_float32_r(ps_search_config(fsgs), "-fillprob"));
317 /* Scans the dictionary and check if all words are present. */
319 fsg_search_check_dict(fsg_search_t *fsgs, fsg_model_t *fsg)
324 dict = ps_search_dict(fsgs);
325 for (i = 0; i < fsg_model_n_word(fsg); ++i) {
329 word = fsg_model_word_str(fsg, i);
330 wid = dict_wordid(dict, word);
331 if (wid == BAD_S3WID) {
332 E_ERROR("The word '%s' is missing in the dictionary\n", word);
341 fsg_search_add_altpron(fsg_search_t *fsgs, fsg_model_t *fsg)
347 dict = ps_search_dict(fsgs);
348 /* Scan FSG's vocabulary for words that have alternate pronunciations. */
350 n_word = fsg_model_n_word(fsg);
351 for (i = 0; i < n_word; ++i) {
355 word = fsg_model_word_str(fsg, i);
356 wid = dict_wordid(dict, word);
357 if (wid != BAD_S3WID) {
358 while ((wid = dict_nextalt(dict, wid)) != BAD_S3WID) {
359 n_alt += fsg_model_add_alt(fsg, word, dict_wordstr(dict, wid));
364 E_INFO("Added %d alternate word transitions\n", n_alt);
369 fsg_set_get_fsg(fsg_search_t *fsgs, const char *name)
373 if (hash_table_lookup(fsgs->fsgs, name, &val) < 0)
375 return (fsg_model_t *)val;
379 fsg_set_add(fsg_search_t *fsgs, char const *name, fsg_model_t *fsg)
382 name = fsg_model_name(fsg);
384 if (!fsg_search_check_dict(fsgs, fsg))
387 /* Add silence transitions and alternate words. */
388 if (cmd_ln_boolean_r(ps_search_config(fsgs), "-fsgusefiller")
389 && !fsg_model_has_sil(fsg))
390 fsg_search_add_silences(fsgs, fsg);
391 if (cmd_ln_boolean_r(ps_search_config(fsgs), "-fsgusealtpron")
392 && !fsg_model_has_alt(fsg))
393 fsg_search_add_altpron(fsgs, fsg);
395 return (fsg_model_t *)hash_table_enter(fsgs->fsgs, name, fsg);
400 fsg_set_remove_byname(fsg_search_t *fsgs, char const *key)
405 /* Look for the matching FSG. */
406 if (hash_table_lookup(fsgs->fsgs, key, &val) < 0) {
407 E_ERROR("FSG `%s' to be deleted not found\n", key);
412 /* Remove it from the FSG table. */
413 hash_table_delete(fsgs->fsgs, key);
414 /* If this was the currently active FSG, also delete other stuff */
415 if (fsgs->fsg == oldfsg) {
416 fsg_lextree_free(fsgs->lextree);
417 fsgs->lextree = NULL;
418 fsg_history_set_fsg(fsgs->history, NULL, NULL);
426 fsg_set_remove(fsg_search_t *fsgs, fsg_model_t *fsg)
432 for (itor = hash_table_iter(fsgs->fsgs);
433 itor; itor = hash_table_iter_next(itor)) {
436 oldfsg = (fsg_model_t *) hash_entry_val(itor->ent);
438 key = hash_entry_key(itor->ent);
439 hash_table_iter_free(itor);
444 E_WARN("FSG '%s' to be deleted not found\n", fsg_model_name(fsg));
448 return fsg_set_remove_byname(fsgs, key);
453 fsg_set_select(fsg_search_t *fsgs, const char *name)
457 fsg = fsg_set_get_fsg(fsgs, name);
459 E_ERROR("FSG '%s' not known; cannot make it current\n", name);
467 fsg_set_iter(fsg_set_t *fsgs)
469 return hash_table_iter(fsgs->fsgs);
473 fsg_set_iter_next(fsg_set_iter_t *itor)
475 return hash_table_iter_next(itor);
479 fsg_set_iter_fsg(fsg_set_iter_t *itor)
481 return ((fsg_model_t *)itor->ent->val);
485 fsg_set_iter_free(fsg_set_iter_t *itor)
487 hash_table_iter_free(itor);
491 fsg_search_sen_active(fsg_search_t *fsgs)
497 acmod_clear_active(ps_search_acmod(fsgs));
499 for (gn = fsgs->pnode_active; gn; gn = gnode_next(gn)) {
500 pnode = (fsg_pnode_t *) gnode_ptr(gn);
501 hmm = fsg_pnode_hmmptr(pnode);
502 assert(hmm_frame(hmm) == fsgs->frame);
503 acmod_activate_hmm(ps_search_acmod(fsgs), hmm);
509 * Evaluate all the active HMMs.
510 * (Executed once per frame.)
513 fsg_search_hmm_eval(fsg_search_t *fsgs)
521 bestscore = WORST_SCORE;
523 if (!fsgs->pnode_active) {
524 E_ERROR("Frame %d: No active HMM!!\n", fsgs->frame);
528 for (n = 0, gn = fsgs->pnode_active; gn; gn = gnode_next(gn), n++) {
531 pnode = (fsg_pnode_t *) gnode_ptr(gn);
532 hmm = fsg_pnode_hmmptr(pnode);
533 assert(hmm_frame(hmm) == fsgs->frame);
536 E_INFO("pnode(%08x) active @frm %5d\n", (int32) pnode,
538 hmm_dump(hmm, stdout);
540 score = hmm_vit_eval(hmm);
542 E_INFO("pnode(%08x) after eval @frm %5d\n",
543 (int32) pnode, fsgs->frame);
544 hmm_dump(hmm, stdout);
547 if (score BETTER_THAN bestscore)
552 E_INFO("[%5d] %6d HMM; bestscr: %11d\n", fsgs->frame, n, bestscore);
554 fsgs->n_hmm_eval += n;
556 /* Adjust beams if #active HMMs larger than absolute threshold */
557 maxhmmpf = cmd_ln_int32_r(ps_search_config(fsgs), "-maxhmmpf");
558 if (maxhmmpf != -1 && n > maxhmmpf) {
560 * Too many HMMs active; reduce the beam factor applied to the default
561 * beams, but not if the factor is already at a floor (0.1).
563 if (fsgs->beam_factor > 0.1) { /* Hack!! Hardwired constant 0.1 */
564 fsgs->beam_factor *= 0.9f; /* Hack!! Hardwired constant 0.9 */
566 (int32) (fsgs->beam_orig * fsgs->beam_factor);
568 (int32) (fsgs->pbeam_orig * fsgs->beam_factor);
570 (int32) (fsgs->wbeam_orig * fsgs->beam_factor);
574 fsgs->beam_factor = 1.0f;
575 fsgs->beam = fsgs->beam_orig;
576 fsgs->pbeam = fsgs->pbeam_orig;
577 fsgs->wbeam = fsgs->wbeam_orig;
580 if (n > fsg_lextree_n_pnode(fsgs->lextree))
581 E_FATAL("PANIC! Frame %d: #HMM evaluated(%d) > #PNodes(%d)\n",
582 fsgs->frame, n, fsg_lextree_n_pnode(fsgs->lextree));
584 fsgs->bestscore = bestscore;
589 fsg_search_pnode_trans(fsg_search_t *fsgs, fsg_pnode_t * pnode)
593 int32 newscore, thresh, nf;
596 assert(!fsg_pnode_leaf(pnode));
598 nf = fsgs->frame + 1;
599 thresh = fsgs->bestscore + fsgs->beam;
601 hmm = fsg_pnode_hmmptr(pnode);
603 for (child = fsg_pnode_succ(pnode);
604 child; child = fsg_pnode_sibling(child)) {
605 newscore = hmm_out_score(hmm) + child->logs2prob;
607 if ((newscore BETTER_THAN thresh)
608 && (newscore BETTER_THAN hmm_in_score(&child->hmm))) {
609 /* Incoming score > pruning threshold and > target's existing score */
610 if (hmm_frame(&child->hmm) < nf) {
611 /* Child node not yet activated; do so */
612 fsgs->pnode_active_next =
613 glist_add_ptr(fsgs->pnode_active_next,
617 hmm_enter(&child->hmm, newscore, hmm_out_history(hmm), nf);
624 fsg_search_pnode_exit(fsg_search_t *fsgs, fsg_pnode_t * pnode)
629 fsg_pnode_ctxt_t ctxt;
632 assert(fsg_pnode_leaf(pnode));
634 hmm = fsg_pnode_hmmptr(pnode);
635 fl = fsg_pnode_fsglink(pnode);
638 wid = fsg_link_wid(fl);
642 E_INFO("[%5d] Exit(%08x) %10d(score) %5d(pred)\n",
643 fsgs->frame, (int32) pnode,
644 hmm_out_score(hmm), hmm_out_history(hmm));
648 * Check if this is filler or single phone word; these do not model right
649 * context (i.e., the exit score applies to all right contexts).
651 if (fsg_model_is_filler(fsgs->fsg, wid)
652 /* FIXME: This might be slow due to repeated calls to dict_to_id(). */
653 || (dict_is_single_phone(ps_search_dict(fsgs),
654 dict_wordid(ps_search_dict(fsgs),
655 fsg_model_word_str(fsgs->fsg, wid))))) {
656 /* Create a dummy context structure that applies to all right contexts */
657 fsg_pnode_add_all_ctxt(&ctxt);
659 /* Create history table entry for this word exit */
660 fsg_history_entry_add(fsgs->history,
664 hmm_out_history(hmm),
665 pnode->ci_ext, ctxt);
669 /* Create history table entry for this word exit */
670 fsg_history_entry_add(fsgs->history,
674 hmm_out_history(hmm),
675 pnode->ci_ext, pnode->ctxt);
681 * (Beam) prune the just evaluated HMMs, determine which ones remain
682 * active, which ones transition to successors, which ones exit and
683 * terminate in their respective destination FSM states.
684 * (Executed once per frame.)
687 fsg_search_hmm_prune_prop(fsg_search_t *fsgs)
692 int32 thresh, word_thresh, phone_thresh;
694 assert(fsgs->pnode_active_next == NULL);
696 thresh = fsgs->bestscore + fsgs->beam;
697 phone_thresh = fsgs->bestscore + fsgs->pbeam;
698 word_thresh = fsgs->bestscore + fsgs->wbeam;
700 for (gn = fsgs->pnode_active; gn; gn = gnode_next(gn)) {
701 pnode = (fsg_pnode_t *) gnode_ptr(gn);
702 hmm = fsg_pnode_hmmptr(pnode);
704 if (hmm_bestscore(hmm) >= thresh) {
705 /* Keep this HMM active in the next frame */
706 if (hmm_frame(hmm) == fsgs->frame) {
707 hmm_frame(hmm) = fsgs->frame + 1;
708 fsgs->pnode_active_next =
709 glist_add_ptr(fsgs->pnode_active_next,
713 assert(hmm_frame(hmm) == fsgs->frame + 1);
716 if (!fsg_pnode_leaf(pnode)) {
717 if (hmm_out_score(hmm) >= phone_thresh) {
718 /* Transition out of this phone into its children */
719 fsg_search_pnode_trans(fsgs, pnode);
723 if (hmm_out_score(hmm) >= word_thresh) {
724 /* Transition out of leaf node into destination FSG state */
725 fsg_search_pnode_exit(fsgs, pnode);
734 * Propagate newly created history entries through null transitions.
737 fsg_search_null_prop(fsg_search_t *fsgs)
739 int32 bpidx, n_entries, thresh, newscore;
740 fsg_hist_entry_t *hist_entry;
746 thresh = fsgs->bestscore + fsgs->wbeam; /* Which beam really?? */
748 n_entries = fsg_history_n_entries(fsgs->history);
750 for (bpidx = fsgs->bpidx_start; bpidx < n_entries; bpidx++) {
752 hist_entry = fsg_history_entry_get(fsgs->history, bpidx);
754 l = fsg_hist_entry_fsglink(hist_entry);
756 /* Destination FSG state for history entry */
757 s = l ? fsg_link_to_state(l) : fsg_model_start_state(fsg);
760 * Check null transitions from d to all other states. (Only need to
761 * propagate one step, since FSG contains transitive closure of null
764 /* Add all links from from_state to dst */
765 for (itor = fsg_model_arcs(fsg, s); itor;
766 itor = fsg_arciter_next(itor)) {
767 fsg_link_t *l = fsg_arciter_get(itor);
769 /* FIXME: Need to deal with tag transitions somehow. */
770 if (fsg_link_wid(l) != -1)
773 fsg_hist_entry_score(hist_entry) +
774 (fsg_link_logs2prob(l) >> SENSCR_SHIFT);
776 if (newscore >= thresh) {
777 fsg_history_entry_add(fsgs->history, l,
778 fsg_hist_entry_frame(hist_entry),
781 fsg_hist_entry_lc(hist_entry),
782 fsg_hist_entry_rc(hist_entry));
790 * Perform cross-word transitions; propagate each history entry created in this
791 * frame to lextree roots attached to the target FSG state for that entry.
794 fsg_search_word_trans(fsg_search_t *fsgs)
796 int32 bpidx, n_entries;
797 fsg_hist_entry_t *hist_entry;
799 int32 score, newscore, thresh, nf, d;
803 n_entries = fsg_history_n_entries(fsgs->history);
805 thresh = fsgs->bestscore + fsgs->beam;
806 nf = fsgs->frame + 1;
808 for (bpidx = fsgs->bpidx_start; bpidx < n_entries; bpidx++) {
809 hist_entry = fsg_history_entry_get(fsgs->history, bpidx);
811 score = fsg_hist_entry_score(hist_entry);
812 assert(fsgs->frame == fsg_hist_entry_frame(hist_entry));
814 l = fsg_hist_entry_fsglink(hist_entry);
816 /* Destination state for hist_entry */
817 d = l ? fsg_link_to_state(l) : fsg_model_start_state(fsgs->
820 lc = fsg_hist_entry_lc(hist_entry);
822 /* Transition to all root nodes attached to state d */
823 for (root = fsg_lextree_root(fsgs->lextree, d);
824 root; root = root->sibling) {
827 if ((root->ctxt.bv[lc >> 5] & (1 << (lc & 0x001f))) &&
828 (hist_entry->rc.bv[rc >> 5] & (1 << (rc & 0x001f)))) {
830 * Last CIphone of history entry is in left-context list supported by
831 * target root node, and
832 * first CIphone of target root node is in right context list supported
834 * So the transition can go ahead (if new score is good enough).
836 newscore = score + root->logs2prob;
838 if ((newscore BETTER_THAN thresh)
839 && (newscore BETTER_THAN hmm_in_score(&root->hmm))) {
840 if (hmm_frame(&root->hmm) < nf) {
841 /* Newly activated node; add to active list */
842 fsgs->pnode_active_next =
843 glist_add_ptr(fsgs->pnode_active_next,
847 ("[%5d] WordTrans bpidx[%d] -> pnode[%08x] (activated)\n",
848 fsgs->frame, bpidx, (int32) root);
854 ("[%5d] WordTrans bpidx[%d] -> pnode[%08x]\n",
855 fsgs->frame, bpidx, (int32) root);
859 hmm_enter(&root->hmm, newscore, bpidx, nf);
868 fsg_search_step(ps_search_t *search, int frame_idx)
870 fsg_search_t *fsgs = (fsg_search_t *)search;
872 acmod_t *acmod = search->acmod;
877 /* Activate our HMMs for the current frame if need be. */
878 if (!acmod->compallsen)
879 fsg_search_sen_active(fsgs);
880 /* Compute GMM scores for the current frame. */
881 senscr = acmod_score(acmod, &frame_idx);
882 fsgs->n_sen_eval += acmod->n_senone_active;
883 hmm_context_set_senscore(fsgs->hmmctx, senscr);
885 /* Mark backpointer table for current frame. */
886 fsgs->bpidx_start = fsg_history_n_entries(fsgs->history);
888 /* Evaluate all active pnodes (HMMs) */
889 fsg_search_hmm_eval(fsgs);
892 * Prune and propagate the HMMs evaluated; create history entries for
893 * word exits. The words exits are tentative, and may be pruned; make
894 * the survivors permanent via fsg_history_end_frame().
896 fsg_search_hmm_prune_prop(fsgs);
897 fsg_history_end_frame(fsgs->history);
900 * Propagate new history entries through any null transitions, creating
901 * new history entries, and then make the survivors permanent.
903 fsg_search_null_prop(fsgs);
904 fsg_history_end_frame(fsgs->history);
907 * Perform cross-word transitions; propagate each history entry across its
908 * terminating state to the root nodes of the lextree attached to the state.
910 fsg_search_word_trans(fsgs);
913 * We've now come full circle, HMM and FSG states have been updated for
915 * Update the active lists, deactivate any currently active HMMs that
916 * did not survive into the next frame
918 for (gn = fsgs->pnode_active; gn; gn = gnode_next(gn)) {
919 pnode = (fsg_pnode_t *) gnode_ptr(gn);
920 hmm = fsg_pnode_hmmptr(pnode);
922 if (hmm_frame(hmm) == fsgs->frame) {
923 /* This HMM NOT activated for the next frame; reset it */
924 fsg_psubtree_pnode_deactivate(pnode);
927 assert(hmm_frame(hmm) == (fsgs->frame + 1));
931 /* Free the currently active list */
932 glist_free(fsgs->pnode_active);
934 /* Make the next-frame active list the current one */
935 fsgs->pnode_active = fsgs->pnode_active_next;
936 fsgs->pnode_active_next = NULL;
938 /* End of this frame; ready for the next */
946 * Set all HMMs to inactive, clear active lists, initialize FSM start
947 * state to be the only active node.
948 * (Executed at the start of each utterance.)
951 fsg_search_start(ps_search_t *search)
953 fsg_search_t *fsgs = (fsg_search_t *)search;
955 fsg_pnode_ctxt_t ctxt;
957 /* Reset dynamic adjustment factor for beams */
958 fsgs->beam_factor = 1.0f;
959 fsgs->beam = fsgs->beam_orig;
960 fsgs->pbeam = fsgs->pbeam_orig;
961 fsgs->wbeam = fsgs->wbeam_orig;
963 silcipid = bin_mdef_ciphone_id(ps_search_acmod(fsgs)->mdef, "SIL");
965 /* Initialize EVERYTHING to be inactive */
966 assert(fsgs->pnode_active == NULL);
967 assert(fsgs->pnode_active_next == NULL);
969 fsg_history_reset(fsgs->history);
970 fsg_history_utt_start(fsgs->history);
973 /* Dummy context structure that allows all right contexts to use this entry */
974 fsg_pnode_add_all_ctxt(&ctxt);
976 /* Create dummy history entry leading to start state */
979 fsg_history_entry_add(fsgs->history,
980 NULL, -1, 0, -1, silcipid, ctxt);
981 fsgs->bpidx_start = 0;
983 /* Propagate dummy history entry through NULL transitions from start state */
984 fsg_search_null_prop(fsgs);
986 /* Perform word transitions from this dummy history entry */
987 fsg_search_word_trans(fsgs);
989 /* Make the next-frame active list the current one */
990 fsgs->pnode_active = fsgs->pnode_active_next;
991 fsgs->pnode_active_next = NULL;
995 fsgs->n_hmm_eval = 0;
996 fsgs->n_sen_eval = 0;
1002 * Cleanup at the end of each utterance.
1005 fsg_search_finish(ps_search_t *search)
1007 fsg_search_t *fsgs = (fsg_search_t *)search;
1012 /* Deactivate all nodes in the current and next-frame active lists */
1013 for (gn = fsgs->pnode_active; gn; gn = gnode_next(gn)) {
1014 pnode = (fsg_pnode_t *) gnode_ptr(gn);
1015 fsg_psubtree_pnode_deactivate(pnode);
1017 for (gn = fsgs->pnode_active_next; gn; gn = gnode_next(gn)) {
1018 pnode = (fsg_pnode_t *) gnode_ptr(gn);
1019 fsg_psubtree_pnode_deactivate(pnode);
1022 glist_free(fsgs->pnode_active);
1023 fsgs->pnode_active = NULL;
1024 glist_free(fsgs->pnode_active_next);
1025 fsgs->pnode_active_next = NULL;
1029 n_hist = fsg_history_n_entries(fsgs->history);
1031 ("%d frames, %d HMMs (%d/fr), %d senones (%d/fr), %d history entries (%d/fr)\n\n",
1032 fsgs->frame, fsgs->n_hmm_eval,
1033 (fsgs->frame > 0) ? fsgs->n_hmm_eval / fsgs->frame : 0,
1035 (fsgs->frame > 0) ? fsgs->n_sen_eval / fsgs->frame : 0,
1036 n_hist, (fsgs->frame > 0) ? n_hist / fsgs->frame : 0);
1042 fsg_search_find_exit(fsg_search_t *fsgs, int frame_idx, int final, int32 *out_score)
1044 fsg_hist_entry_t *hist_entry;
1046 int bpidx, frm, last_frm, besthist;
1049 if (frame_idx == -1)
1050 frame_idx = fsgs->frame - 1;
1051 last_frm = frm = frame_idx;
1053 /* Scan backwards to find a word exit in frame_idx. */
1054 bpidx = fsg_history_n_entries(fsgs->history) - 1;
1056 hist_entry = fsg_history_entry_get(fsgs->history, bpidx);
1057 if (fsg_hist_entry_frame(hist_entry) <= frame_idx) {
1058 frm = last_frm = fsg_hist_entry_frame(hist_entry);
1063 /* No hypothesis (yet). */
1067 /* Now find best word exit in this frame. */
1068 bestscore = INT_MIN;
1071 while (frm == last_frm) {
1075 fl = fsg_hist_entry_fsglink(hist_entry);
1076 score = fsg_hist_entry_score(hist_entry);
1081 if (score BETTER_THAN bestscore) {
1082 /* Only enforce the final state constraint if this is a final hypothesis. */
1084 || fsg_link_to_state(fl) == fsg_model_final_state(fsg)) {
1093 hist_entry = fsg_history_entry_get(fsgs->history, bpidx);
1094 frm = fsg_hist_entry_frame(hist_entry);
1097 /* Final state not reached. */
1098 if (besthist == -1) {
1099 E_ERROR("Final state not reached in frame %d\n", frame_idx);
1103 /* This here's the one we want. */
1105 *out_score = bestscore;
1109 /* FIXME: Mostly duplicated with ngram_search_bestpath(). */
1110 static ps_latlink_t *
1111 fsg_search_bestpath(ps_search_t *search, int32 *out_score, int backward)
1113 fsg_search_t *fsgs = (fsg_search_t *)search;
1115 if (search->last_link == NULL) {
1116 search->last_link = ps_lattice_bestpath(search->dag, NULL,
1118 if (search->last_link == NULL)
1120 /* Also calculate betas so we can fill in the posterior
1121 * probability field in the segmentation. */
1122 if (search->post == 0)
1123 search->post = ps_lattice_posterior(search->dag, NULL, fsgs->ascale);
1126 *out_score = search->last_link->path_scr + search->dag->final_node_ascr;
1127 return search->last_link;
1131 fsg_search_hyp(ps_search_t *search, int32 *out_score)
1133 fsg_search_t *fsgs = (fsg_search_t *)search;
1134 dict_t *dict = ps_search_dict(search);
1139 /* Get last backpointer table index. */
1140 bpidx = fsg_search_find_exit(fsgs, fsgs->frame, fsgs->final, out_score);
1141 /* No hypothesis (yet). */
1145 /* If bestpath is enabled and the utterance is complete, then run it. */
1146 if (fsgs->bestpath && fsgs->final) {
1150 if ((dag = fsg_search_lattice(search)) == NULL) {
1151 E_WARN("Failed to obtain the lattice while bestpath enabled\n");
1154 if ((link = fsg_search_bestpath(search, out_score, FALSE)) == NULL) {
1155 E_WARN("Failed to bestpath in a lattice\n");
1158 return ps_lattice_hyp(dag, link);
1164 fsg_hist_entry_t *hist_entry = fsg_history_entry_get(fsgs->history, bp);
1165 fsg_link_t *fl = fsg_hist_entry_fsglink(hist_entry);
1166 char const *baseword;
1169 bp = fsg_hist_entry_pred(hist_entry);
1170 wid = fsg_link_wid(fl);
1171 if (wid < 0 || fsg_model_is_filler(fsgs->fsg, wid))
1173 baseword = dict_basestr(dict,
1175 fsg_model_word_str(fsgs->fsg, wid)));
1176 len += strlen(baseword) + 1;
1179 ckd_free(search->hyp_str);
1181 search->hyp_str = NULL;
1182 return search->hyp_str;
1184 search->hyp_str = ckd_calloc(1, len);
1187 c = search->hyp_str + len - 1;
1189 fsg_hist_entry_t *hist_entry = fsg_history_entry_get(fsgs->history, bp);
1190 fsg_link_t *fl = fsg_hist_entry_fsglink(hist_entry);
1191 char const *baseword;
1194 bp = fsg_hist_entry_pred(hist_entry);
1195 wid = fsg_link_wid(fl);
1196 if (wid < 0 || fsg_model_is_filler(fsgs->fsg, wid))
1198 baseword = dict_basestr(dict,
1200 fsg_model_word_str(fsgs->fsg, wid)));
1201 len = strlen(baseword);
1203 memcpy(c, baseword, len);
1204 if (c > search->hyp_str) {
1210 return search->hyp_str;
1214 fsg_seg_bp2itor(ps_seg_t *seg, fsg_hist_entry_t *hist_entry)
1216 fsg_search_t *fsgs = (fsg_search_t *)seg->search;
1217 fsg_hist_entry_t *ph = NULL;
1220 if ((bp = fsg_hist_entry_pred(hist_entry)) >= 0)
1221 ph = fsg_history_entry_get(fsgs->history, bp);
1222 seg->word = fsg_model_word_str(fsgs->fsg, hist_entry->fsglink->wid);
1223 seg->ef = fsg_hist_entry_frame(hist_entry);
1224 seg->sf = ph ? fsg_hist_entry_frame(ph) + 1 : 0;
1225 /* This is kind of silly but it happens for null transitions. */
1226 if (seg->sf > seg->ef) seg->sf = seg->ef;
1227 seg->prob = 0; /* Bogus value... */
1228 /* "Language model" score = transition probability. */
1230 seg->lscr = hist_entry->fsglink->logs2prob;
1232 /* FIXME: Not sure exactly how cross-word triphones are handled. */
1233 seg->ascr = hist_entry->score - ph->score - seg->lscr;
1236 seg->ascr = hist_entry->score - seg->lscr;
1241 fsg_seg_free(ps_seg_t *seg)
1243 fsg_seg_t *itor = (fsg_seg_t *)seg;
1244 ckd_free(itor->hist);
1249 fsg_seg_next(ps_seg_t *seg)
1251 fsg_seg_t *itor = (fsg_seg_t *)seg;
1253 if (++itor->cur == itor->n_hist) {
1258 fsg_seg_bp2itor(seg, itor->hist[itor->cur]);
1262 static ps_segfuncs_t fsg_segfuncs = {
1263 /* seg_next */ fsg_seg_next,
1264 /* seg_free */ fsg_seg_free
1268 fsg_search_seg_iter(ps_search_t *search, int32 *out_score)
1270 fsg_search_t *fsgs = (fsg_search_t *)search;
1274 bpidx = fsg_search_find_exit(fsgs, fsgs->frame, fsgs->final, out_score);
1275 /* No hypothesis (yet). */
1279 /* If bestpath is enabled and the utterance is complete, then run it. */
1280 if (fsgs->bestpath && fsgs->final) {
1284 if ((dag = fsg_search_lattice(search)) == NULL)
1286 if ((link = fsg_search_bestpath(search, out_score, TRUE)) == NULL)
1288 return ps_lattice_seg_iter(dag, link, 1.0);
1291 /* Calling this an "iterator" is a bit of a misnomer since we have
1292 * to get the entire backtrace in order to produce it. On the
1293 * other hand, all we actually need is the bptbl IDs, and we can
1294 * allocate a fixed-size array of them. */
1295 itor = ckd_calloc(1, sizeof(*itor));
1296 itor->base.vt = &fsg_segfuncs;
1297 itor->base.search = search;
1298 itor->base.lwf = 1.0;
1302 fsg_hist_entry_t *hist_entry = fsg_history_entry_get(fsgs->history, bp);
1303 bp = fsg_hist_entry_pred(hist_entry);
1306 if (itor->n_hist == 0) {
1310 itor->hist = ckd_calloc(itor->n_hist, sizeof(*itor->hist));
1311 cur = itor->n_hist - 1;
1314 fsg_hist_entry_t *hist_entry = fsg_history_entry_get(fsgs->history, bp);
1315 itor->hist[cur] = hist_entry;
1316 bp = fsg_hist_entry_pred(hist_entry);
1320 /* Fill in relevant fields for first element. */
1321 fsg_seg_bp2itor((ps_seg_t *)itor, itor->hist[0]);
1323 return (ps_seg_t *)itor;
1327 fsg_search_prob(ps_search_t *search)
1329 fsg_search_t *fsgs = (fsg_search_t *)search;
1331 /* If bestpath is enabled and the utterance is complete, then run it. */
1332 if (fsgs->bestpath && fsgs->final) {
1336 if ((dag = fsg_search_lattice(search)) == NULL)
1338 if ((link = fsg_search_bestpath(search, NULL, TRUE)) == NULL)
1340 return search->post;
1343 /* FIXME: Give some kind of good estimate here, eventually. */
1348 static ps_latnode_t *
1349 new_node(ps_lattice_t *dag, fsg_model_t *fsg, int sf, int ef, int32 wid, int32 ascr)
1353 for (node = dag->nodes; node; node = node->next)
1354 if (node->sf == sf && node->wid == wid)
1358 /* Update end frames. */
1359 if (node->lef == -1 || node->lef < ef)
1361 if (node->fef == -1 || node->fef > ef)
1363 /* Update best link score. */
1364 if (ascr BETTER_THAN node->info.best_exit)
1365 node->info.best_exit = ascr;
1368 /* New node; link to head of list */
1369 node = listelem_malloc(dag->latnode_alloc);
1372 node->fef = node->lef = ef;
1373 node->reachable = FALSE;
1374 node->entries = NULL;
1376 node->info.best_exit = ascr;
1378 node->next = dag->nodes;
1386 static ps_latnode_t *
1387 find_node(ps_lattice_t *dag, fsg_model_t *fsg, int sf, int32 wid)
1391 for (node = dag->nodes; node; node = node->next)
1392 if (node->sf == sf && node->wid == wid)
1397 static ps_latnode_t *
1398 find_start_node(fsg_search_t *fsgs, ps_lattice_t *dag)
1401 glist_t start = NULL;
1404 /* Look for all nodes starting in frame zero with some exits. */
1405 for (node = dag->nodes; node; node = node->next) {
1406 if (node->sf == 0 && node->exits) {
1407 E_INFO("Start node %s.%d:%d:%d\n",
1408 fsg_model_word_str(fsgs->fsg, node->wid),
1409 node->sf, node->fef, node->lef);
1410 start = glist_add_ptr(start, node);
1415 /* If there was more than one start node candidate, then we need
1416 * to create an artificial start node with epsilon transitions to
1419 node = gnode_ptr(start);
1425 wid = fsg_model_word_add(fsgs->fsg, "<s>");
1426 if (fsgs->fsg->silwords)
1427 bitvec_set(fsgs->fsg->silwords, wid);
1428 node = new_node(dag, fsgs->fsg, 0, 0, wid, 0);
1429 for (st = start; st; st = gnode_next(st))
1430 ps_lattice_link(dag, node, gnode_ptr(st), 0, 0);
1436 static ps_latnode_t *
1437 find_end_node(fsg_search_t *fsgs, ps_lattice_t *dag)
1443 /* Look for all nodes ending in last frame with some entries. */
1444 for (node = dag->nodes; node; node = node->next) {
1445 if (node->lef == dag->n_frames - 1 && node->entries) {
1446 E_INFO("End node %s.%d:%d:%d (%d)\n",
1447 fsg_model_word_str(fsgs->fsg, node->wid),
1448 node->sf, node->fef, node->lef, node->info.best_exit);
1449 end = glist_add_ptr(end, node);
1455 node = gnode_ptr(end);
1457 else if (nend == 0) {
1458 ps_latnode_t *last = NULL;
1461 /* If there were no end node candidates, then just use the
1462 * node with the last exit frame. */
1463 for (node = dag->nodes; node; node = node->next) {
1464 if (node->lef > ef && node->entries) {
1471 E_INFO("End node %s.%d:%d:%d (%d)\n",
1472 fsg_model_word_str(fsgs->fsg, node->wid),
1473 node->sf, node->fef, node->lef, node->info.best_exit);
1476 /* If there was more than one end node candidate, then we need
1477 * to create an artificial end node with epsilon transitions
1478 * out of all of them. */
1482 wid = fsg_model_word_add(fsgs->fsg, "</s>");
1483 if (fsgs->fsg->silwords)
1484 bitvec_set(fsgs->fsg->silwords, wid);
1485 node = new_node(dag, fsgs->fsg, fsgs->frame, fsgs->frame, wid, 0);
1486 /* Use the "best" (in reality it will be the only) exit link
1487 * score from this final node as the link score. */
1488 for (st = end; st; st = gnode_next(st)) {
1489 ps_latnode_t *src = gnode_ptr(st);
1490 ps_lattice_link(dag, src, node, src->info.best_exit, fsgs->frame);
1498 mark_reachable(ps_lattice_t *dag, ps_latnode_t *end)
1502 /* It doesn't matter which order we do this in. */
1503 end->reachable = TRUE;
1504 q = glist_add_ptr(NULL, end);
1506 ps_latnode_t *node = gnode_ptr(q);
1509 /* Pop the front of the list. */
1510 q = gnode_free(q, NULL);
1511 /* Expand all its predecessors that haven't been seen yet. */
1512 for (x = node->entries; x; x = x->next) {
1513 ps_latnode_t *next = x->link->from;
1514 if (!next->reachable) {
1515 next->reachable = TRUE;
1516 q = glist_add_ptr(q, next);
1523 * Generate a lattice from FSG search results.
1525 * One might think that this is simply a matter of adding acoustic
1526 * scores to the FSG's edges. However, one would be wrong. The
1527 * crucial difference here is that the word lattice is acyclic, and it
1528 * also contains timing information.
1530 static ps_lattice_t *
1531 fsg_search_lattice(ps_search_t *search)
1539 fsgs = (fsg_search_t *)search;
1541 /* Check to see if a lattice has previously been created over the
1542 * same number of frames, and reuse it if so. */
1543 if (search->dag && search->dag->n_frames == fsgs->frame)
1546 /* Nope, create a new one. */
1547 ps_lattice_free(search->dag);
1549 dag = ps_lattice_init_search(search, fsgs->frame);
1553 * Each history table entry represents a link in the word graph.
1554 * The set of nodes is determined by the number of unique
1555 * (word,start-frame) pairs in the history table. So we will
1556 * first find all those nodes.
1558 n = fsg_history_n_entries(fsgs->history);
1559 for (i = 0; i < n; ++i) {
1560 fsg_hist_entry_t *fh = fsg_history_entry_get(fsgs->history, i);
1565 /* Skip null transitions. */
1566 if (fh->fsglink == NULL || fh->fsglink->wid == -1)
1569 /* Find the start node of this link. */
1571 fsg_hist_entry_t *pfh = fsg_history_entry_get(fsgs->history, fh->pred);
1572 /* FIXME: We include the transition score in the lattice
1573 * link score. This is because of the practical
1574 * difficulty of obtaining it separately in bestpath or
1575 * forward-backward search, and because it is essentially
1576 * a unigram probability, so there is no need to treat it
1577 * separately from the acoustic score. However, it's not
1578 * clear that this will actually yield correct results.*/
1579 ascr = fh->score - pfh->score;
1580 sf = pfh->frame + 1;
1588 * Note that although scores are tied to links rather than
1589 * nodes, it's possible that there are no links out of the
1590 * destination node, and thus we need to preserve its score in
1591 * case it turns out to be utterance-final.
1593 node = new_node(dag, fsg, sf, fh->frame, fh->fsglink->wid, ascr);
1597 * Now, we will create links only to nodes that actually exist.
1599 n = fsg_history_n_entries(fsgs->history);
1600 for (i = 0; i < n; ++i) {
1601 fsg_hist_entry_t *fh = fsg_history_entry_get(fsgs->history, i);
1602 fsg_arciter_t *itor;
1603 ps_latnode_t *src, *dest;
1607 /* Skip null transitions. */
1608 if (fh->fsglink == NULL || fh->fsglink->wid == -1)
1611 /* Find the start node of this link and calculate its link score. */
1613 fsg_hist_entry_t *pfh = fsg_history_entry_get(fsgs->history, fh->pred);
1614 sf = pfh->frame + 1;
1615 ascr = fh->score - pfh->score;
1621 src = find_node(dag, fsg, sf, fh->fsglink->wid);
1624 for (itor = fsg_model_arcs(fsg, fsg_link_to_state(fh->fsglink));
1625 itor; itor = fsg_arciter_next(itor)) {
1626 fsg_link_t *link = fsg_arciter_get(itor);
1628 /* FIXME: Need to figure out what to do about tag transitions. */
1629 if (link->wid >= 0) {
1631 * For each non-epsilon link following this one, look for a
1632 * matching node in the lattice and link to it.
1634 if ((dest = find_node(dag, fsg, sf, link->wid)) != NULL)
1635 ps_lattice_link(dag, src, dest, ascr, fh->frame);
1639 * Transitive closure on nulls has already been done, so we
1640 * just need to look one link forward from them.
1642 fsg_arciter_t *itor2;
1644 /* Add all non-null links out of j. */
1645 for (itor2 = fsg_model_arcs(fsg, fsg_link_to_state(link));
1646 itor2; itor2 = fsg_arciter_next(itor2)) {
1647 fsg_link_t *link = fsg_arciter_get(itor2);
1648 if (link->wid == -1)
1650 if ((dest = find_node(dag, fsg, sf, link->wid)) != NULL)
1651 ps_lattice_link(dag, src, dest, ascr, fh->frame);
1657 /* Figure out which nodes are the start and end nodes. */
1658 if ((dag->start = find_start_node(fsgs, dag)) == NULL)
1660 if ((dag->end = find_end_node(fsgs, dag)) == NULL)
1662 E_INFO("lattice start node %s.%d end node %s.%d\n",
1663 fsg_model_word_str(fsg, dag->start->wid), dag->start->sf,
1664 fsg_model_word_str(fsg, dag->end->wid), dag->end->sf);
1665 /* FIXME: Need to calculate final_node_ascr here. */
1668 * Convert word IDs from FSG to dictionary.
1670 for (node = dag->nodes; node; node = node->next) {
1671 node->wid = dict_wordid(dag->search->dict,
1672 fsg_model_word_str(fsg, node->wid));
1673 node->basewid = dict_basewid(dag->search->dict, node->wid);
1677 * Now we are done, because the links in the graph are uniquely
1678 * defined by the history table. However we should remove any
1679 * nodes which are not reachable from the end node of the FSG.
1680 * Everything is reachable from the start node by definition.
1682 mark_reachable(dag, dag->end);
1684 ps_lattice_delete_unreachable(dag);
1686 int32 silpen, fillpen;
1688 silpen = (int32)(logmath_log(fsg->lmath,
1689 cmd_ln_float32_r(ps_search_config(fsgs), "-silprob"))
1692 fillpen = (int32)(logmath_log(fsg->lmath,
1693 cmd_ln_float32_r(ps_search_config(fsgs), "-fillprob"))
1696 ps_lattice_bypass_fillers(dag, silpen, fillpen);
1702 ps_lattice_free(dag);