boolean train(int[][] trainingData) {
        //Clear any previous models
        modelTrained = false;
        observationSequence = null;
        estimatedStates = null;
        trainingIterationLog = null;

        int n, bestIndex = 0;
        double bestLogValue = 0;

//        currentIter = 0;
//        newLoglikelihood = 0;
        if (numRandomTrainingIterations > 1) {

            //A buffer to keep track each AB matrix
            ArrayList< MatrixDouble> aTracker = new ArrayList<MatrixDouble>(numRandomTrainingIterations);
            ArrayList< MatrixDouble> bTracker = new ArrayList<MatrixDouble>(numRandomTrainingIterations);
            double[] loglikelihoodTracker = new double[numRandomTrainingIterations];

            int maxNumTestIter = maxNumIter > 10 ? 10 : maxNumIter;

            //Try and find the best starting point
            for (n = 0; n < numRandomTrainingIterations; n++) {
                //Reset the model to a new random starting values
                randomizeMatrices(numStates, numSymbols);

                if (!train_(trainingData, maxNumTestIter)) {
                    return false;
                }

                aTracker.add(n, a);
                bTracker.add(n, b);
                loglikelihoodTracker[n] = newLoglikelihood;
            }

            //Get the best result and set it as the a and b starting values
            bestIndex = 0;
            bestLogValue = loglikelihoodTracker[0];
            for (n = 1; n < numRandomTrainingIterations; n++) {
                if (bestLogValue < loglikelihoodTracker[n]) {
                    bestLogValue = loglikelihoodTracker[n];
                    bestIndex = n;
                }
            }

            //Set a and b
            a = aTracker.get(bestIndex);
            b = bTracker.get(bestIndex);

        } else {
            randomizeMatrices(numStates, numSymbols);
        }

        //Perform the actual training
        if (!train_(trainingData, maxNumIter)) {
            return false;
        }
        //Calculate the observationSequence buffer length

        final int numObs = trainingData.length;
        int k = 0;
        int averageObsLength = 0;
        for (k = 0; k < numObs; k++) {
            final int T = trainingData[k].length;
            averageObsLength += T;
        }

        averageObsLength = (int) Math.floor(averageObsLength / (double) numObs);
        observationSequence = new int[averageObsLength];
        estimatedStates = new int[averageObsLength];

        //Finally, flag that the model was trained
        modelTrained = true;
        return true;
    }