このHMMがこんな出力をする確率は?

出力があるとして、HMMがその出力をする確率がどれだけあるか求めてみます。
前向きアルゴリズムといいますが、ほとんどビタビのアルゴリズムと同じプログラムになってます。
出力はこんな感じになりました。

出力:serrar rer te rr rrsertah reht
確率:0.00000000000000023598%


このアルゴリズムは、出力があるとき、複数のHMMのうちどれがもっともこの出力になる可能性が高いかを調べるときなどに使います。

import java.util.*;

public class HmmForward {
    public static void main(String[] args){
        String[] Q = {//状態
            "0", "1", "21", "22", "31", "32", "33"};
        double[][] A = {//状態遷移頻度
            {0,  1,   2,    0,    2,    0,    0},
            {0,  0,   1,    0,    1,    0,    0},
            {0,  0,   0,    1,    0,    0,    0},
            {2,  2,   3,    0,    3,    0,    0},
            {0,  0,   0,    0,    0,    1,    0},
            {0,  0,   0,    0,    0,    0,    1},
            {3,  1,   3,    0,    2,    0,    0}};
        String[] Σ = {//出力文字
            " ", "a", "e", "s", "t", "h", "r"};
        double[][] E = {//文字出力頻度
            {1,   0,   0,   0,   0,   0,   0},
            {0,   2,   0,   3,   3,   0,   0},
            {0,   0,   0,   2,   2,   1,   3},
            {0,   1,   2,   0,   0,   1,   1},
            {0,   0,   0,   2,   2,   1,   2},
            {0,   2,   3,   0,   0,   0,   0},
            {0,   0,   0,   0,   0,   1,   2}};
        //確率の正規化
        for(double[] d : A){
            double sum = 0;
            for(double v : d) sum += v;
            for(int i = 0; i  < d.length; ++i){
                d[i] /= sum;
            }
        }
        for(double[] d : E){
            double sum = 0;
            for(double v : d) sum += v;
            for(int i = 0; i  < d.length; ++i){
                d[i] /= sum;
            }            
        }
        
        //出力文字列
        String output = "serrar rer te rr rrsertah reht";
        System.out.println("出力:" + output);
        
        //文字列を分解
        List<Integer> outputindex = new ArrayList<Integer>();
        //出力文字の転置インデックス
        Map<String, Integer> reverse = new HashMap<String, Integer>();
        for(int i = 0; i < Σ.length; ++i){
            reverse.put(Σ[i], i);
        }
        for(int i = 0; i < output.length(); ++i){
            outputindex.add(reverse.get(output.substring(i, i + 1)));
        }

        //状態遷移の推定
        double[] probervility = new double[Q.length];//この状態に遷移する確率
        probervility[0] = 1;//初期状態に遷移する確率を100%にしておく
        //↑ここまでは状態遷移の出力がない以外、前のサンプルと同じ
        
        for(int s : outputindex){//各文字について
            double[] nextprob = new double[Q.length];
            for(int i = 0; i < Q.length; ++i){//現在状態について
                //この状態までの遷移確率
                double pi = probervility[i];
                for(int j = 0; j < Q.length; ++j){//次の状態について
                    //状態S_iから状態S_jへの遷移確率
                    double pj = A[i][j] * pi;
                    //状態S_jでの文字sの出力確率
                    double ps = pj * E[j][s];
                    
                    nextprob[j] += ps;
                }
            }
            probervility = nextprob;
        }
        double result = 0;
        for(double d : probervility){
            result += d;
        }
        //結果出力
        System.out.printf("確率:%.20f%%", result * 100);
    }
}