/* Copyright (c) 1991-2002 Doshita Lab. Speech Group, Kyoto University */
/* Copyright (c) 2000-2002 Speech and Acoustics Processing Lab., NAIST */
/*   All rights reserved   */

/* check_hmm_restriction.c --- check if Julius can handle the hmmdef */

/* $Id: check_hmm_restriction.c,v 1.4 2002/09/12 01:38:49 ri Exp $ */

#include <sent/stddefs.h>
#include <sent/htk_hmm.h>
#include <sent/htk_param.h>
#include <sent/hmm.h>

#define GOOD 0
#define FIXED 1
#define BAD 3

/*
 *
 *  TRANSITION RESTRICTIONS:
 *
 * for HTK and Julius:
 *   no arc to initial state
 *   no arc from final state
 * 
 * for Julius only:
 *   allow only one arc from initial state
 *   allow only one arc to final state
 *   (internal skip/loop is allowed)
 *
 */

/* transition allowance matrix */
/* G=good,F=fixed,B=bad */
/*  |G|F|B */
/* -+-+-+- */
/* G|G|F|B */
/* F|F|F|B */
/* B|B|B|B */

/* When unacceptable transition is found,
   Julius output only warning and proceed by modifying transition to
   suite for the restriction.
*/

/* transition probability is not loganized yet */

/* check transition of t */
static int
trans_ok_p(t)
     HTK_HMM_Trans *t;
{
  int i, j;
  int tflag = BAD;
  int retflag = BAD;
  PROB maxprob;
  int maxid = -1;
  
  /* allow only one arc from initial state */
  tflag = BAD;
  for (i=0;i<t->statenum;i++) {
    if (t->a[0][i] != (PROB)0.0) {
      if (tflag == BAD) {
	tflag = GOOD;
      } else {			/* 2nd time */
	j_printerr("Warning: initial state has more than one arc\n");
	tflag = BAD;
	break;
      }
    }
  }
  if (tflag == BAD) {		/* unacceptable transition found */
    if (i >= t->statenum) {	/* no arc */
      j_printerr("Error: initial state has no arc\n");
    } else {
      /* modify the transition: gather them to an arc with best probability */
      maxprob = 0.0; maxid = -1;
      for (j=0;j<t->statenum;j++) {
	if (maxprob < t->a[0][j]) {
	  maxprob = t->a[0][j];
	  maxid = j;
	}
      }
      if (maxid == -1) {
	j_error("Error: trans_ok_p: no transition in a state?\n");
      }
      t->a[0][maxid] = 1.0;
      for (j=0;j<t->statenum;j++) {
	if (j == maxid) continue;
	t->a[0][j] = 0.0;
      }
      tflag = FIXED;
    }
  }

  retflag = tflag;
  
  /* allow only one arc to final state */
  tflag = BAD;
  for (i=0;i<t->statenum;i++) {
    if (t->a[i][t->statenum-1] != (PROB)0.0) {
      if (tflag == BAD) {
	tflag = GOOD;
      } else {			/* 2nd time */
	j_printerr("Warning: more than one arc to end state\n");
	tflag = BAD;
	break;
      }
    }
  }
  if (tflag == BAD) {
    if (i >= t->statenum) {	/* no arc */
      j_printerr("Error: no arc to end state\n");
    } else {
      /* modify the transition: gather them to an arc with best probability */
      maxprob = (PROB)0.0;
      for (j=0;j<t->statenum;j++) {
	if (maxprob < t->a[j][t->statenum-1]) {
	  maxprob = t->a[j][t->statenum-1];
	  maxid = j;
	}
      }
      for (i=0;i<t->statenum;i++) {
	if (t->a[i][t->statenum-1] == (PROB)0.0) continue;
	if (i == maxid) continue;
	for (j=t->statenum-2;j>=0;j--) {
	  if (t->a[i][j] != (PROB)0.0) {
	    t->a[i][j] += t->a[i][t->statenum-1];
	    t->a[i][t->statenum-1] = (PROB)0.0;
	    break;
	  }
	}
      }
      tflag = FIXED;
    }
  }
    
  return(retflag | tflag);
}

/* check HMM limitation (call trans_ok_p() above) */
boolean
check_hmm_limit(HTK_HMM_Data *dt)
{
  boolean return_flag = TRUE;
  int tflag;

  tflag = trans_ok_p(dt->tr);
  if (tflag == BAD) {
    return_flag = FALSE;
    j_printerr("Limit: HMM \"%s\" has unsupported arc.\n", dt->name);
    put_htk_trans(dt->tr);
  } else if (tflag == FIXED) {
    j_printerr("Warning: HMM \"%s\" has unsupported arc.\n", dt->name);
    j_printerr("SERIOUS WARNING: Transition arc has been modified as below\n");
    j_printerr("SERIOUS WARNING: This may cause unintended recognition result\n");
    put_htk_trans(dt->tr);
  }
#if 0
  if (dt->state_num <= 3) {
    return_flag = FALSE;
    j_printerr("Limit: HMM \"%s\" too short (only %d states)\n", dt->name,
	    dt->state_num);
  }
#endif
  return(return_flag);
}

/* check all defined HMM */
boolean
check_all_hmm_limit(HTK_HMM_INFO *hmminfo)
{
  HTK_HMM_Data *dt;
  boolean return_flag = TRUE;

  for (dt = hmminfo->start; dt; dt = dt->next) {
    if (check_hmm_limit(dt) == FALSE) {
      return_flag = FALSE;
    }
  }
  return(return_flag);
}

