/// <summary>
        ///   Constructs a new potential function modeling Hidden Markov Models.
        /// </summary>
        ///
        /// <param name="states">The number of states.</param>
        /// <param name="symbols">The number of symbols.</param>
        /// <param name="outputClasses">The number of output classes.</param>
        ///
        public MarkovDiscreteFunction(int states, int symbols, int outputClasses)
        {
            this.Outputs = outputClasses;
            this.Symbols = symbols;

            int factorIndex    = 0;
            var factorParams   = new List <double>();
            var factorFeatures = new List <IFeature <int> >();

            this.Factors = new FactorPotential <int> [Outputs];

            int[] classOffset = new int[outputClasses];
            int[] edgeOffset  = new int[outputClasses];
            int[] stateOffset = new int[outputClasses];
            int[] classCount  = new int[outputClasses];
            int[] edgeCount   = new int[outputClasses];
            int[] stateCount  = new int[outputClasses];


            // Create features for initial class probabilities
            for (int c = 0; c < outputClasses; c++)
            {
                var stateParams   = new List <double>();
                var stateFeatures = new List <IFeature <int> >();

                var edgeParams   = new List <double>();
                var edgeFeatures = new List <IFeature <int> >();

                var classParams   = new List <double>();
                var classFeatures = new List <IFeature <int> >();


                // Create features for class labels
                classParams.Add(Math.Log(1.0 / outputClasses));
                classFeatures.Add(new OutputFeature <int>(this, c, c));

                // Create features for initial state probabilities
                for (int i = 0; i < states; i++)
                {
                    edgeParams.Add((i == 0) ? Math.Log(1.0) : Math.Log(0.0));
                    edgeFeatures.Add(new InitialFeature <int>(this, c, i));
                }

                // Create features for state transition probabilities
                for (int i = 0; i < states; i++)
                {
                    for (int j = 0; j < states; j++)
                    {
                        edgeParams.Add(Math.Log(1.0 / states));
                        edgeFeatures.Add(new TransitionFeature <int>(this, c, i, j));
                    }
                }

                // Create features for symbol emission probabilities
                for (int i = 0; i < states; i++)
                {
                    for (int k = 0; k < symbols; k++)
                    {
                        stateParams.Add(Math.Log(1.0 / symbols));
                        stateFeatures.Add(new EmissionFeature(this, c, i, k));
                    }
                }

                classOffset[c] = factorIndex;
                edgeOffset[c]  = factorIndex + classParams.Count;
                stateOffset[c] = factorIndex + classParams.Count + edgeParams.Count;

                classCount[c] = classParams.Count;
                edgeCount[c]  = edgeParams.Count;
                stateCount[c] = stateParams.Count;


                // 1. classes
                factorFeatures.AddRange(classFeatures);
                factorParams.AddRange(classParams);

                // 2. edges
                factorFeatures.AddRange(edgeFeatures);
                factorParams.AddRange(edgeParams);

                // 3. states
                factorFeatures.AddRange(stateFeatures);
                factorParams.AddRange(stateParams);

                factorIndex += classParams.Count + stateParams.Count + edgeParams.Count;
            }

            Accord.Diagnostics.Debug.Assert(factorIndex == factorParams.Count);
            Accord.Diagnostics.Debug.Assert(factorIndex == factorFeatures.Count);

            this.Weights  = factorParams.ToArray();
            this.Features = factorFeatures.ToArray();

            for (int c = 0; c < outputClasses; c++)
            {
                Factors[c] = new MarkovDiscreteFactor(this, states, c, symbols,
                                                      classIndex: classOffset[c], classCount: classCount[c],  // 1. classes
                                                      edgeIndex: edgeOffset[c], edgeCount: edgeCount[c],      // 2. edges
                                                      stateIndex: stateOffset[c], stateCount: stateCount[c]); // 3. states
            }
        }
        /// <summary>
        ///   Constructs a new potential function modeling Hidden Markov Models.
        /// </summary>
        ///
        /// <param name="classifier">The classifier model.</param>
        /// <param name="includePriors">True to include class features (priors), false otherwise.</param>
        ///
        public MarkovDiscreteFunction(HiddenMarkovClassifier <GeneralDiscreteDistribution, int> classifier, bool includePriors = true)
        {
            this.Symbols = classifier.Models[0].Emissions[0].Length;
            this.Outputs = classifier.Classes;

            int factorIndex    = 0;
            var factorParams   = new List <double>();
            var factorFeatures = new List <IFeature <int> >();

            this.Factors = new FactorPotential <int> [Outputs];

            int[] classOffset = new int[classifier.Classes];
            int[] edgeOffset  = new int[classifier.Classes];
            int[] stateOffset = new int[classifier.Classes];
            int[] classCount  = new int[classifier.Classes];
            int[] edgeCount   = new int[classifier.Classes];
            int[] stateCount  = new int[classifier.Classes];


            // Create features for initial class probabilities
            for (int c = 0; c < classifier.Classes; c++)
            {
                var stateParams   = new List <double>();
                var stateFeatures = new List <IFeature <int> >();

                var edgeParams   = new List <double>();
                var edgeFeatures = new List <IFeature <int> >();

                var classParams   = new List <double>();
                var classFeatures = new List <IFeature <int> >();

                var model = classifier[c];

                if (includePriors)
                {
                    // Create features for class labels
                    classParams.Add(Math.Log(classifier.Priors[c]));
                    classFeatures.Add(new OutputFeature <int>(this, c, c));
                }

                // Create features for initial state probabilities
                for (int i = 0; i < model.States; i++)
                {
                    edgeParams.Add(model.LogInitial[i]);
                    edgeFeatures.Add(new InitialFeature <int>(this, c, i));
                }

                // Create features for state transition probabilities
                for (int i = 0; i < model.States; i++)
                {
                    for (int j = 0; j < model.States; j++)
                    {
                        edgeParams.Add(model.LogTransitions[i][j]);
                        edgeFeatures.Add(new TransitionFeature <int>(this, c, i, j));
                    }
                }

                // Create features for symbol emission probabilities
                for (int i = 0; i < model.States; i++)
                {
                    for (int k = 0; k < model.Emissions[i].Length; k++)
                    {
                        stateParams.Add(model.Emissions[i][k]);
                        stateFeatures.Add(new EmissionFeature(this, c, i, k));
                    }
                }


                classOffset[c] = factorIndex;
                edgeOffset[c]  = factorIndex + classParams.Count;
                stateOffset[c] = factorIndex + classParams.Count + edgeParams.Count;

                classCount[c] = classParams.Count;
                edgeCount[c]  = edgeParams.Count;
                stateCount[c] = stateParams.Count;


                // 1. classes
                factorFeatures.AddRange(classFeatures);
                factorParams.AddRange(classParams);

                // 2. edges
                factorFeatures.AddRange(edgeFeatures);
                factorParams.AddRange(edgeParams);

                // 3. states
                factorFeatures.AddRange(stateFeatures);
                factorParams.AddRange(stateParams);

                factorIndex += classParams.Count + stateParams.Count + edgeParams.Count;
            }

            Accord.Diagnostics.Debug.Assert(factorIndex == factorParams.Count);
            Accord.Diagnostics.Debug.Assert(factorIndex == factorFeatures.Count);

            this.Weights  = factorParams.ToArray();
            this.Features = factorFeatures.ToArray();


            for (int c = 0; c < classifier.Models.Length; c++)
            {
                Factors[c] = new MarkovDiscreteFactor(this, classifier.Models[c].States, c, Symbols,
                                                      classIndex: classOffset[c], classCount: classCount[c],  // 1. classes
                                                      edgeIndex: edgeOffset[c], edgeCount: edgeCount[c],      // 2. edges
                                                      stateIndex: stateOffset[c], stateCount: stateCount[c]); // 3. states
            }
        }