summaryrefslogtreecommitdiffstats
path: root/media/pocketsphinx/src/fsg_search.c
diff options
context:
space:
mode:
Diffstat (limited to 'media/pocketsphinx/src/fsg_search.c')
-rw-r--r--media/pocketsphinx/src/fsg_search.c1550
1 files changed, 1550 insertions, 0 deletions
diff --git a/media/pocketsphinx/src/fsg_search.c b/media/pocketsphinx/src/fsg_search.c
new file mode 100644
index 000000000..f24a0fb83
--- /dev/null
+++ b/media/pocketsphinx/src/fsg_search.c
@@ -0,0 +1,1550 @@
+/* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* ====================================================================
+ * Copyright (c) 1999-2004 Carnegie Mellon University. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
+ * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
+ * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ====================================================================
+ *
+ */
+
+/*
+ * fsg_search.c -- Search structures for FSM decoding.
+ *
+ * **********************************************
+ * CMU ARPA Speech Project
+ *
+ * Copyright (c) 2004 Carnegie Mellon University.
+ * ALL RIGHTS RESERVED.
+ * **********************************************
+ *
+ * HISTORY
+ *
+ * 18-Feb-2004 M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon
+ * Started.
+ */
+
+/* System headers. */
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+/* SphinxBase headers. */
+#include <sphinxbase/err.h>
+#include <sphinxbase/ckd_alloc.h>
+#include <sphinxbase/strfuncs.h>
+#include <sphinxbase/cmd_ln.h>
+
+/* Local headers. */
+#include "pocketsphinx_internal.h"
+#include "ps_lattice_internal.h"
+#include "fsg_search_internal.h"
+#include "fsg_history.h"
+#include "fsg_lextree.h"
+#include "dict.h"
+
+/* Turn this on for detailed debugging dump */
+#define __FSG_DBG__ 0
+#define __FSG_DBG_CHAN__ 0
+
+static ps_seg_t *fsg_search_seg_iter(ps_search_t *search, int32 *out_score);
+static ps_lattice_t *fsg_search_lattice(ps_search_t *search);
+static int fsg_search_prob(ps_search_t *search);
+
+static ps_searchfuncs_t fsg_funcs = {
+ /* name: */ "fsg",
+ /* start: */ fsg_search_start,
+ /* step: */ fsg_search_step,
+ /* finish: */ fsg_search_finish,
+ /* reinit: */ fsg_search_reinit,
+ /* free: */ fsg_search_free,
+ /* lattice: */ fsg_search_lattice,
+ /* hyp: */ fsg_search_hyp,
+ /* prob: */ fsg_search_prob,
+ /* seg_iter: */ fsg_search_seg_iter,
+};
+
+static int
+fsg_search_add_silences(fsg_search_t *fsgs, fsg_model_t *fsg)
+{
+ dict_t *dict;
+ int32 wid;
+ int n_sil;
+
+ dict = ps_search_dict(fsgs);
+ /*
+ * NOTE: Unlike N-Gram search, we do not use explicit start and
+ * end symbols. This is because the start and end nodes are
+ * defined in the grammar. We do add silence/filler self-loops to
+ * all states in order to allow for silence between words and at
+ * the beginning and end of utterances.
+ *
+ * This has some implications for word graph generation, namely,
+ * that there can (and usually will) be multiple start and end
+ * states in the word graph. We therefore do add explicit start
+ * and end nodes to the graph.
+ */
+ /* Add silence self-loops to all states. */
+ fsg_model_add_silence(fsg, "<sil>", -1,
+ cmd_ln_float32_r(ps_search_config(fsgs), "-silprob"));
+ n_sil = 0;
+ /* Add self-loops for all other fillers. */
+ for (wid = dict_filler_start(dict); wid < dict_filler_end(dict); ++wid) {
+ char const *word = dict_wordstr(dict, wid);
+ if (wid == dict_startwid(dict) || wid == dict_finishwid(dict))
+ continue;
+ fsg_model_add_silence(fsg, word, -1,
+ cmd_ln_float32_r(ps_search_config(fsgs), "-fillprob"));
+ ++n_sil;
+ }
+
+ return n_sil;
+}
+
+/* Scans the dictionary and check if all words are present. */
+static int
+fsg_search_check_dict(fsg_search_t *fsgs, fsg_model_t *fsg)
+{
+ dict_t *dict;
+ int i;
+
+ dict = ps_search_dict(fsgs);
+ for (i = 0; i < fsg_model_n_word(fsg); ++i) {
+ char const *word;
+ int32 wid;
+
+ word = fsg_model_word_str(fsg, i);
+ wid = dict_wordid(dict, word);
+ if (wid == BAD_S3WID) {
+ E_WARN("The word '%s' is missing in the dictionary. Trying to create new phoneme \n", word);
+ if (!dict->ngram_g2p_model) {
+ E_ERROR("NO dict->ngram_g2p_model. Aborting..");
+ return FALSE;
+ }
+
+ int new_wid = dict_add_g2p_word(dict, word);
+ if (new_wid > 0){
+ /* Now we also have to add it to dict2pid. */
+ dict2pid_add_word(ps_search_dict2pid(fsgs), new_wid);
+ } else {
+ E_ERROR("Exiting... \n");
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static int
+fsg_search_add_altpron(fsg_search_t *fsgs, fsg_model_t *fsg)
+{
+ dict_t *dict;
+ int n_alt, n_word;
+ int i;
+
+ dict = ps_search_dict(fsgs);
+ /* Scan FSG's vocabulary for words that have alternate pronunciations. */
+ n_alt = 0;
+ n_word = fsg_model_n_word(fsg);
+ for (i = 0; i < n_word; ++i) {
+ char const *word;
+ int32 wid;
+
+ word = fsg_model_word_str(fsg, i);
+ wid = dict_wordid(dict, word);
+ if (wid != BAD_S3WID) {
+ while ((wid = dict_nextalt(dict, wid)) != BAD_S3WID) {
+ n_alt += fsg_model_add_alt(fsg, word, dict_wordstr(dict, wid));
+ }
+ }
+ }
+
+ E_INFO("Added %d alternate word transitions\n", n_alt);
+ return n_alt;
+}
+
+ps_search_t *
+fsg_search_init(fsg_model_t *fsg,
+ cmd_ln_t *config,
+ acmod_t *acmod,
+ dict_t *dict,
+ dict2pid_t *d2p)
+{
+ fsg_search_t *fsgs = ckd_calloc(1, sizeof(*fsgs));
+ ps_search_init(ps_search_base(fsgs), &fsg_funcs, config, acmod, dict, d2p);
+
+ fsgs->fsg = fsg_model_retain(fsg);
+ /* Initialize HMM context. */
+ fsgs->hmmctx = hmm_context_init(bin_mdef_n_emit_state(acmod->mdef),
+ acmod->tmat->tp, NULL, acmod->mdef->sseq);
+ if (fsgs->hmmctx == NULL) {
+ ps_search_free(ps_search_base(fsgs));
+ return NULL;
+ }
+
+ /* Intialize the search history object */
+ fsgs->history = fsg_history_init(NULL, dict);
+ fsgs->frame = -1;
+
+ /* Get search pruning parameters */
+ fsgs->beam_factor = 1.0f;
+ fsgs->beam = fsgs->beam_orig
+ = (int32) logmath_log(acmod->lmath, cmd_ln_float64_r(config, "-beam"))
+ >> SENSCR_SHIFT;
+ fsgs->pbeam = fsgs->pbeam_orig
+ = (int32) logmath_log(acmod->lmath, cmd_ln_float64_r(config, "-pbeam"))
+ >> SENSCR_SHIFT;
+ fsgs->wbeam = fsgs->wbeam_orig
+ = (int32) logmath_log(acmod->lmath, cmd_ln_float64_r(config, "-wbeam"))
+ >> SENSCR_SHIFT;
+
+ /* LM related weights/penalties */
+ fsgs->lw = cmd_ln_float32_r(config, "-lw");
+ fsgs->pip = (int32) (logmath_log(acmod->lmath, cmd_ln_float32_r(config, "-pip"))
+ * fsgs->lw)
+ >> SENSCR_SHIFT;
+ fsgs->wip = (int32) (logmath_log(acmod->lmath, cmd_ln_float32_r(config, "-wip"))
+ * fsgs->lw)
+ >> SENSCR_SHIFT;
+
+ /* Acoustic score scale for posterior probabilities. */
+ fsgs->ascale = 1.0 / cmd_ln_float32_r(config, "-ascale");
+
+ E_INFO("FSG(beam: %d, pbeam: %d, wbeam: %d; wip: %d, pip: %d)\n",
+ fsgs->beam_orig, fsgs->pbeam_orig, fsgs->wbeam_orig,
+ fsgs->wip, fsgs->pip);
+
+ if (!fsg_search_check_dict(fsgs, fsg)) {
+ fsg_search_free(ps_search_base(fsgs));
+ return NULL;
+ }
+
+ if (cmd_ln_boolean_r(config, "-fsgusefiller") &&
+ !fsg_model_has_sil(fsg))
+ fsg_search_add_silences(fsgs, fsg);
+
+ if (cmd_ln_boolean_r(config, "-fsgusealtpron") &&
+ !fsg_model_has_alt(fsg))
+ fsg_search_add_altpron(fsgs, fsg);
+
+ if (fsg_search_reinit(ps_search_base(fsgs),
+ ps_search_dict(fsgs),
+ ps_search_dict2pid(fsgs)) < 0)
+ {
+ ps_search_free(ps_search_base(fsgs));
+ return NULL;
+ }
+
+ return ps_search_base(fsgs);
+}
+
+void
+fsg_search_free(ps_search_t *search)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+
+ ps_search_deinit(search);
+ fsg_lextree_free(fsgs->lextree);
+ if (fsgs->history) {
+ fsg_history_reset(fsgs->history);
+ fsg_history_set_fsg(fsgs->history, NULL, NULL);
+ fsg_history_free(fsgs->history);
+ }
+ hmm_context_free(fsgs->hmmctx);
+ fsg_model_free(fsgs->fsg);
+ ckd_free(fsgs);
+}
+
+int
+fsg_search_reinit(ps_search_t *search, dict_t *dict, dict2pid_t *d2p)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+
+ /* Free the old lextree */
+ if (fsgs->lextree)
+ fsg_lextree_free(fsgs->lextree);
+
+ /* Free old dict2pid, dict */
+ ps_search_base_reinit(search, dict, d2p);
+
+ /* Update the number of words (not used by this module though). */
+ search->n_words = dict_size(dict);
+
+ /* Allocate new lextree for the given FSG */
+ fsgs->lextree = fsg_lextree_init(fsgs->fsg, dict, d2p,
+ ps_search_acmod(fsgs)->mdef,
+ fsgs->hmmctx, fsgs->wip, fsgs->pip);
+
+ /* Inform the history module of the new fsg */
+ fsg_history_set_fsg(fsgs->history, fsgs->fsg, dict);
+
+ return 0;
+}
+
+
+static void
+fsg_search_sen_active(fsg_search_t *fsgs)
+{
+ gnode_t *gn;
+ fsg_pnode_t *pnode;
+ hmm_t *hmm;
+
+ acmod_clear_active(ps_search_acmod(fsgs));
+
+ for (gn = fsgs->pnode_active; gn; gn = gnode_next(gn)) {
+ pnode = (fsg_pnode_t *) gnode_ptr(gn);
+ hmm = fsg_pnode_hmmptr(pnode);
+ assert(hmm_frame(hmm) == fsgs->frame);
+ acmod_activate_hmm(ps_search_acmod(fsgs), hmm);
+ }
+}
+
+
+/*
+ * Evaluate all the active HMMs.
+ * (Executed once per frame.)
+ */
+static void
+fsg_search_hmm_eval(fsg_search_t *fsgs)
+{
+ gnode_t *gn;
+ fsg_pnode_t *pnode;
+ hmm_t *hmm;
+ int32 bestscore;
+ int32 n, maxhmmpf;
+
+ bestscore = WORST_SCORE;
+
+ if (!fsgs->pnode_active) {
+ E_ERROR("Frame %d: No active HMM!!\n", fsgs->frame);
+ return;
+ }
+
+ for (n = 0, gn = fsgs->pnode_active; gn; gn = gnode_next(gn), n++) {
+ int32 score;
+
+ pnode = (fsg_pnode_t *) gnode_ptr(gn);
+ hmm = fsg_pnode_hmmptr(pnode);
+ assert(hmm_frame(hmm) == fsgs->frame);
+
+#if __FSG_DBG__
+ E_INFO("pnode(%08x) active @frm %5d\n", (int32) pnode,
+ fsgs->frame);
+ hmm_dump(hmm, stdout);
+#endif
+ score = hmm_vit_eval(hmm);
+#if __FSG_DBG_CHAN__
+ E_INFO("pnode(%08x) after eval @frm %5d\n",
+ (int32) pnode, fsgs->frame);
+ hmm_dump(hmm, stdout);
+#endif
+
+ if (score BETTER_THAN bestscore)
+ bestscore = score;
+ }
+
+#if __FSG_DBG__
+ E_INFO("[%5d] %6d HMM; bestscr: %11d\n", fsgs->frame, n, bestscore);
+#endif
+ fsgs->n_hmm_eval += n;
+
+ /* Adjust beams if #active HMMs larger than absolute threshold */
+ maxhmmpf = cmd_ln_int32_r(ps_search_config(fsgs), "-maxhmmpf");
+ if (maxhmmpf != -1 && n > maxhmmpf) {
+ /*
+ * Too many HMMs active; reduce the beam factor applied to the default
+ * beams, but not if the factor is already at a floor (0.1).
+ */
+ if (fsgs->beam_factor > 0.1) { /* Hack!! Hardwired constant 0.1 */
+ fsgs->beam_factor *= 0.9f; /* Hack!! Hardwired constant 0.9 */
+ fsgs->beam =
+ (int32) (fsgs->beam_orig * fsgs->beam_factor);
+ fsgs->pbeam =
+ (int32) (fsgs->pbeam_orig * fsgs->beam_factor);
+ fsgs->wbeam =
+ (int32) (fsgs->wbeam_orig * fsgs->beam_factor);
+ }
+ }
+ else {
+ fsgs->beam_factor = 1.0f;
+ fsgs->beam = fsgs->beam_orig;
+ fsgs->pbeam = fsgs->pbeam_orig;
+ fsgs->wbeam = fsgs->wbeam_orig;
+ }
+
+ if (n > fsg_lextree_n_pnode(fsgs->lextree))
+ E_FATAL("PANIC! Frame %d: #HMM evaluated(%d) > #PNodes(%d)\n",
+ fsgs->frame, n, fsg_lextree_n_pnode(fsgs->lextree));
+
+ fsgs->bestscore = bestscore;
+}
+
+
+static void
+fsg_search_pnode_trans(fsg_search_t *fsgs, fsg_pnode_t * pnode)
+{
+ fsg_pnode_t *child;
+ hmm_t *hmm;
+ int32 newscore, thresh, nf;
+
+ assert(pnode);
+ assert(!fsg_pnode_leaf(pnode));
+
+ nf = fsgs->frame + 1;
+ thresh = fsgs->bestscore + fsgs->beam;
+
+ hmm = fsg_pnode_hmmptr(pnode);
+
+ for (child = fsg_pnode_succ(pnode);
+ child; child = fsg_pnode_sibling(child)) {
+ newscore = hmm_out_score(hmm) + child->logs2prob;
+
+ if ((newscore BETTER_THAN thresh)
+ && (newscore BETTER_THAN hmm_in_score(&child->hmm))) {
+ /* Incoming score > pruning threshold and > target's existing score */
+ if (hmm_frame(&child->hmm) < nf) {
+ /* Child node not yet activated; do so */
+ fsgs->pnode_active_next =
+ glist_add_ptr(fsgs->pnode_active_next,
+ (void *) child);
+ }
+
+ hmm_enter(&child->hmm, newscore, hmm_out_history(hmm), nf);
+ }
+ }
+}
+
+
+static void
+fsg_search_pnode_exit(fsg_search_t *fsgs, fsg_pnode_t * pnode)
+{
+ hmm_t *hmm;
+ fsg_link_t *fl;
+ int32 wid;
+ fsg_pnode_ctxt_t ctxt;
+
+ assert(pnode);
+ assert(fsg_pnode_leaf(pnode));
+
+ hmm = fsg_pnode_hmmptr(pnode);
+ fl = fsg_pnode_fsglink(pnode);
+ assert(fl);
+
+ wid = fsg_link_wid(fl);
+ assert(wid >= 0);
+
+#if __FSG_DBG__
+ E_INFO("[%5d] Exit(%08x) %10d(score) %5d(pred)\n",
+ fsgs->frame, (int32) pnode,
+ hmm_out_score(hmm), hmm_out_history(hmm));
+#endif
+
+ /*
+ * Check if this is filler or single phone word; these do not model right
+ * context (i.e., the exit score applies to all right contexts).
+ */
+ if (fsg_model_is_filler(fsgs->fsg, wid)
+ /* FIXME: This might be slow due to repeated calls to dict_to_id(). */
+ || (dict_is_single_phone(ps_search_dict(fsgs),
+ dict_wordid(ps_search_dict(fsgs),
+ fsg_model_word_str(fsgs->fsg, wid))))) {
+ /* Create a dummy context structure that applies to all right contexts */
+ fsg_pnode_add_all_ctxt(&ctxt);
+
+ /* Create history table entry for this word exit */
+ fsg_history_entry_add(fsgs->history,
+ fl,
+ fsgs->frame,
+ hmm_out_score(hmm),
+ hmm_out_history(hmm),
+ pnode->ci_ext, ctxt);
+
+ }
+ else {
+ /* Create history table entry for this word exit */
+ fsg_history_entry_add(fsgs->history,
+ fl,
+ fsgs->frame,
+ hmm_out_score(hmm),
+ hmm_out_history(hmm),
+ pnode->ci_ext, pnode->ctxt);
+ }
+}
+
+
+/*
+ * (Beam) prune the just evaluated HMMs, determine which ones remain
+ * active, which ones transition to successors, which ones exit and
+ * terminate in their respective destination FSM states.
+ * (Executed once per frame.)
+ */
+static void
+fsg_search_hmm_prune_prop(fsg_search_t *fsgs)
+{
+ gnode_t *gn;
+ fsg_pnode_t *pnode;
+ hmm_t *hmm;
+ int32 thresh, word_thresh, phone_thresh;
+
+ assert(fsgs->pnode_active_next == NULL);
+
+ thresh = fsgs->bestscore + fsgs->beam;
+ phone_thresh = fsgs->bestscore + fsgs->pbeam;
+ word_thresh = fsgs->bestscore + fsgs->wbeam;
+
+ for (gn = fsgs->pnode_active; gn; gn = gnode_next(gn)) {
+ pnode = (fsg_pnode_t *) gnode_ptr(gn);
+ hmm = fsg_pnode_hmmptr(pnode);
+
+ if (hmm_bestscore(hmm) >= thresh) {
+ /* Keep this HMM active in the next frame */
+ if (hmm_frame(hmm) == fsgs->frame) {
+ hmm_frame(hmm) = fsgs->frame + 1;
+ fsgs->pnode_active_next =
+ glist_add_ptr(fsgs->pnode_active_next,
+ (void *) pnode);
+ }
+ else {
+ assert(hmm_frame(hmm) == fsgs->frame + 1);
+ }
+
+ if (!fsg_pnode_leaf(pnode)) {
+ if (hmm_out_score(hmm) >= phone_thresh) {
+ /* Transition out of this phone into its children */
+ fsg_search_pnode_trans(fsgs, pnode);
+ }
+ }
+ else {
+ if (hmm_out_score(hmm) >= word_thresh) {
+ /* Transition out of leaf node into destination FSG state */
+ fsg_search_pnode_exit(fsgs, pnode);
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * Propagate newly created history entries through null transitions.
+ */
+static void
+fsg_search_null_prop(fsg_search_t *fsgs)
+{
+ int32 bpidx, n_entries, thresh, newscore;
+ fsg_hist_entry_t *hist_entry;
+ fsg_link_t *l;
+ int32 s;
+ fsg_model_t *fsg;
+
+ fsg = fsgs->fsg;
+ thresh = fsgs->bestscore + fsgs->wbeam; /* Which beam really?? */
+
+ n_entries = fsg_history_n_entries(fsgs->history);
+
+ for (bpidx = fsgs->bpidx_start; bpidx < n_entries; bpidx++) {
+ fsg_arciter_t *itor;
+ hist_entry = fsg_history_entry_get(fsgs->history, bpidx);
+
+ l = fsg_hist_entry_fsglink(hist_entry);
+
+ /* Destination FSG state for history entry */
+ s = l ? fsg_link_to_state(l) : fsg_model_start_state(fsg);
+
+ /*
+ * Check null transitions from d to all other states. (Only need to
+ * propagate one step, since FSG contains transitive closure of null
+ * transitions.)
+ */
+ /* Add all links from from_state to dst */
+ for (itor = fsg_model_arcs(fsg, s); itor;
+ itor = fsg_arciter_next(itor)) {
+ fsg_link_t *l = fsg_arciter_get(itor);
+
+ /* FIXME: Need to deal with tag transitions somehow. */
+ if (fsg_link_wid(l) != -1)
+ continue;
+ newscore =
+ fsg_hist_entry_score(hist_entry) +
+ (fsg_link_logs2prob(l) >> SENSCR_SHIFT);
+
+ if (newscore >= thresh) {
+ fsg_history_entry_add(fsgs->history, l,
+ fsg_hist_entry_frame(hist_entry),
+ newscore,
+ bpidx,
+ fsg_hist_entry_lc(hist_entry),
+ fsg_hist_entry_rc(hist_entry));
+ }
+ }
+ }
+}
+
+
+/*
+ * Perform cross-word transitions; propagate each history entry created in this
+ * frame to lextree roots attached to the target FSG state for that entry.
+ */
+static void
+fsg_search_word_trans(fsg_search_t *fsgs)
+{
+ int32 bpidx, n_entries;
+ fsg_hist_entry_t *hist_entry;
+ fsg_link_t *l;
+ int32 score, newscore, thresh, nf, d;
+ fsg_pnode_t *root;
+ int32 lc, rc;
+
+ n_entries = fsg_history_n_entries(fsgs->history);
+
+ thresh = fsgs->bestscore + fsgs->beam;
+ nf = fsgs->frame + 1;
+
+ for (bpidx = fsgs->bpidx_start; bpidx < n_entries; bpidx++) {
+ hist_entry = fsg_history_entry_get(fsgs->history, bpidx);
+ assert(hist_entry);
+ score = fsg_hist_entry_score(hist_entry);
+ assert(fsgs->frame == fsg_hist_entry_frame(hist_entry));
+
+ l = fsg_hist_entry_fsglink(hist_entry);
+
+ /* Destination state for hist_entry */
+ d = l ? fsg_link_to_state(l) : fsg_model_start_state(fsgs->
+ fsg);
+
+ lc = fsg_hist_entry_lc(hist_entry);
+
+ /* Transition to all root nodes attached to state d */
+ for (root = fsg_lextree_root(fsgs->lextree, d);
+ root; root = root->sibling) {
+ rc = root->ci_ext;
+
+ if ((root->ctxt.bv[lc >> 5] & (1 << (lc & 0x001f))) &&
+ (hist_entry->rc.bv[rc >> 5] & (1 << (rc & 0x001f)))) {
+ /*
+ * Last CIphone of history entry is in left-context list supported by
+ * target root node, and
+ * first CIphone of target root node is in right context list supported
+ * by history entry;
+ * So the transition can go ahead (if new score is good enough).
+ */
+ newscore = score + root->logs2prob;
+
+ if ((newscore BETTER_THAN thresh)
+ && (newscore BETTER_THAN hmm_in_score(&root->hmm))) {
+ if (hmm_frame(&root->hmm) < nf) {
+ /* Newly activated node; add to active list */
+ fsgs->pnode_active_next =
+ glist_add_ptr(fsgs->pnode_active_next,
+ (void *) root);
+#if __FSG_DBG__
+ E_INFO
+ ("[%5d] WordTrans bpidx[%d] -> pnode[%08x] (activated)\n",
+ fsgs->frame, bpidx, (int32) root);
+#endif
+ }
+ else {
+#if __FSG_DBG__
+ E_INFO
+ ("[%5d] WordTrans bpidx[%d] -> pnode[%08x]\n",
+ fsgs->frame, bpidx, (int32) root);
+#endif
+ }
+
+ hmm_enter(&root->hmm, newscore, bpidx, nf);
+ }
+ }
+ }
+ }
+}
+
+
+int
+fsg_search_step(ps_search_t *search, int frame_idx)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+ int16 const *senscr;
+ acmod_t *acmod = search->acmod;
+ gnode_t *gn;
+ fsg_pnode_t *pnode;
+ hmm_t *hmm;
+
+ /* Activate our HMMs for the current frame if need be. */
+ if (!acmod->compallsen)
+ fsg_search_sen_active(fsgs);
+ /* Compute GMM scores for the current frame. */
+ senscr = acmod_score(acmod, &frame_idx);
+ fsgs->n_sen_eval += acmod->n_senone_active;
+ hmm_context_set_senscore(fsgs->hmmctx, senscr);
+
+ /* Mark backpointer table for current frame. */
+ fsgs->bpidx_start = fsg_history_n_entries(fsgs->history);
+
+ /* Evaluate all active pnodes (HMMs) */
+ fsg_search_hmm_eval(fsgs);
+
+ /*
+ * Prune and propagate the HMMs evaluated; create history entries for
+ * word exits. The words exits are tentative, and may be pruned; make
+ * the survivors permanent via fsg_history_end_frame().
+ */
+ fsg_search_hmm_prune_prop(fsgs);
+ fsg_history_end_frame(fsgs->history);
+
+ /*
+ * Propagate new history entries through any null transitions, creating
+ * new history entries, and then make the survivors permanent.
+ */
+ fsg_search_null_prop(fsgs);
+ fsg_history_end_frame(fsgs->history);
+
+ /*
+ * Perform cross-word transitions; propagate each history entry across its
+ * terminating state to the root nodes of the lextree attached to the state.
+ */
+ fsg_search_word_trans(fsgs);
+
+ /*
+ * We've now come full circle, HMM and FSG states have been updated for
+ * the next frame.
+ * Update the active lists, deactivate any currently active HMMs that
+ * did not survive into the next frame
+ */
+ for (gn = fsgs->pnode_active; gn; gn = gnode_next(gn)) {
+ pnode = (fsg_pnode_t *) gnode_ptr(gn);
+ hmm = fsg_pnode_hmmptr(pnode);
+
+ if (hmm_frame(hmm) == fsgs->frame) {
+ /* This HMM NOT activated for the next frame; reset it */
+ fsg_psubtree_pnode_deactivate(pnode);
+ }
+ else {
+ assert(hmm_frame(hmm) == (fsgs->frame + 1));
+ }
+ }
+
+ /* Free the currently active list */
+ glist_free(fsgs->pnode_active);
+
+ /* Make the next-frame active list the current one */
+ fsgs->pnode_active = fsgs->pnode_active_next;
+ fsgs->pnode_active_next = NULL;
+
+ /* End of this frame; ready for the next */
+ ++fsgs->frame;
+
+ return 1;
+}
+
+
+/*
+ * Set all HMMs to inactive, clear active lists, initialize FSM start
+ * state to be the only active node.
+ * (Executed at the start of each utterance.)
+ */
+int
+fsg_search_start(ps_search_t *search)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+ int32 silcipid;
+ fsg_pnode_ctxt_t ctxt;
+
+ /* Reset dynamic adjustment factor for beams */
+ fsgs->beam_factor = 1.0f;
+ fsgs->beam = fsgs->beam_orig;
+ fsgs->pbeam = fsgs->pbeam_orig;
+ fsgs->wbeam = fsgs->wbeam_orig;
+
+ silcipid = bin_mdef_ciphone_id(ps_search_acmod(fsgs)->mdef, "SIL");
+
+ /* Initialize EVERYTHING to be inactive */
+ assert(fsgs->pnode_active == NULL);
+ assert(fsgs->pnode_active_next == NULL);
+
+ fsg_history_reset(fsgs->history);
+ fsg_history_utt_start(fsgs->history);
+ fsgs->final = FALSE;
+
+ /* Dummy context structure that allows all right contexts to use this entry */
+ fsg_pnode_add_all_ctxt(&ctxt);
+
+ /* Create dummy history entry leading to start state */
+ fsgs->frame = -1;
+ fsgs->bestscore = 0;
+ fsg_history_entry_add(fsgs->history,
+ NULL, -1, 0, -1, silcipid, ctxt);
+ fsgs->bpidx_start = 0;
+
+ /* Propagate dummy history entry through NULL transitions from start state */
+ fsg_search_null_prop(fsgs);
+
+ /* Perform word transitions from this dummy history entry */
+ fsg_search_word_trans(fsgs);
+
+ /* Make the next-frame active list the current one */
+ fsgs->pnode_active = fsgs->pnode_active_next;
+ fsgs->pnode_active_next = NULL;
+
+ ++fsgs->frame;
+
+ fsgs->n_hmm_eval = 0;
+ fsgs->n_sen_eval = 0;
+
+ return 0;
+}
+
+/*
+ * Cleanup at the end of each utterance.
+ */
+int
+fsg_search_finish(ps_search_t *search)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+ gnode_t *gn;
+ fsg_pnode_t *pnode;
+ int32 n_hist;
+
+ /* Deactivate all nodes in the current and next-frame active lists */
+ for (gn = fsgs->pnode_active; gn; gn = gnode_next(gn)) {
+ pnode = (fsg_pnode_t *) gnode_ptr(gn);
+ fsg_psubtree_pnode_deactivate(pnode);
+ }
+ for (gn = fsgs->pnode_active_next; gn; gn = gnode_next(gn)) {
+ pnode = (fsg_pnode_t *) gnode_ptr(gn);
+ fsg_psubtree_pnode_deactivate(pnode);
+ }
+
+ glist_free(fsgs->pnode_active);
+ fsgs->pnode_active = NULL;
+ glist_free(fsgs->pnode_active_next);
+ fsgs->pnode_active_next = NULL;
+
+ fsgs->final = TRUE;
+
+ n_hist = fsg_history_n_entries(fsgs->history);
+ E_INFO
+ ("%d frames, %d HMMs (%d/fr), %d senones (%d/fr), %d history entries (%d/fr)\n\n",
+ fsgs->frame, fsgs->n_hmm_eval,
+ (fsgs->frame > 0) ? fsgs->n_hmm_eval / fsgs->frame : 0,
+ fsgs->n_sen_eval,
+ (fsgs->frame > 0) ? fsgs->n_sen_eval / fsgs->frame : 0,
+ n_hist, (fsgs->frame > 0) ? n_hist / fsgs->frame : 0);
+
+ return 0;
+}
+
+static int
+fsg_search_find_exit(fsg_search_t *fsgs, int frame_idx, int final, int32 *out_score, int32* out_is_final)
+{
+ fsg_hist_entry_t *hist_entry = NULL;
+ fsg_model_t *fsg;
+ int bpidx, frm, last_frm, besthist;
+ int32 bestscore;
+
+ if (out_is_final)
+ *out_is_final = FALSE;
+
+ if (frame_idx == -1)
+ frame_idx = fsgs->frame - 1;
+ last_frm = frm = frame_idx;
+
+ /* Scan backwards to find a word exit in frame_idx. */
+ bpidx = fsg_history_n_entries(fsgs->history) - 1;
+ while (bpidx > 0) {
+ hist_entry = fsg_history_entry_get(fsgs->history, bpidx);
+ if (fsg_hist_entry_frame(hist_entry) <= frame_idx) {
+ frm = last_frm = fsg_hist_entry_frame(hist_entry);
+ break;
+ }
+ bpidx--;
+ }
+
+ /* No hypothesis (yet). */
+ if (bpidx <= 0)
+ return bpidx;
+
+ /* Now find best word exit in this frame. */
+ bestscore = INT_MIN;
+ besthist = -1;
+ fsg = fsgs->fsg;
+ while (frm == last_frm) {
+ fsg_link_t *fl;
+ int32 score;
+
+ fl = fsg_hist_entry_fsglink(hist_entry);
+ score = fsg_hist_entry_score(hist_entry);
+
+ if (fl == NULL)
+ break;
+
+ /* Prefer final hypothesis */
+ if (score == bestscore && fsg_link_to_state(fl) == fsg_model_final_state(fsg)) {
+ besthist = bpidx;
+ } else if (score BETTER_THAN bestscore) {
+ /* Only enforce the final state constraint if this is a final hypothesis. */
+ if ((!final)
+ || fsg_link_to_state(fl) == fsg_model_final_state(fsg)) {
+ bestscore = score;
+ besthist = bpidx;
+ }
+ }
+
+ --bpidx;
+ if (bpidx < 0)
+ break;
+ hist_entry = fsg_history_entry_get(fsgs->history, bpidx);
+ frm = fsg_hist_entry_frame(hist_entry);
+ }
+
+ /* Final state not reached. */
+ if (besthist == -1) {
+ E_ERROR("Final result does not match the grammar in frame %d\n", frame_idx);
+ return -1;
+ }
+
+ /* This here's the one we want. */
+ if (out_score)
+ *out_score = bestscore;
+ if (out_is_final) {
+ fsg_link_t *fl;
+ hist_entry = fsg_history_entry_get(fsgs->history, besthist);
+ fl = fsg_hist_entry_fsglink(hist_entry);
+ *out_is_final = (fsg_link_to_state(fl) == fsg_model_final_state(fsg));
+ }
+ return besthist;
+}
+
+/* FIXME: Mostly duplicated with ngram_search_bestpath(). */
+static ps_latlink_t *
+fsg_search_bestpath(ps_search_t *search, int32 *out_score, int backward)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+
+ if (search->last_link == NULL) {
+ search->last_link = ps_lattice_bestpath(search->dag, NULL,
+ 1.0, fsgs->ascale);
+ if (search->last_link == NULL)
+ return NULL;
+ /* Also calculate betas so we can fill in the posterior
+ * probability field in the segmentation. */
+ if (search->post == 0)
+ search->post = ps_lattice_posterior(search->dag, NULL, fsgs->ascale);
+ }
+ if (out_score)
+ *out_score = search->last_link->path_scr + search->dag->final_node_ascr;
+ return search->last_link;
+}
+
+char const *
+fsg_search_hyp(ps_search_t *search, int32 *out_score, int32 *out_is_final)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+ dict_t *dict = ps_search_dict(search);
+ char *c;
+ size_t len;
+ int bp, bpidx;
+
+ /* Get last backpointer table index. */
+ bpidx = fsg_search_find_exit(fsgs, fsgs->frame, fsgs->final, out_score, out_is_final);
+ /* No hypothesis (yet). */
+ if (bpidx <= 0) {
+ return NULL;
+ }
+
+ /* If bestpath is enabled and the utterance is complete, then run it. */
+ if (fsgs->bestpath && fsgs->final) {
+ ps_lattice_t *dag;
+ ps_latlink_t *link;
+
+ if ((dag = fsg_search_lattice(search)) == NULL) {
+ E_WARN("Failed to obtain the lattice while bestpath enabled\n");
+ return NULL;
+ }
+ if ((link = fsg_search_bestpath(search, out_score, FALSE)) == NULL) {
+ E_WARN("Failed to find the bestpath in a lattice\n");
+ return NULL;
+ }
+ return ps_lattice_hyp(dag, link);
+ }
+
+ bp = bpidx;
+ len = 0;
+ while (bp > 0) {
+ fsg_hist_entry_t *hist_entry = fsg_history_entry_get(fsgs->history, bp);
+ fsg_link_t *fl = fsg_hist_entry_fsglink(hist_entry);
+ char const *baseword;
+ int32 wid;
+
+ bp = fsg_hist_entry_pred(hist_entry);
+ wid = fsg_link_wid(fl);
+ if (wid < 0 || fsg_model_is_filler(fsgs->fsg, wid))
+ continue;
+ baseword = dict_basestr(dict,
+ dict_wordid(dict,
+ fsg_model_word_str(fsgs->fsg, wid)));
+ len += strlen(baseword) + 1;
+ }
+
+ ckd_free(search->hyp_str);
+ if (len == 0) {
+ search->hyp_str = NULL;
+ return search->hyp_str;
+ }
+ search->hyp_str = ckd_calloc(1, len);
+
+ bp = bpidx;
+ c = search->hyp_str + len - 1;
+ while (bp > 0) {
+ fsg_hist_entry_t *hist_entry = fsg_history_entry_get(fsgs->history, bp);
+ fsg_link_t *fl = fsg_hist_entry_fsglink(hist_entry);
+ char const *baseword;
+ int32 wid;
+
+ bp = fsg_hist_entry_pred(hist_entry);
+ wid = fsg_link_wid(fl);
+ if (wid < 0 || fsg_model_is_filler(fsgs->fsg, wid))
+ continue;
+ baseword = dict_basestr(dict,
+ dict_wordid(dict,
+ fsg_model_word_str(fsgs->fsg, wid)));
+ len = strlen(baseword);
+ c -= len;
+ memcpy(c, baseword, len);
+ if (c > search->hyp_str) {
+ --c;
+ *c = ' ';
+ }
+ }
+
+ return search->hyp_str;
+}
+
+static void
+fsg_seg_bp2itor(ps_seg_t *seg, fsg_hist_entry_t *hist_entry)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)seg->search;
+ fsg_hist_entry_t *ph = NULL;
+ int32 bp;
+
+ if ((bp = fsg_hist_entry_pred(hist_entry)) >= 0)
+ ph = fsg_history_entry_get(fsgs->history, bp);
+ seg->word = fsg_model_word_str(fsgs->fsg, hist_entry->fsglink->wid);
+ seg->ef = fsg_hist_entry_frame(hist_entry);
+ seg->sf = ph ? fsg_hist_entry_frame(ph) + 1 : 0;
+ /* This is kind of silly but it happens for null transitions. */
+ if (seg->sf > seg->ef) seg->sf = seg->ef;
+ seg->prob = 0; /* Bogus value... */
+ /* "Language model" score = transition probability. */
+ seg->lback = 1;
+ seg->lscr = fsg_link_logs2prob(hist_entry->fsglink) >> SENSCR_SHIFT;
+ if (ph) {
+ /* FIXME: Not sure exactly how cross-word triphones are handled. */
+ seg->ascr = hist_entry->score - ph->score - seg->lscr;
+ }
+ else
+ seg->ascr = hist_entry->score - seg->lscr;
+}
+
+
+static void
+fsg_seg_free(ps_seg_t *seg)
+{
+ fsg_seg_t *itor = (fsg_seg_t *)seg;
+ ckd_free(itor->hist);
+ ckd_free(itor);
+}
+
+static ps_seg_t *
+fsg_seg_next(ps_seg_t *seg)
+{
+ fsg_seg_t *itor = (fsg_seg_t *)seg;
+
+ if (++itor->cur == itor->n_hist) {
+ fsg_seg_free(seg);
+ return NULL;
+ }
+
+ fsg_seg_bp2itor(seg, itor->hist[itor->cur]);
+ return seg;
+}
+
+static ps_segfuncs_t fsg_segfuncs = {
+ /* seg_next */ fsg_seg_next,
+ /* seg_free */ fsg_seg_free
+};
+
+static ps_seg_t *
+fsg_search_seg_iter(ps_search_t *search, int32 *out_score)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+ fsg_seg_t *itor;
+ int bp, bpidx, cur;
+
+ bpidx = fsg_search_find_exit(fsgs, fsgs->frame, fsgs->final, out_score, NULL);
+ /* No hypothesis (yet). */
+ if (bpidx <= 0)
+ return NULL;
+
+ /* If bestpath is enabled and the utterance is complete, then run it. */
+ if (fsgs->bestpath && fsgs->final) {
+ ps_lattice_t *dag;
+ ps_latlink_t *link;
+
+ if ((dag = fsg_search_lattice(search)) == NULL)
+ return NULL;
+ if ((link = fsg_search_bestpath(search, out_score, TRUE)) == NULL)
+ return NULL;
+ return ps_lattice_seg_iter(dag, link, 1.0);
+ }
+
+ /* Calling this an "iterator" is a bit of a misnomer since we have
+ * to get the entire backtrace in order to produce it. On the
+ * other hand, all we actually need is the bptbl IDs, and we can
+ * allocate a fixed-size array of them. */
+ itor = ckd_calloc(1, sizeof(*itor));
+ itor->base.vt = &fsg_segfuncs;
+ itor->base.search = search;
+ itor->base.lwf = 1.0;
+ itor->n_hist = 0;
+ bp = bpidx;
+ while (bp > 0) {
+ fsg_hist_entry_t *hist_entry = fsg_history_entry_get(fsgs->history, bp);
+ bp = fsg_hist_entry_pred(hist_entry);
+ ++itor->n_hist;
+ }
+ if (itor->n_hist == 0) {
+ ckd_free(itor);
+ return NULL;
+ }
+ itor->hist = ckd_calloc(itor->n_hist, sizeof(*itor->hist));
+ cur = itor->n_hist - 1;
+ bp = bpidx;
+ while (bp > 0) {
+ fsg_hist_entry_t *hist_entry = fsg_history_entry_get(fsgs->history, bp);
+ itor->hist[cur] = hist_entry;
+ bp = fsg_hist_entry_pred(hist_entry);
+ --cur;
+ }
+
+ /* Fill in relevant fields for first element. */
+ fsg_seg_bp2itor((ps_seg_t *)itor, itor->hist[0]);
+
+ return (ps_seg_t *)itor;
+}
+
+static int
+fsg_search_prob(ps_search_t *search)
+{
+ fsg_search_t *fsgs = (fsg_search_t *)search;
+
+ /* If bestpath is enabled and the utterance is complete, then run it. */
+ if (fsgs->bestpath && fsgs->final) {
+ ps_lattice_t *dag;
+ ps_latlink_t *link;
+
+ if ((dag = fsg_search_lattice(search)) == NULL)
+ return 0;
+ if ((link = fsg_search_bestpath(search, NULL, TRUE)) == NULL)
+ return 0;
+ return search->post;
+ }
+ else {
+ /* FIXME: Give some kind of good estimate here, eventually. */
+ return 0;
+ }
+}
+
+static ps_latnode_t *
+find_node(ps_lattice_t *dag, fsg_model_t *fsg, int sf, int32 wid, int32 node_id)
+{
+ ps_latnode_t *node;
+
+ for (node = dag->nodes; node; node = node->next)
+ if ((node->sf == sf) && (node->wid == wid) && (node->node_id == node_id))
+ break;
+ return node;
+}
+
+static ps_latnode_t *
+new_node(ps_lattice_t *dag, fsg_model_t *fsg, int sf, int ef, int32 wid, int32 node_id, int32 ascr)
+{
+ ps_latnode_t *node;
+
+ node = find_node(dag, fsg, sf, wid, node_id);
+
+ if (node) {
+ /* Update end frames. */
+ if (node->lef == -1 || node->lef < ef)
+ node->lef = ef;
+ if (node->fef == -1 || node->fef > ef)
+ node->fef = ef;
+ /* Update best link score. */
+ if (ascr BETTER_THAN node->info.best_exit)
+ node->info.best_exit = ascr;
+ }
+ else {
+ /* New node; link to head of list */
+ node = listelem_malloc(dag->latnode_alloc);
+ node->wid = wid;
+ node->sf = sf;
+ node->fef = node->lef = ef;
+ node->reachable = FALSE;
+ node->entries = NULL;
+ node->exits = NULL;
+ node->info.best_exit = ascr;
+ node->node_id = node_id;
+
+ node->next = dag->nodes;
+ dag->nodes = node;
+ ++dag->n_nodes;
+ }
+
+ return node;
+}
+
+static ps_latnode_t *
+find_start_node(fsg_search_t *fsgs, ps_lattice_t *dag)
+{
+ ps_latnode_t *node;
+ glist_t start = NULL;
+ int nstart = 0;
+
+ /* Look for all nodes starting in frame zero with some exits. */
+ for (node = dag->nodes; node; node = node->next) {
+ if (node->sf == 0 && node->exits) {
+ E_INFO("Start node %s.%d:%d:%d\n",
+ fsg_model_word_str(fsgs->fsg, node->wid),
+ node->sf, node->fef, node->lef);
+ start = glist_add_ptr(start, node);
+ ++nstart;
+ }
+ }
+
+ /* If there was more than one start node candidate, then we need
+ * to create an artificial start node with epsilon transitions to
+ * all of them. */
+ if (nstart == 1) {
+ node = gnode_ptr(start);
+ }
+ else {
+ gnode_t *st;
+ int wid;
+
+ wid = fsg_model_word_add(fsgs->fsg, "<s>");
+ if (fsgs->fsg->silwords)
+ bitvec_set(fsgs->fsg->silwords, wid);
+ node = new_node(dag, fsgs->fsg, 0, 0, wid, -1, 0);
+ for (st = start; st; st = gnode_next(st))
+ ps_lattice_link(dag, node, gnode_ptr(st), 0, 0);
+ }
+ glist_free(start);
+ return node;
+}
+
+static ps_latnode_t *
+find_end_node(fsg_search_t *fsgs, ps_lattice_t *dag)
+{
+ ps_latnode_t *node;
+ glist_t end = NULL;
+ int nend = 0;
+
+ /* Look for all nodes ending in last frame with some entries. */
+ for (node = dag->nodes; node; node = node->next) {
+ if (node->lef == dag->n_frames - 1 && node->entries) {
+ E_INFO("End node %s.%d:%d:%d (%d)\n",
+ fsg_model_word_str(fsgs->fsg, node->wid),
+ node->sf, node->fef, node->lef, node->info.best_exit);
+ end = glist_add_ptr(end, node);
+ ++nend;
+ }
+ }
+
+ if (nend == 1) {
+ node = gnode_ptr(end);
+ }
+ else if (nend == 0) {
+ ps_latnode_t *last = NULL;
+ int ef = 0;
+
+ /* If there were no end node candidates, then just use the
+ * node with the last exit frame. */
+ for (node = dag->nodes; node; node = node->next) {
+ if (node->lef > ef && node->entries) {
+ last = node;
+ ef = node->lef;
+ }
+ }
+ node = last;
+ if (node)
+ E_INFO("End node %s.%d:%d:%d (%d)\n",
+ fsg_model_word_str(fsgs->fsg, node->wid),
+ node->sf, node->fef, node->lef, node->info.best_exit);
+ }
+ else {
+ /* If there was more than one end node candidate, then we need
+ * to create an artificial end node with epsilon transitions
+ * out of all of them. */
+ gnode_t *st;
+ int wid;
+ wid = fsg_model_word_add(fsgs->fsg, "</s>");
+ if (fsgs->fsg->silwords)
+ bitvec_set(fsgs->fsg->silwords, wid);
+ node = new_node(dag, fsgs->fsg, fsgs->frame, fsgs->frame, wid, -1, 0);
+ /* Use the "best" (in reality it will be the only) exit link
+ * score from this final node as the link score. */
+ for (st = end; st; st = gnode_next(st)) {
+ ps_latnode_t *src = gnode_ptr(st);
+ ps_lattice_link(dag, src, node, src->info.best_exit, fsgs->frame);
+ }
+ }
+ glist_free(end);
+ return node;
+}
+
+static void
+mark_reachable(ps_lattice_t *dag, ps_latnode_t *end)
+{
+ glist_t q;
+
+ /* It doesn't matter which order we do this in. */
+ end->reachable = TRUE;
+ q = glist_add_ptr(NULL, end);
+ while (q) {
+ ps_latnode_t *node = gnode_ptr(q);
+ latlink_list_t *x;
+
+ /* Pop the front of the list. */
+ q = gnode_free(q, NULL);
+ /* Expand all its predecessors that haven't been seen yet. */
+ for (x = node->entries; x; x = x->next) {
+ ps_latnode_t *next = x->link->from;
+ if (!next->reachable) {
+ next->reachable = TRUE;
+ q = glist_add_ptr(q, next);
+ }
+ }
+ }
+}
+
+/**
+ * Generate a lattice from FSG search results.
+ *
+ * One might think that this is simply a matter of adding acoustic
+ * scores to the FSG's edges. However, one would be wrong. The
+ * crucial difference here is that the word lattice is acyclic, and it
+ * also contains timing information.
+ */
+static ps_lattice_t *
+fsg_search_lattice(ps_search_t *search)
+{
+ fsg_search_t *fsgs;
+ fsg_model_t *fsg;
+ ps_latnode_t *node;
+ ps_lattice_t *dag;
+ int32 i, n;
+
+ fsgs = (fsg_search_t *)search;
+
+ /* Check to see if a lattice has previously been created over the
+ * same number of frames, and reuse it if so. */
+ if (search->dag && search->dag->n_frames == fsgs->frame)
+ return search->dag;
+
+ /* Nope, create a new one. */
+ ps_lattice_free(search->dag);
+ search->dag = NULL;
+ dag = ps_lattice_init_search(search, fsgs->frame);
+ fsg = fsgs->fsg;
+
+ /*
+ * Each history table entry represents a link in the word graph.
+ * The set of nodes is determined by the number of unique
+ * (word,start-frame) pairs in the history table. So we will
+ * first find all those nodes.
+ */
+ n = fsg_history_n_entries(fsgs->history);
+ for (i = 0; i < n; ++i) {
+ fsg_hist_entry_t *fh = fsg_history_entry_get(fsgs->history, i);
+ int32 ascr;
+ int sf;
+
+ /* Skip null transitions. */
+ if (fh->fsglink == NULL || fh->fsglink->wid == -1)
+ continue;
+
+ /* Find the start node of this link. */
+ if (fh->pred) {
+ fsg_hist_entry_t *pfh = fsg_history_entry_get(fsgs->history, fh->pred);
+ /* FIXME: We include the transition score in the lattice
+ * link score. This is because of the practical
+ * difficulty of obtaining it separately in bestpath or
+ * forward-backward search, and because it is essentially
+ * a unigram probability, so there is no need to treat it
+ * separately from the acoustic score. However, it's not
+ * clear that this will actually yield correct results.*/
+ ascr = fh->score - pfh->score;
+ sf = pfh->frame + 1;
+ }
+ else {
+ ascr = fh->score;
+ sf = 0;
+ }
+
+ /*
+ * Note that although scores are tied to links rather than
+ * nodes, it's possible that there are no links out of the
+ * destination node, and thus we need to preserve its score in
+ * case it turns out to be utterance-final.
+ */
+ new_node(dag, fsg, sf, fh->frame, fh->fsglink->wid, fsg_link_to_state(fh->fsglink), ascr);
+ }
+
+ /*
+ * Now, we will create links only to nodes that actually exist.
+ */
+ n = fsg_history_n_entries(fsgs->history);
+ for (i = 0; i < n; ++i) {
+ fsg_hist_entry_t *fh = fsg_history_entry_get(fsgs->history, i);
+ fsg_arciter_t *itor;
+ ps_latnode_t *src, *dest;
+ int32 ascr;
+ int sf;
+
+ /* Skip null transitions. */
+ if (fh->fsglink == NULL || fh->fsglink->wid == -1)
+ continue;
+
+ /* Find the start node of this link and calculate its link score. */
+ if (fh->pred) {
+ fsg_hist_entry_t *pfh = fsg_history_entry_get(fsgs->history, fh->pred);
+ sf = pfh->frame + 1;
+ ascr = fh->score - pfh->score;
+ }
+ else {
+ ascr = fh->score;
+ sf = 0;
+ }
+ src = find_node(dag, fsg, sf, fh->fsglink->wid, fsg_link_to_state(fh->fsglink));
+ sf = fh->frame + 1;
+
+ for (itor = fsg_model_arcs(fsg, fsg_link_to_state(fh->fsglink));
+ itor; itor = fsg_arciter_next(itor)) {
+ fsg_link_t *link = fsg_arciter_get(itor);
+
+ /* FIXME: Need to figure out what to do about tag transitions. */
+ if (link->wid >= 0) {
+ /*
+ * For each non-epsilon link following this one, look for a
+ * matching node in the lattice and link to it.
+ */
+ if ((dest = find_node(dag, fsg, sf, link->wid, fsg_link_to_state(link))) != NULL)
+ ps_lattice_link(dag, src, dest, ascr, fh->frame);
+ }
+ else {
+ /*
+ * Transitive closure on nulls has already been done, so we
+ * just need to look one link forward from them.
+ */
+ fsg_arciter_t *itor2;
+
+ /* Add all non-null links out of j. */
+ for (itor2 = fsg_model_arcs(fsg, fsg_link_to_state(link));
+ itor2; itor2 = fsg_arciter_next(itor2)) {
+ fsg_link_t *link = fsg_arciter_get(itor2);
+
+ if (link->wid == -1)
+ continue;
+
+ if ((dest = find_node(dag, fsg, sf, link->wid, fsg_link_to_state(link))) != NULL) {
+ ps_lattice_link(dag, src, dest, ascr, fh->frame);
+ }
+ }
+ }
+ }
+ }
+
+
+ /* Figure out which nodes are the start and end nodes. */
+ if ((dag->start = find_start_node(fsgs, dag)) == NULL) {
+ E_WARN("Failed to find the start node\n");
+ goto error_out;
+ }
+ if ((dag->end = find_end_node(fsgs, dag)) == NULL) {
+ E_WARN("Failed to find the end node\n");
+ goto error_out;
+ }
+
+
+ E_INFO("lattice start node %s.%d end node %s.%d\n",
+ fsg_model_word_str(fsg, dag->start->wid), dag->start->sf,
+ fsg_model_word_str(fsg, dag->end->wid), dag->end->sf);
+ /* FIXME: Need to calculate final_node_ascr here. */
+
+ /*
+ * Convert word IDs from FSG to dictionary.
+ */
+ for (node = dag->nodes; node; node = node->next) {
+ node->wid = dict_wordid(dag->search->dict,
+ fsg_model_word_str(fsg, node->wid));
+ node->basewid = dict_basewid(dag->search->dict, node->wid);
+ }
+
+ /*
+ * Now we are done, because the links in the graph are uniquely
+ * defined by the history table. However we should remove any
+ * nodes which are not reachable from the end node of the FSG.
+ * Everything is reachable from the start node by definition.
+ */
+ mark_reachable(dag, dag->end);
+
+ ps_lattice_delete_unreachable(dag);
+ {
+ int32 silpen, fillpen;
+
+ silpen = (int32)(logmath_log(fsg->lmath,
+ cmd_ln_float32_r(ps_search_config(fsgs), "-silprob"))
+ * fsg->lw)
+ >> SENSCR_SHIFT;
+ fillpen = (int32)(logmath_log(fsg->lmath,
+ cmd_ln_float32_r(ps_search_config(fsgs), "-fillprob"))
+ * fsg->lw)
+ >> SENSCR_SHIFT;
+
+ ps_lattice_penalize_fillers(dag, silpen, fillpen);
+ }
+ search->dag = dag;
+
+ return dag;
+
+
+error_out:
+ ps_lattice_free(dag);
+ return NULL;
+
+}
+