@Override
  protected void computeBeta(final HiddenMarkovModel hmm, final MLDataSet oseq) {
    this.beta = new double[oseq.size()][hmm.getStateCount()];

    for (int i = 0; i < hmm.getStateCount(); i++) {
      this.beta[oseq.size() - 1][i] = 1. / this.ctFactors[oseq.size() - 1];
    }

    for (int t = oseq.size() - 2; t >= 0; t--) {
      for (int i = 0; i < hmm.getStateCount(); i++) {
        computeBetaStep(hmm, oseq.get(t + 1), t, i);
        this.beta[t][i] /= this.ctFactors[t];
      }
    }
  }
  static HiddenMarkovModel buildDiscHMM() {
    HiddenMarkovModel hmm = new HiddenMarkovModel(2, 2);

    hmm.setPi(0, 0.95);
    hmm.setPi(1, 0.05);

    hmm.setStateDistribution(0, new DiscreteDistribution(new double[][] {{0.95, 0.05}}));
    hmm.setStateDistribution(1, new DiscreteDistribution(new double[][] {{0.20, 0.80}}));

    hmm.setTransitionProbability(0, 1, 0.05);
    hmm.setTransitionProbability(0, 0, 0.95);
    hmm.setTransitionProbability(1, 0, 0.10);
    hmm.setTransitionProbability(1, 1, 0.90);

    return hmm;
  }
  private void computeStep(
      final HiddenMarkovModel hmm, final MLDataPair o, final int t, final int j) {
    double minDelta = Double.MAX_VALUE;
    int min_psy = 0;

    for (int i = 0; i < hmm.getStateCount(); i++) {
      final double thisDelta = this.delta[t - 1][i] - Math.log(hmm.getTransitionProbability(i, j));

      if (minDelta > thisDelta) {
        minDelta = thisDelta;
        min_psy = i;
      }
    }

    this.delta[t][j] = minDelta - Math.log(hmm.getStateDistribution(j).probability(o));
    this.psy[t][j] = min_psy;
  }
  @Override
  protected void computeAlpha(final HiddenMarkovModel hmm, final MLDataSet oseq) {
    this.alpha = new double[oseq.size()][hmm.getStateCount()];

    for (int i = 0; i < hmm.getStateCount(); i++) {
      computeAlphaInit(hmm, oseq.get(0), i);
    }
    scale(this.ctFactors, this.alpha, 0);

    final Iterator<MLDataPair> seqIterator = oseq.iterator();
    if (seqIterator.hasNext()) {
      seqIterator.next();
    }

    for (int t = 1; t < oseq.size(); t++) {
      final MLDataPair observation = seqIterator.next();

      for (int i = 0; i < hmm.getStateCount(); i++) {
        computeAlphaStep(hmm, observation, t, i);
      }
      scale(this.ctFactors, this.alpha, t);
    }
  }
  public ViterbiCalculator(final MLDataSet oseq, final HiddenMarkovModel hmm) {
    if (oseq.size() < 1) {
      throw new IllegalArgumentException("Must not have empty sequence");
    }

    this.delta = new double[oseq.size()][hmm.getStateCount()];
    this.psy = new int[oseq.size()][hmm.getStateCount()];
    this.stateSequence = new int[oseq.size()];

    for (int i = 0; i < hmm.getStateCount(); i++) {
      this.delta[0][i] =
          -Math.log(hmm.getPi(i)) - Math.log(hmm.getStateDistribution(i).probability(oseq.get(0)));
      this.psy[0][i] = 0;
    }

    final Iterator<MLDataPair> oseqIterator = oseq.iterator();
    if (oseqIterator.hasNext()) {
      oseqIterator.next();
    }

    int t = 1;
    while (oseqIterator.hasNext()) {
      final MLDataPair observation = oseqIterator.next();

      for (int i = 0; i < hmm.getStateCount(); i++) {
        computeStep(hmm, observation, t, i);
      }

      t++;
    }

    this.lnProbability = Double.MAX_VALUE;
    for (int i = 0; i < hmm.getStateCount(); i++) {
      final double thisProbability = this.delta[oseq.size() - 1][i];

      if (this.lnProbability > thisProbability) {
        this.lnProbability = thisProbability;
        this.stateSequence[oseq.size() - 1] = i;
      }
    }
    this.lnProbability = -this.lnProbability;

    for (int t2 = oseq.size() - 2; t2 >= 0; t2--) {
      this.stateSequence[t2] = this.psy[t2 + 1][this.stateSequence[t2 + 1]];
    }
  }