content: /*
   *
   *
   *    Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
   *    See the NOTICE file distributed with this work for additional information regarding copyright ownership.
   *    The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use
   *    this file except in compliance with the License.  You may obtain a copy of the License at
   *
   *            http://www.apache.org/licenses/LICENSE-2.0
   *
   *    Unless required by applicable law or agreed to in writing, software distributed under the License is
   *    distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *    See the License for the specific language governing permissions and limitations under the License.
   *
   *
   */
  
  package eu.amidst.dynamic.inference;
  
  import eu.amidst.core.distribution.*;
  import eu.amidst.core.inference.ImportanceSamplingRobust;
  import eu.amidst.core.inference.InferenceAlgorithm;
  import eu.amidst.core.inference.messagepassing.VMP;
  import eu.amidst.core.models.BayesianNetwork;
  import eu.amidst.core.models.DAG;
  import eu.amidst.core.utils.MultinomialIndex;
  import eu.amidst.core.utils.Serialization;
  import eu.amidst.core.variables.*;
  import eu.amidst.dynamic.models.DynamicBayesianNetwork;
  import eu.amidst.dynamic.models.DynamicDAG;
  import eu.amidst.dynamic.utils.DynamicBayesianNetworkGenerator;
  import eu.amidst.dynamic.utils.DynamicToStaticBNConverter;
  import eu.amidst.dynamic.variables.DynamicAssignment;
  import eu.amidst.dynamic.variables.DynamicVariables;
  import org.apache.commons.lang3.StringUtils;
  
  import java.io.IOException;
  import java.util.*;
  import java.util.stream.Collectors;
  import java.util.stream.IntStream;
  
  /**
   * This class implements the MAP Inference algorithm for {@link DynamicBayesianNetwork} models.
   */
  public class DynamicMAPInference implements InferenceAlgorithmForDBN {
  
      /**
       * Represents the search algorithm to be used.
       */
      public enum SearchAlgorithm {
          VMP, IS
      }
  
      /** Represents the {@link DynamicBayesianNetwork} model. */
      private DynamicBayesianNetwork model;
  
      /** Represents a {@link BayesianNetwork} object. */
      private BayesianNetwork unfoldedStaticModel;
  
      /** Represents a {@link BayesianNetwork} object. */
      private List<BayesianNetwork> mergedClassVarModels;
  
      /** Represents the number of time steps. */
      private int nTimeSteps = 2;
  
      /** Represents the number of consecutive class variables to merge */
      private int nMergedClassVars = 2;
  
      /** Represents the sample size when using IS. */
      private int sampleSize = 1000;
  
      /** Represents the MAP {@link Variable}. */
      private Variable MAPvariable;
  
      /** Represents the name of the MAP {@link Variable}. */
      private String MAPvarName;
  
      /** Represents a dynamic evidence defined as a list of {@link DynamicAssignment} objects. */
      private List<DynamicAssignment> evidence;
  
      /** Represents a static evidence defined as an {@link Assignment} object. */
      private Assignment staticEvidence;
  
      /** Represents the processing mode, either parallel (i.e., true) or not. */
      private boolean parallelMode = true;
  
      /** Represents the initial seed. */
      private int seed = 125123;
  
      /** Represents the MAP sequence defined as an {@link Assignment} object. */
      private Assignment MAPestimate;
  
      /** Represents the MAP sequence defined as an array of integers. */
      private int[] MAPsequence;
  
      /** Represents the Log Probability of the MAP estimate. */
      private double MAPestimateLogProbability;
  
      String groupedClassName = "__GROUPED_CLASS__";
  
      private List<int[]> bestSequenceEachModel;
  
      List<List<UnivariateDistribution>> allGroupedPosteriorDistributions;
      List<UnivariateDistribution> allUngroupedPosteriorDistributions;
  
      /**
       * {@inheritDoc}
       */
      @Override
      public void addDynamicEvidence(DynamicAssignment assignment) {
          throw new UnsupportedOperationException("Operation not supported in Dynamic MAP Inference");
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public void reset() {
  
          seed=125123;
          parallelMode = true;
          sampleSize=1000;
          nMergedClassVars = 2;
          nTimeSteps = 2;
  
          model=null;
          unfoldedStaticModel =null;
          mergedClassVarModels=null;
  
          evidence=null;
          MAPvarName=null;
          MAPvariable=null;
          MAPestimate=null;
          MAPsequence=null;
          MAPestimateLogProbability=Double.NaN;
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public <E extends UnivariateDistribution> E getFilteredPosterior(Variable var) {
          throw new UnsupportedOperationException("Operation not supported in Dynamic MAP Inference");
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public <E extends UnivariateDistribution> E getPredictivePosterior(Variable var, int nTimesAhead) {
          throw new UnsupportedOperationException("Operation not supported in Dynamic MAP Inference");
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public long getTimeIDOfLastEvidence() {
          return (long)this.evidence.stream().mapToDouble(DynamicAssignment::getTimeID).max().getAsDouble();
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public long getTimeIDOfPosterior() {
          throw new UnsupportedOperationException("Operation not supported in Dynamic MAP Inference");
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public DynamicBayesianNetwork getOriginalModel() {
          return this.model;
      }
  
      /**
       * Sets the evidence for this DynamicMAPInference.
       * @param evidence a list of {@link DynamicAssignment} objects.
       */
      public void setEvidence(List<DynamicAssignment> evidence) {
  
          if(evidence==null) {
              this.evidence = null;
              this.staticEvidence = null;
              return;
          }
          long sequenceID = evidence.get(0).getSequenceID();
          for(DynamicAssignment dynAssig : evidence) {
              if (dynAssig.getSequenceID()!=sequenceID) {
                  System.out.println("Error: Different sequence IDs in the evidence");
                  System.exit(-15);
              }
              if (dynAssig.getTimeID()>=nTimeSteps || dynAssig.getTimeID()<0) {
                  System.out.println("Error: Evidence time ID out of the range");
                  System.exit(-20);
              }
              if (!Double.isNaN(dynAssig.getValue(MAPvariable))) {
                  System.out.println("Error: MAP variable should not be in the evidence");
                  System.exit(-30);
              }
          }
          this.evidence = evidence;
  
          if (unfoldedStaticModel!=null) {
              staticEvidence = new HashMapAssignment(unfoldedStaticModel.getNumberOfVars());
  
              evidence.stream().forEach(dynamicAssignment -> {
                  int time = (int) dynamicAssignment.getTimeID();
                  Set<Variable> dynAssigVariables = dynamicAssignment.getVariables();
                  for (Variable dynVariable : dynAssigVariables) {
                      Variable staticVariable = unfoldedStaticModel.getVariables().getVariableByName(dynVariable.getName() + "_t" + Integer.toString(time));
                      double varValue = dynamicAssignment.getValue(dynVariable);
                      staticEvidence.setValue(staticVariable, varValue);
                  }
  
              });
          }
      }
  
  //    public void setEvidence(DataStream<DynamicDataInstance> evidence1) {
  //
  //        DynamicDataInstance currentDataInstance = evidence1.stream().findFirst().get();
  //        long sequenceID = currentDataInstance.getSequenceID();
  //
  //        List<DynamicAssignment> evidence2 = new ArrayList<>(1);
  //        for(DynamicDataInstance instance : evidence1) {
  //            if(instance.getSequenceID()!=sequenceID) {
  //                continue;
  //            }
  //            instance.setValue(this.MAPvariable, Utils.missingValue());
  //
  //            DynamicAssignment dynamicAssignment = new HashMapDynamicAssignment(this.model.getNumberOfVars());
  //            this.model.getDynamicVariables().getListOfDynamicVariables().stream().filter(var -> !(var.getName()==this.MAPvarName)).forEach(var -> {
  //                dynamicAssignment.setValue(var,instance.getValue(var));
  //            });
  //            evidence2.add(dynamicAssignment);
  //        }
  //
  //        //evidence2.sort( (l1,l2) -> (l1.getTimeID()<l2.getTimeID() ? 1 : -1) ) ;
  //
  ////        evidence2.forEach(evid -> {
  ////            System.out.println(evid.outputString(this.model.getDynamicVariables().getListOfDynamicVariables()));
  //////            System.out.println(evid.getSequenceID());
  //////            System.out.println(evid.getTimeID());
  ////        });
  //
  //
  //        this.evidence = evidence2;
  //
  ////        if (staticEvenModel!=null) {
  ////            staticEvidence = new HashMapAssignment(staticEvenModel.getNumberOfVars());
  ////
  ////            evidence.stream().forEach(dynamicAssignment -> {
  ////                int time = (int) dynamicAssignment.getTimeID();
  ////                Set<Variable> dynAssigVariables = dynamicAssignment.getVariables();
  ////                for (Variable dynVariable : dynAssigVariables) {
  ////                    Variable staticVariable = staticEvenModel.getVariables().getVariableByName(dynVariable.getName() + "_t" + Integer.toString(time));
  ////                    double varValue = dynamicAssignment.getValue(dynVariable);
  ////                    staticEvidence.setValue(staticVariable, varValue);
  ////                }
  ////
  ////            });
  ////        }
  //    }
  
      public List<List<UnivariateDistribution>> getGroupedPosteriorDistributions() {
          return allGroupedPosteriorDistributions;
      }
  
      public List<UnivariateDistribution> getUngroupedPosteriorDistributions() {
          return allUngroupedPosteriorDistributions;
      }
  
      /**
       * Sets the model for this DynamicMAPInference.
       * @param model a {@link DynamicBayesianNetwork} object.
       */
      public void setModel(DynamicBayesianNetwork model) {
          this.model = model;
          this.unfoldedStaticModel = DynamicToStaticBNConverter.convertDBNtoBN(model,nTimeSteps);
      }
  
      /**
       * Sets the parallel mode for this DynamicMAPInference.
       * @param parallelMode true if the parallel mode is activated, false otherwise.
       */
      public void setParallelMode(boolean parallelMode) {
          this.parallelMode = parallelMode;
      }
  
      /**
       * Sets the MAP variable for this DynamicMAPInference.
       * @param MAPvariable a valid {@link Variable} object.
       */
      public void setMAPvariable(Variable MAPvariable) {
  
          boolean parents0 = model.getDynamicDAG().getParentSetTime0(MAPvariable).getNumberOfParents()>0;
          boolean parentsT = model.getDynamicDAG().getParentSetTimeT(MAPvariable).getParents().stream().anyMatch(parent -> !parent.isInterfaceVariable());
  
          if (parents0 || parentsT) {
              System.out.println("Error: The dynamic MAP variable must not have parents");
              System.exit(-5);
          }
  
          if (!MAPvariable.isMultinomial()) {
              System.out.println("Error: The dynamic MAP variable must be multinomial");
              System.exit(-10);
          }
          this.MAPvariable = MAPvariable;
          this.MAPvarName = MAPvariable.getName();
  
      }
  
      /**
       * Sets the number of time steps.
       * @param ntimeSteps an {@code int} that represents the number of time steps.
       */
      public void setNumberOfTimeSteps(int ntimeSteps) {
          if(ntimeSteps<2) {
              System.out.println("Error: The number of time steps should be at least 2");
              System.exit(-12);
          }
          nTimeSteps = ntimeSteps;
  
          this.setModel(this.model);
  
      }
  
      /**
       * Sets the number of consecutive copies of the class variable to merge.
       * Should be less than or equal to the number of time steps.
       * @param nMergedClassVars an {@code int} that represents the number of class variable copies to merge.
       */
      public void setNumberOfMergedClassVars(int nMergedClassVars) {
          if (nMergedClassVars > nTimeSteps) {
              System.out.println("Error: The number of merged class variables should be less or equal than the number of time steps");
              System.exit(-13);
          }
          if (nMergedClassVars<2 || nMergedClassVars > 10) {
              System.out.println("Error: The number of merged class variables should be between 2 and 10");
              System.exit(-14);
          }
          this.nMergedClassVars = nMergedClassVars;
      }
  
      /**
       * Sets the size of the sample when using Importance Sampling for inference.
       * @param sampleSize an {@code int} that represents the size of the sample.
       */
      public void setSampleSize(int sampleSize) {
          this.sampleSize = sampleSize;
      }
  
      /**
       * Sets the seed for repeatability in simulations.
       * @param seed an {@code int} that represents the seed.
       */
      public void setSeed(int seed) {
          this.seed = seed;
      }
  
      /**
       * Returns the MAP sequence found as an {@link Assignment} of variables
       * @return an {@link Assignment} object with the MAP sequence
       */
      public Assignment getMAPestimate() {
          return MAPestimate;
      }
  
      /**
       * Returns the MAP sequence found as array of integers
       * @return an array of integers representing the MAP sequence
       */
      public int[] getMAPsequence() {
          return MAPsequence;
      }
  
      public double getMAPestimateLogProbability() {
          return MAPestimateLogProbability;
      }
  
      public double getMAPestimateProbability() {
          return Math.exp(MAPestimateLogProbability);
      }
  
      /**
       * Returns the list of models with merged class variable.
       * @return a List of {@link BayesianNetwork} objects.
       */
      public List<BayesianNetwork> getMergedClassVarModels() {
          return mergedClassVarModels;
      }
  
      /**
       * Returns a single {@link BayesianNetwork} object, corresponding to the unfolded dynamic network
       * over the specified number of time steps.
       * @return a {@link BayesianNetwork} object.
       */
      public BayesianNetwork getUnfoldedStaticModel() {
          return unfoldedStaticModel;
      }
  
      public Assignment getUnfoldedEvidence() {
          return staticEvidence;
      }
  
      /**
       * Returns replicated MAP variables.
       * @return a {@code List} of {@link Variable}.
       */
      public List<Variable> getReplicatedMAPVariables() {
          if (MAPestimate==null) {
              return null;
          }
          return getMAPestimate().getVariables().stream().sorted((var1,var2) -> (var1.getVarID()>var2.getVarID() ? 1 : -1)).collect(Collectors.toList());
      }
  
      public List<int[]> getBestSequencesForEachSubmodel() {
          return bestSequenceEachModel;
      }
  
      /**
       * Computes Dynamic MAP for the even static model.
       */
      public void computeMergedClassVarModels() {
  
          DynamicDAG dynamicDAG = model.getDynamicDAG();
          DynamicVariables dynamicVariables = model.getDynamicVariables();
  
          mergedClassVarModels = new ArrayList<>(nMergedClassVars);
  
          IntStream.range(0,nMergedClassVars).forEachOrdered(modelNumber -> {
  
  //            System.out.println("Model number " + modelNumber);
              Variables variables = obtainReplicatedStaticVariables(dynamicVariables, modelNumber);
  
              DAG dag = obtainStaticDAG(dynamicDAG,variables,modelNumber);
  //            System.out.println(dag.toString());
  
              BayesianNetwork bn = obtainStaticMergedClassVarNetwork(dag,variables,modelNumber);
  //            System.out.println(bn.toString());
  
  
  //            int replicationsMAPVariable = (modelNumber==0 ? 0 : 1) + (nTimeSteps-modelNumber)/nMergedClassVars + ((nTimeSteps-modelNumber)%nMergedClassVars==0 ? 0 : 1);
  
  //            IntStream.range(0,replicationsMAPVariable).forEach(i-> {
  //                System.out.println("Variable " + groupedClassName + "_t" + i);
  //                System.out.println(bn.getConditionalDistribution(bn.getVariables().getVariableByName(groupedClassName + "_t" + i)).toString());
  //                System.out.println();
  //            });
  
  //            System.out.println(bn.getConditionalDistribution(bn.getVariables().getVariableByName(groupedClassName + "_t1")).toString());
  //            System.out.println();
  //            System.out.println(bn.getConditionalDistribution(bn.getVariables().getVariableByName(groupedClassName + "_t2")).toString());
  //            System.out.println();
  //            System.out.println(bn.getConditionalDistribution(bn.getVariables().getVariableByName(groupedClassName + "_t3")).toString());
  //            System.out.println();
              mergedClassVarModels.add(bn);
  //            System.out.println("MODEL " + modelNumber);
  //            System.out.println(bn);
          });
      }
  
  
      /**
       * Runs the inference algorithm.
       */
      public void runInference() {
  
          if (MAPvariable == null || MAPvarName == null) {
              System.out.println("Error: The MAP variable has not been set");
              System.exit(-30);
          }
  
  
          if (this.mergedClassVarModels == null) {
              this.computeMergedClassVarModels();
          }
  //        if (this.staticEvenModel == null) {
  //            this.computeDynamicMAPEvenModel();
  //        }
  //
  //        if (evidence!=null && staticEvidence==null) {
  //
  //            staticEvidence = new HashMapAssignment(staticEvenModel.getNumberOfVars());
  //
  //            evidence.stream().forEach(dynamicAssignment -> {
  //                int time = (int) dynamicAssignment.getTimeID();
  //                Set<Variable> dynAssigVariables = dynamicAssignment.getVariables();
  //                for (Variable dynVariable : dynAssigVariables) {
  //                    Variable staticVariable = staticEvenModel.getVariables().getVariableByName(dynVariable.getName() + "_t" + Integer.toString(time));
  //                    double varValue = dynamicAssignment.getValue(dynVariable);
  //                    staticEvidence.setValue(staticVariable, varValue);
  //                }
  //
  //            });
  //        }
          this.runInference(SearchAlgorithm.VMP);
      }
  
      /**
       * Runs the inference given an input search algorithm.
       * @param searchAlgorithm a valid {@link SearchAlgorithm} value.
       */
      public void runInference(SearchAlgorithm searchAlgorithm) {
  
          if (MAPvariable == null || MAPvarName == null) {
              System.out.println("Error: The MAP variable has not been set");
              System.exit(-30);
          }
  
          if (this.mergedClassVarModels == null) {
              this.computeMergedClassVarModels();
          }
  
          if (this.unfoldedStaticModel == null) {
              unfoldedStaticModel = DynamicToStaticBNConverter.convertDBNtoBN(model,nTimeSteps);
          }
  
  //
  //        if (evidence!=null && staticEvidence==null) {
  //
  //            staticEvidence = new HashMapAssignment(staticEvenModel.getNumberOfVars());
  //
  //            evidence.stream().forEach(dynamicAssignment -> {
  //                int time = (int) dynamicAssignment.getTimeID();
  //                Set<Variable> dynAssigVariables = dynamicAssignment.getVariables();
  //                for (Variable dynVariable : dynAssigVariables) {
  //                    Variable staticVariable = staticEvenModel.getVariables().getVariableByName(dynVariable.getName() + "_t" + Integer.toString(time));
  //                    double varValue = dynamicAssignment.getValue(dynVariable);
  //                    staticEvidence.setValue(staticVariable, varValue);
  //                }
  //
  //            });
  //        }
  //
  
          if (evidence!=null && staticEvidence==null) {
  
              staticEvidence = new HashMapAssignment(unfoldedStaticModel.getNumberOfVars());
  
              evidence.stream().forEach(dynamicAssignment -> {
                  int time = (int) dynamicAssignment.getTimeID();
                  Set<Variable> dynAssigVariables = dynamicAssignment.getVariables();
                  for (Variable dynVariable : dynAssigVariables) {
                      Variable staticVariable = unfoldedStaticModel.getVariables().getVariableByName(dynVariable.getName() + "_t" + Integer.toString(time));
                      double varValue = dynamicAssignment.getValue(dynVariable);
                      staticEvidence.setValue(staticVariable, varValue);
                  }
              });
          }
  
  
          List<InferenceAlgorithm> staticModelsInference = new ArrayList<>(nMergedClassVars);
          IntStream.range(0,nMergedClassVars).forEachOrdered(i -> {
              InferenceAlgorithm currentModelInference;
              switch (searchAlgorithm) {
                  case VMP:
                      currentModelInference = new VMP();
                      //((VMP)currentModelInference).setTestELBO(true);
                      ((VMP)currentModelInference).setThreshold(0.0001);
                      ((VMP) currentModelInference).setMaxIter(3000);
                      break;
  
                  case IS:
                  default:
  
                      currentModelInference = new ImportanceSamplingRobust();
  
                      Random random = new Random((this.seed));
                      currentModelInference.setSeed(random.nextInt());
  
                      this.seed = random.nextInt();
  
                      ((ImportanceSamplingRobust) currentModelInference).setSampleSize(sampleSize);
  
                      break;
              }
              currentModelInference.setParallelMode(this.parallelMode);
              currentModelInference.setModel(mergedClassVarModels.get(i));
  
              if(searchAlgorithm==SearchAlgorithm.IS) {
                  ((ImportanceSamplingRobust) currentModelInference).setVariablesAPosteriori(mergedClassVarModels.get(i).getVariables().getListOfVariables().stream().filter(variable -> variable.getName().contains(groupedClassName)).collect(Collectors.toList()));
              }
  
              BayesianNetwork thisModel = mergedClassVarModels.get(i);
  
              if(staticEvidence!=null) {
                  Assignment thisEvidence = new HashMapAssignment();
                  for (Variable varEvidence : staticEvidence.getVariables()) {
                      thisEvidence.setValue(thisModel.getVariables().getVariableByName(varEvidence.getName()), staticEvidence.getValue(varEvidence));
                  }
                  currentModelInference.setEvidence(thisEvidence);
              }
              currentModelInference.runInference();
  
              //System.out.println(currentModelInference.getLogProbabilityOfEvidence());
  
              staticModelsInference.add(currentModelInference);
          });
  //
  //        IntStream.range(0, 2).parallel().forEach(i -> {
  //            if (i == 0) {
  //                evenModelInference.setParallelMode(this.parallelMode);
  //                evenModelInference.setModel(staticEvenModel);
  //                if (evidence != null) {
  //                    evenModelInference.setEvidence(staticEvidence);
  //                }
  //                evenModelInference.runInference();
  //            }
  //            else {
  //                oddModelInference.setParallelMode(this.parallelMode);
  //                oddModelInference.setModel(staticOddModel);
  //                if (evidence != null) {
  //                    oddModelInference.setEvidence(staticEvidence);
  //                }
  //                oddModelInference.runInference();
  //            }
  //        });
  
          List<List<UnivariateDistribution>> posteriorMAPDistributions = new ArrayList<>();
          IntStream.range(0,nMergedClassVars).forEachOrdered(modelNumber -> {
  
              List<UnivariateDistribution> currentModelPosteriorMAPDistributions = new ArrayList<>();
              int nReplicationsMAPVariable = (modelNumber==0 ? 0 : 1) + (nTimeSteps-modelNumber)/nMergedClassVars + ((nTimeSteps-modelNumber)%nMergedClassVars==0 ? 0 : 1);
  
              IntStream.range(0,nReplicationsMAPVariable).forEachOrdered(i -> {
                  currentModelPosteriorMAPDistributions.add(staticModelsInference.get(modelNumber).getPosterior(i));
                  //System.out.println(staticModelsInference.get(modelNumber).getPosterior(i).toString());
              });
  
              posteriorMAPDistributions.add(currentModelPosteriorMAPDistributions);
          });
  
  //        int replicationsMAPVariableEvenModel = nTimeSteps/2 + nTimeSteps%2;
  //        IntStream.range(0,replicationsMAPVariableEvenModel).forEachOrdered(i -> posteriorMAPDistributionsEvenModel.add(evenModelInference.getPosterior(i)));
  //
  //        int replicationsMAPVariableOddModel = 1 + (nTimeSteps-1)/2 + (nTimeSteps-1)%2;
  //        IntStream.range(0,replicationsMAPVariableOddModel).forEachOrdered(i -> posteriorMAPDistributionsOddModel.add(oddModelInference.getPosterior(i)));
  
  
  
          posteriorMAPDistributions.forEach(list -> {
  
              StringBuilder stringBuilder = new StringBuilder();
  
              list.forEach(uniDist -> {
                  stringBuilder.append(Arrays.toString(uniDist.getParameters()));
                  stringBuilder.append(" ,  ");
              });
              //System.out.println("Model number " + posteriorMAPDistributions.indexOf(list) + ": " + stringBuilder.toString());
          });
  
          allGroupedPosteriorDistributions = posteriorMAPDistributions;
  
          bestSequenceEachModel = new ArrayList<>();
          IntStream.range(0,nMergedClassVars).forEachOrdered(modelNumber -> {
              int [] thisModelBestSequence = new int[nTimeSteps];
              int indexSequence = 0;
  
              List<UnivariateDistribution> thisModelPosteriors = posteriorMAPDistributions.get(modelNumber);
  
              for (int k = 0; k < thisModelPosteriors.size(); k++) {
                  UnivariateDistribution thisDistribution = thisModelPosteriors.get(k);
  
                  int indexMaxProbability = (int)argMax(thisDistribution.getParameters())[1];
                  int thisDistribNumberOfMergedVars = (int) Math.round(Math.log(thisDistribution.getVariable().getNumberOfStates()) / Math.log(MAPvariable.getNumberOfStates()));
  
                  String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(indexMaxProbability), 10), MAPvariable.getNumberOfStates());
                  m_base_nStates = StringUtils.leftPad(m_base_nStates, thisDistribNumberOfMergedVars, '0');
  
                  for (int j = 0; j < m_base_nStates.length(); j++) {
                      thisModelBestSequence[indexSequence] = Integer.parseInt(m_base_nStates.substring(j,j+1));
                      indexSequence++;
                  }
              }
  
              //System.out.println("Best sequence model " + modelNumber + ": " + Arrays.toString(thisModelBestSequence));
              bestSequenceEachModel.add(thisModelBestSequence);
          });
  
          List<double[]> conditionalDistributionsMAPvariable = obtainMAPVariableConditionalDistributions(posteriorMAPDistributions);
          //System.out.println("Cond Distributions: " + Arrays.toString(conditionalDistributionsMAPvariable));
  
          StringBuilder stringBuilder = new StringBuilder();
          conditionalDistributionsMAPvariable.forEach(conDistr -> {
  
              stringBuilder.append(Arrays.toString(conDistr));
              stringBuilder.append(" ,  ");
          });
          //System.out.println("Combined Conditional Distributions: \n" + stringBuilder.toString());
  
  
          computeMostProbableSequence(conditionalDistributionsMAPvariable);
  
      }
  
      /**
       * Runs the inference for Ungrouped MAP variable given an input search algorithm.
       * @param searchAlgorithm a valid {@link SearchAlgorithm} value.
       */
      public void runInferenceUngroupedMAPVariable(SearchAlgorithm searchAlgorithm) {
  
          if (MAPvariable == null || MAPvarName == null) {
              System.out.println("Error: The MAP variable has not been set");
              System.exit(-30);
          }
  
  
          if (this.unfoldedStaticModel == null) {
              unfoldedStaticModel = DynamicToStaticBNConverter.convertDBNtoBN(model,nTimeSteps);
          }
  
          if (evidence!=null && staticEvidence==null) {
  
              staticEvidence = new HashMapAssignment(unfoldedStaticModel.getNumberOfVars());
  
              evidence.stream().forEach(dynamicAssignment -> {
                  int time = (int) dynamicAssignment.getTimeID();
                  Set<Variable> dynAssigVariables = dynamicAssignment.getVariables();
                  for (Variable dynVariable : dynAssigVariables) {
                      Variable staticVariable = unfoldedStaticModel.getVariables().getVariableByName(dynVariable.getName() + "_t" + Integer.toString(time));
                      double varValue = dynamicAssignment.getValue(dynVariable);
                      staticEvidence.setValue(staticVariable, varValue);
                  }
  
              });
          }
  
          InferenceAlgorithm staticModelInference;
          switch(searchAlgorithm) {
              case VMP:
                  staticModelInference = new VMP();
                  //((VMP)staticModelInference).setTestELBO(true);
                  ((VMP)staticModelInference).setThreshold(0.0001);
                  ((VMP)staticModelInference).setMaxIter(3000);
                  break;
  
              case IS:
              default:
                  ImportanceSamplingRobust importanceSampling =  new ImportanceSamplingRobust();
                  importanceSampling.setSampleSize(this.sampleSize);
                  Random random = new Random((this.seed));
                  importanceSampling.setSeed(random.nextInt());
                  staticModelInference=importanceSampling;
                  break;
          }
  
  
          staticModelInference.setParallelMode(this.parallelMode);
          staticModelInference.setModel(unfoldedStaticModel);
  
          if (searchAlgorithm==SearchAlgorithm.IS) {
              ((ImportanceSamplingRobust)staticModelInference).setVariablesAPosteriori(unfoldedStaticModel.getVariables().getListOfVariables().stream().filter(variable -> variable.getName().contains(MAPvarName)).collect(Collectors.toList()));
          }
          if (evidence != null) {
              staticModelInference.setEvidence(staticEvidence);
          }
          staticModelInference.runInference();
  
          List<UnivariateDistribution> posteriorMAPDistributionsStaticModel = new ArrayList<>();
          IntStream.range(0,nTimeSteps).forEachOrdered(i -> {
              posteriorMAPDistributionsStaticModel.add(staticModelInference.getPosterior(i));
              //System.out.println("Ungrouped Posterior " + i + staticModelInference.getPosterior(i).toString());
          });
  
          allUngroupedPosteriorDistributions = posteriorMAPDistributionsStaticModel;
  
          double [] probabilities = posteriorMAPDistributionsStaticModel.stream().map(dist -> argMax(dist.getParameters())).mapToDouble(array -> array[0]).toArray();
          double MAPsequenceProbability = Math.exp(Arrays.stream(probabilities).map(prob -> Math.log(prob)).sum());
  
          int [] MAPsequence = posteriorMAPDistributionsStaticModel.stream().map(dist -> argMax(dist.getParameters())).mapToInt(array -> (int) array[1]).toArray();
  
          MAPestimate = new HashMapAssignment(nTimeSteps);
          IntStream.range(0,nTimeSteps).forEach(t-> {
              Variables variables = Serialization.deepCopy(this.unfoldedStaticModel.getVariables());
              Variable currentVar;
              if (variables.getVariableByName(MAPvarName + "_t" + Integer.toString(t))!=null) {
                  currentVar  = variables.getVariableByName(MAPvarName + "_t" + Integer.toString(t));
              }
              else {
                  currentVar  = variables.newMultinomialVariable(MAPvarName + "_t" + Integer.toString(t), MAPvariable.getNumberOfStates());
              }
  
              MAPestimate.setValue(currentVar,MAPsequence[t]);
          });
          MAPestimateLogProbability = Math.log(MAPsequenceProbability);
          this.MAPsequence = MAPsequence;
      }
  
  
  
      private List<double[]> obtainMAPVariableConditionalDistributions(List<List<UnivariateDistribution>> posteriorMAPVariableDistributions ) {
  
          List<double[]> listCondDistributions = new ArrayList<>(nTimeSteps);
  
          int nStates = MAPvariable.getNumberOfStates();
  
          // Univariate distribution Y_0
          // UnivariateDistribution dist0_1 = posteriorMAPDistributionsEvenModel.get(0); // This variable Z_0 groups Y_0 and Y_1
          // UnivariateDistribution dist0 = posteriorMAPDistributionsOddModel.get(0); // This variable is just Y_0 (not a group)
  //        double[] dist0_probs = new double[nStates];
  
  //        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n");
          IntStream.range(0, nTimeSteps).forEachOrdered(timeStep -> {
  
  //            System.out.println("\n\nTime step " + timeStep);
              double[] combinedConditionalDistributionProbabilities, baseDistributionProbabilities;
  
              int baseModelIndex = (timeStep+1)%nMergedClassVars;
              int baseDistributionIndex = (timeStep >= baseModelIndex) ? (baseModelIndex == 0 ? 0 : 1) + (timeStep - baseModelIndex) / nMergedClassVars : (timeStep - baseModelIndex) / nMergedClassVars;
              baseDistributionProbabilities = posteriorMAPVariableDistributions.get(baseModelIndex).get(baseDistributionIndex).getParameters();
              int nStatesBaseDistribution = baseDistributionProbabilities.length;
              int baseDistrib_nMergedVars = (int) Math.round(Math.log(nStatesBaseDistribution) / Math.log(nStates));
  
              combinedConditionalDistributionProbabilities =
  
                      IntStream.range(0, nMergedClassVars).mapToObj(modelNumber -> {
  
                          if (modelNumber==baseModelIndex) {
  //                            System.out.println("\nModel number " + modelNumber);
                              //System.out.println(Arrays.toString(baseDistributionProbabilities));
                              return baseDistributionProbabilities;
                          }
  
  //                        System.out.println("\nModel number " + modelNumber);
  
                          int distributionIndex = (timeStep >= modelNumber) ? (modelNumber == 0 ? 0 : 1) + (timeStep - modelNumber) / nMergedClassVars : (timeStep - modelNumber) / nMergedClassVars;
                          int currentVarIndex = (timeStep >= modelNumber) ? (timeStep - modelNumber) % nMergedClassVars : timeStep;
  //                        System.out.println("CurrentVarIndex " + currentVarIndex);
  
                          UnivariateDistribution currentDistrib = posteriorMAPVariableDistributions.get(modelNumber).get(distributionIndex);
                          //System.out.println(currentDistrib.toString());
  
  
                          double[] probabilities = new double[nStatesBaseDistribution];
  
                          int currentDistrib_nMergedVars = (int) Math.round(Math.log(currentDistrib.getVariable().getNumberOfStates()) / Math.log(nStates));
                          int current_nMergedVarsBaseDist = (int) Math.round(Math.log(baseDistributionProbabilities.length) / Math.log(nStates));
  
  
                          if (distributionIndex==0) {
  
  //                            System.out.println("Current nMergedVars " + currentDistrib_nMergedVars + ", current nMergedVarsBaseDist " + current_nMergedVarsBaseDist);
                              for (int m = 0; m < Math.pow(nStates, currentDistrib_nMergedVars); m++) {
  
                                  String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(m), 10), nStates);
                                  m_base_nStates = StringUtils.leftPad(m_base_nStates, currentDistrib_nMergedVars, '0');
  
  //                                int index_init = currentVarIndex - ((timeStep >= nMergedClassVars) ? nMergedClassVars : timeStep);
                                  int index_init = currentVarIndex + 1 - baseDistrib_nMergedVars;
                                  int index_end = currentVarIndex + 1;
  
  //                                String statesSequence = m_base_nStates.substring(currentVarIndex, currentVarIndex + current_nMergedVarsBaseDist);
                                  String statesSequence = m_base_nStates.substring(index_init, index_end);
                                  int currentState = Integer.parseInt(statesSequence, nStates);
  
  //                                System.out.println("Current state " + currentState);
  
                                  probabilities[currentState] += currentDistrib.getParameters()[m];
                              }
                          }
                          else {
                              UnivariateDistribution previousDistrib = posteriorMAPVariableDistributions.get(modelNumber).get(distributionIndex - 1);
                              int previousDistrib_nMergedVars = (int) Math.round(Math.log(previousDistrib.getVariable().getNumberOfStates()) / Math.log(nStates));
  
  //                            System.out.println("Current nMergedVars " + currentDistrib_nMergedVars + ", previous nMergedVars " + previousDistrib_nMergedVars + ", current nMergedVarsBaseDist " + current_nMergedVarsBaseDist);
  
                              for (int n = 0; n < Math.pow(nStates, previousDistrib_nMergedVars); n++) {
  
                                  String n_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(n), 10), nStates);
                                  n_base_nStates = StringUtils.leftPad(n_base_nStates, previousDistrib_nMergedVars, '0');
  
                                  for (int m = 0; m < Math.pow(nStates, currentDistrib_nMergedVars); m++) {
  
                                      String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(m), 10), nStates);
                                      m_base_nStates = StringUtils.leftPad(m_base_nStates, currentDistrib_nMergedVars, '0');
  
  //                                    String statesSequence = m_base_nStates.substring(currentVarIndex, currentVarIndex + current_nMergedVarsBaseDist);
  //                                    int currentState = Integer.parseInt(statesSequence, nStates);
  
  
                                      String n_concat_m_base_nStates = n_base_nStates.concat(m_base_nStates);
                                      int index_init = previousDistrib_nMergedVars + currentVarIndex + 1 - baseDistrib_nMergedVars;
                                      int index_end = previousDistrib_nMergedVars + currentVarIndex + 1;
                                      String statesSequence = n_concat_m_base_nStates.substring( index_init, index_end);
  //                                    System.out.println("Complete sequence: " + n_concat_m_base_nStates + ", statesSequence:" + statesSequence);
  
  //                                    int subIndices_m = currentVarIndex;
  //                                    int subIndices_n = 1 + ((timeStep >= nMergedClassVars) ? (previousDistrib_nMergedVars - nMergedClassVars + currentVarIndex) : (previousDistrib_nMergedVars - (nMergedClassVars-timeStep) + currentVarIndex));
  //
  //                                    System.out.println("n_base_nStates: " + n_base_nStates + "m_base_nStates: " + m_base_nStates );
  //
  //                                    System.out.println("subIndices_m: " + Integer.toString(subIndices_m));
  //                                    System.out.println("subIndices_n: " + Integer.toString(subIndices_n));
  //
  //                                    String statesSequence_m = m_base_nStates.substring(0, subIndices_m);
  //                                    String statesSequence_n = n_base_nStates.substring(subIndices_n, previousDistrib_nMergedVars);
  //
  //
  //                                    System.out.println("statesSequence n: " + statesSequence_n + ", statesSequence m: " + statesSequence_m );
  //
  //                                    String statesSequence = statesSequence_n.concat(statesSequence_m);
  //                                    System.out.println("States sequence length: " + statesSequence.length() + ", sequence: " + statesSequence);
  
                                      int currentState = Integer.parseInt(statesSequence, nStates);
  //                                    System.out.println("Current state " + currentState);
  
                                      probabilities[currentState] += previousDistrib.getParameters()[n] * currentDistrib.getParameters()[m];
                                  }
                              }
                          }
  //                        System.out.println("Model distribution: " + Arrays.toString(probabilities));
                          return probabilities;
                      })
                              .reduce(new double[baseDistributionProbabilities.length], (doubleArray1, doubleArray2) -> {
                                  if (doubleArray1.length != doubleArray2.length) {
  //                            System.out.println("Problem with lengths");
                                      System.exit(-40);
                                  }
                                  for (int i = 0; i < doubleArray1.length; i++)
                                      doubleArray1[i] += ((double) 1 / nMergedClassVars) * doubleArray2[i];
                                  return doubleArray1;
                              });
  
              //System.out.println("Combined distribution " + Arrays.toString(combinedConditionalDistributionProbabilities));
              listCondDistributions.add(combinedConditionalDistributionProbabilities);
          });
  
  //        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n");
          return listCondDistributions;
      }
  
      /**
       * Computes the Most Probable Sequence given the posterior distributions of the MAP variable.
       * @param posteriorDistributionsMAPvariable a {@code List} of conditional distribution values.
       */
      private void computeMostProbableSequence(List<double[]> posteriorDistributionsMAPvariable) {
  
          int[] MAPsequence = new int[nTimeSteps];
          int nStates = MAPvariable.getNumberOfStates();
  
          int[] argMaxValues = new int[nTimeSteps-1];
  
          double MAPsequenceProbability=-1;
          double [] current_probs;
          double [] current_max_probs = new double[(int)Math.pow(nStates,nMergedClassVars-1)];
          double [] previous_max_probs = new double[(int)Math.pow(nStates,nMergedClassVars-1)];
  
  
          for (int t = nTimeSteps-1; t >= 1; t--) {
  
  //            System.out.println("Time:" + t);
              double [] currentDistProbabilities = posteriorDistributionsMAPvariable.get(t);
  
              if (Arrays.stream(currentDistProbabilities).anyMatch(Double::isNaN)) {
                  MAPsequence = new int[nTimeSteps];
                  for (int i = 0; i < MAPsequence.length; i++) {
                      MAPsequence[i]=-1;
                      return;
                  }
              }
              int currentDistrib_nMergedVars = (int) Math.round(Math.log(currentDistProbabilities.length)/Math.log(nStates));
  
  //            System.out.println("Current Probabilities:" + Arrays.toString(currentDistProbabilities));
              current_max_probs = new double[(int)Math.pow(nStates, currentDistrib_nMergedVars-1)];
  
              if (t==nTimeSteps-1) {
                  previous_max_probs = Arrays.stream(previous_max_probs).map(d -> 1).toArray();
              }
  //            System.out.println("Current Max Probs Length: " + current_max_probs.length);
  //            System.out.println("Previous Max Probs: " + Arrays.toString(previous_max_probs));
  
              for (int m = 0; m < Math.pow(nStates, currentDistrib_nMergedVars); m++) {
  
  //                System.out.println("State: " + m );
                  String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(m), 10), nStates);
                  m_base_nStates = StringUtils.leftPad(m_base_nStates, currentDistrib_nMergedVars, '0');
  
  
                  int currentStateFirstVars;
                  int currentStateLastVars;
                  if (t>0) {
                      String stateFirstVars = m_base_nStates.substring(0, currentDistrib_nMergedVars-1);
                      currentStateFirstVars = Integer.parseInt(stateFirstVars, nStates);
  
                      String stateLastVars = m_base_nStates.substring(1,currentDistrib_nMergedVars);
                      currentStateLastVars = Integer.parseInt(stateLastVars, nStates);
                  }
                  else {
                      currentStateFirstVars=0;
                      currentStateLastVars= Integer.parseInt(m_base_nStates, nStates);
                  }
  
  //                System.out.println("FirstVars:" + currentStateFirstVars + ", LastVars:" + currentStateLastVars);
  
                  double currentProb = currentDistProbabilities[m] * previous_max_probs[currentStateLastVars];
                  double maxProb = current_max_probs[currentStateFirstVars];
  
                  if (currentProb > maxProb) {
                      current_max_probs[currentStateFirstVars] = currentProb;
                  }
              }
  
  //            System.out.println("Current Max Probabilities:" + Arrays.toString(current_max_probs));
  
              argMaxValues[t-1] = (int)argMax(current_max_probs)[1];
              previous_max_probs = current_max_probs;
  
              if (t==1) {
                  MAPsequenceProbability =  argMax(current_max_probs)[0];
              }
  //            System.out.println("MAP Sequence Prob:" + MAPsequenceProbability);
  //            System.out.println("Arg Max Value: " + argMaxValues[t-1]+ "\n\n\n");
  
          }
  
  //        for (int t = nTimeSteps-1; t >= 0; t--) {
  //
  //            current_probs = posteriorDistributionsMAPvariable.get(t);
  //            double maxProb=-1;
  //
  //            current_max_probs = new double[MAPvarNStates];
  //
  //            if (t==(nTimeSteps-1)) { // There are no previous_max_probs
  //                for (int j = 0; j < MAPvarNStates; j++) { // To go over all values of Y_{t-1}
  //                    maxProb=-1;
  //                    for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_t
  //                        if (current_probs[j * MAPvarNStates + k] > maxProb) {
  //                            maxProb = current_probs[j * MAPvarNStates + k];
  //
  //                        }
  //                    }
  //                    current_max_probs[j]=maxProb;
  //                }
  //                argMaxValues[t] = (int)argMax(current_max_probs)[1];
  //                previous_max_probs = current_max_probs;
  //            }
  //            else if (t>0 && t<(nTimeSteps-1)) {
  //                for (int j = 0; j < MAPvarNStates; j++) { // To go over all values of Y_{t-1}
  //                    maxProb=-1;
  //                    for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_t
  //                        if (current_probs[j * MAPvarNStates + k]*previous_max_probs[j] > maxProb) {
  //                            maxProb = current_probs[j * MAPvarNStates + k]*previous_max_probs[k];
  //                        }
  //                    }
  //                    current_max_probs[j]=maxProb;
  //                }
  //                argMaxValues[t] = (int)argMax(current_max_probs)[1];
  //                previous_max_probs = current_max_probs;
  //            }
  //            else { // Here, t=0
  //                for (int j = 0; j < MAPvarNStates; j++) { // To go over all values of Y_0
  //                    maxProb=-1;
  //                    for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_1
  //                        if (current_probs[j]*previous_max_probs[j] > maxProb) {
  //                            maxProb = current_probs[j]*previous_max_probs[j];
  //                        }
  //                    }
  //                    current_max_probs[j]=maxProb;
  //                }
  //                MAPsequenceProbability =  argMax(current_max_probs)[0];
  //                argMaxValues[t] = (int)argMax(current_max_probs)[1];
  //                previous_max_probs = current_max_probs;
  //            }
  //        }
  
          //System.out.println(Arrays.toString(argMaxValues));
  
  //        System.out.println("\n\n TRACEBACK \n\n");
  
          //int previousVarMAPState = argMaxValues[0];
          MAPsequence[0] = argMaxValues[0];
  
          int thisVarMAPState = 0;
          for (int t = 1; t < nTimeSteps; t++) {
  
  //            System.out.println("Time Step: " + t);
              current_probs = posteriorDistributionsMAPvariable.get(t);
  
              StringBuilder prevVarsStateBuilder = new StringBuilder();
  
              int j_max = Math.min(nMergedClassVars-1, t);
              for (int j = 0; j < Math.min(nMergedClassVars-1, t); j++) {
  //                System.out.println("Append: " + Integer.toString(MAPsequence[t-j_max+j]) );
                  prevVarsStateBuilder.append( Integer.toString(MAPsequence[t-j_max+j]) );
              }
              //previousVarMAPState = argMaxValues[t-1];
  
  //            System.out.println("PrevVarsState: " + prevVarsStateBuilder.toString());
  //            String prevVarsState = Integer.toString(Integer.parseInt(prevVarsStateBuilder.toString()), nStates);
              String prevVarsState = prevVarsStateBuilder.toString();
  
  //            System.out.println("Prev Vars State:" + prevVarsState);
  
  
  //            String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(previousVarMAPState), 10), nStates);
  //            m_base_nStates = StringUtils.leftPad(m_base_nStates, currentDistrib_nMergedVars, '0');
  //
  //            String stateConditioningVars = m_base_nStates.substring(0, currentDistrib_nMergedVars-1);
  //            int currentStateConditioningVars = Integer.parseInt(stateConditioningVars, nStates);
  //
  //            String stateLastVar = m_base_nStates.substring(currentDistrib_nMergedVars-1, currentDistrib_nMergedVars);
  //            int currentStateLastVar = Integer.parseInt(stateLastVar, nStates);
  //
  //            double currentProb=currentDistProbabilities[m] * previous_max_probs[currentStateLastVar];
  //            double maxProb=current_max_probs[currentStateConditioningVars];
  //
  //            if (currentProb>maxProb) {
  //                current_max_probs[currentStateConditioningVars] = currentProb ;
  //            }
  
              double maxProb = -1;
              for (int j = 0; j < nStates; j++) { // To go over all values of Y_t
  
                  int currentState = Integer.parseInt(prevVarsState.concat(Integer.toString(j)),nStates);
  //                System.out.println("Current state:" + currentState);
  
                  if (current_probs[currentState] > maxProb) {
                      maxProb = current_probs[currentState];
                      thisVarMAPState = j;
                  }
              }
              MAPsequence[t]=thisVarMAPState;
  //            System.out.println("Currente Sequence Value: " + MAPsequence[t] + "\n\n");
          }
  
  //        int previousVarMAPState = argMaxValues[0];
  //        MAPsequence[0] = argMaxValues[0];
  //
  //        int thisVarMAPState = 0;
  //        for (int t = 1; t < nTimeSteps; t++) {
  //            current_probs = posteriorDistributionsMAPvariable.get(t);
  //            previousVarMAPState = argMaxValues[t-1];
  //
  //            double maxProb = -1;
  //            for (int j = 0; j < nStates; j++) { // To go over all values of Y_t
  //
  //                if (current_probs[previousVarMAPState * nStates + j] >= maxProb) {
  //                    maxProb = current_probs[previousVarMAPState * nStates + j];
  //                    thisVarMAPState = j;
  //                }
  //            }
  //            MAPsequence[t]=thisVarMAPState;
  //        }
  
  
  
  
  
  
  
          if (Arrays.stream(MAPsequence).anyMatch(value -> value<0)) {
              MAPestimateLogProbability=Double.NaN;
          }
          else {
              MAPestimateLogProbability = Math.log(MAPsequenceProbability);
          }
          this.MAPsequence = MAPsequence;
  
  //        System.out.println("FINAL SEQUENCE: " + Arrays.toString(MAPsequence));
      }
  
  //    /**
  //     * Computes the Most Probable Sequence given the posterior distributions of the MAP variable.
  //     * @param posteriorDistributionsMAPvariable a {@code List} of conditional distribution values.
  //     */
  //    private void computeMostProbableSequence(List<double[]> posteriorDistributionsMAPvariable) {
  //
  //        int[] MAPsequence = new int[nTimeSteps];
  //        int MAPvarNStates = MAPvariable.getNumberOfStates();
  //
  //        int[] argMaxValues = new int[nTimeSteps];
  //
  //        double MAPsequenceProbability=-1;
  //        double [] current_probs;
  //        double [] current_max_probs;
  //        double [] previous_max_probs = new double[MAPvarNStates];;
  //
  //        for (int t = nTimeSteps-1; t >= 0; t--) {
  //
  //            current_probs = posteriorDistributionsMAPvariable.get(t);
  //            double maxProb=-1;
  //
  //            current_max_probs = new double[MAPvarNStates];
  //
  //            if (t==(nTimeSteps-1)) { // There are no previous_max_probs
  //                for (int j = 0; j < MAPvarNStates; j++) { // To go over all values of Y_{t-1}
  //                    maxProb=-1;
  //                    for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_t
  //                        if (current_probs[j * MAPvarNStates + k] > maxProb) {
  //                            maxProb = current_probs[j * MAPvarNStates + k];
  //
  //                        }
  //                    }
  //                    current_max_probs[j]=maxProb;
  //                }
  //                argMaxValues[t] = (int)argMax(current_max_probs)[1];
  //                previous_max_probs = current_max_probs;
  //            }
  //            else if (t>0 && t<(nTimeSteps-1)) {
  //                for (int j = 0; j < MAPvarNStates; j++) { // To go over all values of Y_{t-1}
  //                    maxProb=-1;
  //                    for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_t
  //                        if (current_probs[j * MAPvarNStates + k]*previous_max_probs[j] > maxProb) {
  //                            maxProb = current_probs[j * MAPvarNStates + k]*previous_max_probs[k];
  //                        }
  //                    }
  //                    current_max_probs[j]=maxProb;
  //                }
  //                argMaxValues[t] = (int)argMax(current_max_probs)[1];
  //                previous_max_probs = current_max_probs;
  //            }
  //            else { // Here, t=0
  //                for (int j = 0; j < MAPvarNStates; j++) { // To go over all values of Y_0
  //                    maxProb=-1;
  //                    for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_1
  //                        if (current_probs[j]*previous_max_probs[j] > maxProb) {
  //                            maxProb = current_probs[j]*previous_max_probs[j];
  //                        }
  //                    }
  //                    current_max_probs[j]=maxProb;
  //                }
  //                MAPsequenceProbability =  argMax(current_max_probs)[0];
  //                argMaxValues[t] = (int)argMax(current_max_probs)[1];
  //                previous_max_probs = current_max_probs;
  //            }
  //        }
  //
  //        int previousVarMAPState = argMaxValues[0];
  //        MAPsequence[0] = argMaxValues[0];
  //
  //        int thisVarMAPState = 0;
  //        for (int t = 1; t < nTimeSteps; t++) {
  //            current_probs = posteriorDistributionsMAPvariable.get(t);
  //            previousVarMAPState = argMaxValues[t-1];
  //
  //            double maxProb = -1;
  //            for (int j = 0; j < MAPvarNStates; j++) { // To go over all values of Y_t
  //
  //                if (current_probs[previousVarMAPState * MAPvarNStates + j] >= maxProb) {
  //                    maxProb = current_probs[previousVarMAPState * MAPvarNStates + j];
  //                    thisVarMAPState = j;
  //                }
  //            }
  //            MAPsequence[t]=thisVarMAPState;
  //        }
  //
  //        MAPestimate = new HashMapAssignment(nTimeSteps);
  //
  //        if (Arrays.stream(MAPsequence).anyMatch(value -> value<0)) {
  //            MAPestimateLogProbability=Double.NaN;
  //        }
  //        else {
  //            IntStream.range(0, nTimeSteps).forEach(t -> {
  ////                Variables varsAux = Serialization.deepCopy(this.staticEvenModel.getVariables());
  ////                Variable currentVar = varsAux.newMultinomialVariable(MAPvarName + "_t" + Integer.toString(t), MAPvariable.getNumberOfStates());
  //                Variable currentVar;
  //                try {
  //                    currentVar = this.staticEvenModel.getVariables().getVariableByName(MAPvarName + "_t" + Integer.toString(t));
  //                }
  //                catch (Exception e) {
  //                    Variables copy = Serialization.deepCopy(this.staticEvenModel.getVariables());
  //                    currentVar = copy.newMultinomialVariable(MAPvarName + "_t" + Integer.toString(t), MAPvariable.getNumberOfStates());
  //                }
  //                MAPestimate.setValue(currentVar, MAPsequence[t]);
  //            });
  //            MAPestimateLogProbability = Math.log(MAPsequenceProbability);
  //        }
  //
  //        this.MAPsequence = MAPsequence;
  //    }
  
  
  
  //    /**
  //     * Returns Combined Conditional Distributions for both even and odd models.
  //     * @param posteriorMAPVariableDistributions a {@code List} of {@code List} of {@link UnivariateDistribution} for each model.
  //     * @return a {@code List} of conditional distributions values.
  //     */
  //    private List<double[]> combinePosteriorConditionalDistributions(List<List<UnivariateDistribution>> posteriorMAPVariableDistributions) {
  //
  //        List<double[]> listCondDistributions = new ArrayList<>(nTimeSteps);
  //
  //        int nStates = MAPvariable.getNumberOfStates();
  //
  //        // Univariate distribution Y_0
  //        // UnivariateDistribution dist0_1 = posteriorMAPDistributionsEvenModel.get(0); // This variable Z_0 groups Y_0 and Y_1
  //        // UnivariateDistribution dist0 = posteriorMAPDistributionsOddModel.get(0); // This variable is just Y_0 (not a group)
  ////        double[] dist0_probs = new double[nStates];
  //
  //        IntStream.range(0, nTimeSteps).forEachOrdered(timeStep -> {
  //
  ////            System.out.println("\n\nTime step " + timeStep);
  //            double [] combinedConditionalDistributionProbabilities;
  //
  //            combinedConditionalDistributionProbabilities =
  //
  //            IntStream.range(0, nMergedClassVars).mapToObj(modelNumber -> {
  //
  ////                System.out.println("\nModel number " + modelNumber);
  //
  //                int distributionIndex = (timeStep>=modelNumber) ? (modelNumber==0 ? 0 : 1) + (timeStep-modelNumber)/nMergedClassVars : (timeStep-modelNumber)/nMergedClassVars;
  //
  //                UnivariateDistribution dist0 = posteriorMAPVariableDistributions.get(modelNumber).get(distributionIndex);
  ////                System.out.println(dist0.toString());
  //
  //                double[] probabilities  = new double[nStates];
  //
  //                int current_nMergedVars = (int)Math.round(Math.log(dist0.getVariable().getNumberOfStates())/Math.log(nStates));
  //
  //                for (int m = 0; m < Math.pow(nStates,current_nMergedVars); m++) {
  //                    String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(m), 10), nStates);
  //                    m_base_nStates = StringUtils.leftPad(m_base_nStates, current_nMergedVars, '0');
  //                    int currentVarIndex = (timeStep>=modelNumber) ? (timeStep-modelNumber)%nMergedClassVars : timeStep;
  //                    int currentState = Integer.parseInt(m_base_nStates.substring(currentVarIndex,currentVarIndex+1));
  //
  //                    probabilities[currentState] += dist0.getParameters()[m];
  //                }
  ////                System.out.println(Arrays.toString(probabilities));
  //                return probabilities;
  //
  //            })
  //            .reduce(new double[nStates], (doubleArray1, doubleArray2)-> {
  //                for (int i=0; i<doubleArray1.length; i++)
  //                    doubleArray1[i] += ((double)1/nMergedClassVars) * doubleArray2[i];
  //                return doubleArray1;
  //            });
  //
  ////            System.out.println("Combined distribution " + Arrays.toString(combinedConditionalDistributionProbabilities));
  //            listCondDistributions.add(combinedConditionalDistributionProbabilities);
  //        });
  //
  //
  //
  //
  ////
  ////        double[] probs1  = new double[nMergedStates];
  ////        int probs_index1 = 0;
  ////
  ////        for (int m = 0; m < Math.pow(nStates,repetitionsConDistT); m++) {
  ////            String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(m), 10), nStates);
  ////            m_base_nStates = StringUtils.leftPad(m_base_nStates, repetitionsConDistT, '0');
  ////
  ////            double probT=1;
  ////            for ( int n=0; n < m_base_nStates.length(); n++) {
  ////                int currentState = Integer.parseInt(m_base_nStates.substring(n,n+1));
  ////
  ////                int previousState;
  ////                if(n>=1)
  ////                    previousState = Integer.parseInt(m_base_nStates.substring(n-1,n));
  ////                else
  ////                    previousState = parentState;
  ////
  ////                assignment1 = new HashMapAssignment(2);
  ////                assignment1.setValue(dynVar.getInterfaceVariable(), previousState);
  ////                assignment1.setValue(dynVar,currentState);
  ////
  ////
  ////                probT = probT * conDistT.getConditionalProbability(assignment1);
  ////            }
  ////            probs1[probs_index1] =  probT;
  ////            probs_index1++;
  ////        }
  ////
  ////
  ////
  ////
  ////
  ////        dist0_probs = dist0.getParameters();
  ////        for (int i = 0; i < MAPvarNStates; i++) {
  ////            dist0_probs[i] = (double) 1/2 * dist0_probs[i];
  ////
  ////            for (int j = 0; j < MAPvarNStates; j++) {
  ////                dist0_probs[i] = dist0_probs[i] + (double) 1/2 * dist0_1.getProbability(i*MAPvarNStates + j);
  ////            }
  ////        }
  ////        listCondDistributions.add(dist0_probs);
  ////
  ////
  //
  //
  //
  //
  //
  ////
  ////        // Conditional distribution Y_1 | Y_0;
  ////        UnivariateDistribution dist_paired1 = posteriorMAPDistributionsEvenModel.get(0); // This variable Z_0 groups Y_0 and Y_1
  ////        UnivariateDistribution dist_unpaired_1 = posteriorMAPDistributionsOddModel.get(1); // This variable groups Y_1 and Y_2 (if nTimeSteps>2)
  ////
  ////        double[] dist_probs1 = dist_paired1.getParameters();
  ////
  ////        for (int i = 0; i < MAPvarNStates; i++) { // To go over all values of Y_0
  ////            for (int j = 0; j < MAPvarNStates; j++) { // To go over all values of Y_1
  ////                int index = i * MAPvarNStates + j;
  ////                dist_probs1[index] = (double) 1/2 * dist_probs1[index];
  ////
  ////                if (nTimeSteps>2) {
  ////                    for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_2 in the distrib of (Y_1,Y_2)
  ////                        dist_probs1[index] = dist_probs1[index] + (double) 1 / 2 * dist_unpaired_1.getProbability(j * MAPvarNStates + k);
  ////                    }
  ////                }
  ////                else {
  ////                    for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_2 in the distrib of (Y_1,Y_2)
  ////                        dist_probs1[index] = dist_probs1[index] + (double) 1 / 2 * dist_unpaired_1.getProbability(j);
  ////                    }
  ////                }
  ////            }
  ////        }
  ////        listCondDistributions.add(dist_probs1);
  ////
  ////        IntStream.range(2, nTimeSteps - 1).forEachOrdered(t -> {
  ////            if (t % 2 == 0) {
  ////                int idxOdd = 1 + (t - 2) / 2;
  ////                UnivariateDistribution dist_paired = posteriorMAPDistributionsOddModel.get(idxOdd); // This variable groups Y_t and Y_{t-1}
  ////
  ////                UnivariateDistribution dist_unpaired_pre = posteriorMAPDistributionsEvenModel.get(idxOdd - 1); // This variable groups Y_{t-2} and Y_{t-1}
  ////                UnivariateDistribution dist_unpaired_post = posteriorMAPDistributionsEvenModel.get(idxOdd); // This variable groups Y_t and Y_{t+1}
  ////
  ////                double[] dist_probs = dist_paired.getParameters();
  ////
  ////                for (int i = 0; i < MAPvarNStates; i++) {  // To go over all values of Y_{t-1}
  ////                    for (int j = 0; j < MAPvarNStates; j++) {  // To go over all values of Y_t
  ////
  ////                        int index = i * MAPvarNStates + j;
  ////                        dist_probs[index] = (double) 1 / 2 * dist_probs[index];
  ////
  ////                        for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_{t-2}
  ////                            for (int m = 0; m < MAPvarNStates; m++) {  // To go over all values of Y_{t+1}
  ////                                dist_probs[index] = dist_probs[index] + (double) 1 / 2 * dist_unpaired_pre.getProbability(k * MAPvarNStates + i) * dist_unpaired_post.getProbability(j * MAPvarNStates + m);
  ////                            }
  ////                        }
  ////                    }
  ////                }
  ////                listCondDistributions.add(dist_probs);
  ////            } else {
  ////                int idxEven = (t - 1) / 2;
  ////                UnivariateDistribution dist_paired = posteriorMAPDistributionsEvenModel.get(idxEven); // This variable groups Y_t and Y_{t-1}
  ////
  ////                UnivariateDistribution dist_unpaired_pre = posteriorMAPDistributionsOddModel.get(idxEven); // This variable groups Y_{t-2} and Y_{t-1}
  ////                UnivariateDistribution dist_unpaired_post = posteriorMAPDistributionsOddModel.get(idxEven + 1); // This variable groups Y_t and Y_{t+1}
  ////
  ////                double[] dist_probs = dist_paired.getParameters();
  ////
  ////                for (int i = 0; i < MAPvarNStates; i++) {  // To go over all values of Y_{t-1}
  ////                    for (int j = 0; j < MAPvarNStates; j++) {  // To go over all values of Y_t
  ////
  ////                        int index = i * MAPvarNStates + j;
  ////                        dist_probs[index] = (double) 1 / 2 * dist_probs[index];
  ////
  ////                        for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_{t-2}
  ////                            for (int m = 0; m < MAPvarNStates; m++) {  // To go over all values of Y_{t+1}
  ////                                dist_probs[index] = dist_probs[index] + (double) 1 / 2 * dist_unpaired_pre.getProbability(k * MAPvarNStates + i) * dist_unpaired_post.getProbability(j * MAPvarNStates + m);
  ////                            }
  ////                        }
  ////                    }
  ////                }
  ////                listCondDistributions.add(dist_probs);
  ////            }
  ////        });
  ////
  ////        if (nTimeSteps>2) {
  ////            // Conditional distribution Y_t | Y_{t-1},  with  t = nTimeSteps-1
  ////            int t = (nTimeSteps - 1);
  ////            if (t % 2 == 0) {
  ////                int idxOdd = 1 + (t - 2) / 2;
  ////                UnivariateDistribution dist_paired = posteriorMAPDistributionsOddModel.get(idxOdd); // This variable groups Y_t and Y_{t-1}
  ////
  ////                UnivariateDistribution dist_unpaired_pre = posteriorMAPDistributionsEvenModel.get(idxOdd - 1);  // This variable groups Y_{t-2} and Y_{t-1}
  ////                UnivariateDistribution dist_unpaired_post = posteriorMAPDistributionsEvenModel.get(idxOdd); // This variable is just Y_t (not a group)
  ////
  ////                double[] dist_probs = dist_paired.getParameters();
  ////
  ////                for (int i = 0; i < MAPvarNStates; i++) {  // To go over all values of Y_{t-1}
  ////                    for (int j = 0; j < MAPvarNStates; j++) {  // To go over all values of Y_t
  ////
  ////                        int index = i * MAPvarNStates + j;
  ////                        dist_probs[index] = (double) 1 / 2 * dist_probs[index];
  ////
  ////                        for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_{t-2}
  ////
  ////                            dist_probs[index] = dist_probs[index] + (double) 1 / 2 * dist_unpaired_pre.getProbability(k * MAPvarNStates + i) * dist_unpaired_post.getProbability(j);
  ////                        }
  ////                    }
  ////                }
  ////                listCondDistributions.add(dist_probs);
  ////            }
  ////            else {
  ////                int idxEven = (t - 1) / 2;
  ////                UnivariateDistribution dist_paired = posteriorMAPDistributionsEvenModel.get(idxEven); // This variable groups Y_t and Y_{t-1}
  ////
  ////                UnivariateDistribution dist_unpaired_pre = posteriorMAPDistributionsOddModel.get(idxEven);  // This variable groups Y_{t-2} and Y_{t-1}
  ////
  ////                double[] dist_probs = dist_paired.getParameters();
  ////
  ////                for (int i = 0; i < MAPvarNStates; i++) {  // To go over all values of Y_{t-1}
  ////                    for (int j = 0; j < MAPvarNStates; j++) {  // To go over all values of Y_t
  ////
  ////                        int index = i * MAPvarNStates + j;
  ////                        dist_probs[index] = (double) 1 / 2 * dist_probs[index];
  ////
  ////                        for (int k = 0; k < MAPvarNStates; k++) { // To go over all values of Y_{t-2}
  ////                            dist_probs[index] = dist_probs[index] + (double) 1 / 2 * dist_unpaired_pre.getProbability(k * MAPvarNStates + i);
  ////                        }
  ////                    }
  ////                }
  ////                listCondDistributions.add(dist_probs);
  ////            }
  ////        }
  //
  //        return listCondDistributions;
  //    }
  
      /**
       * Returns the max value and its corresponding index.
       * @param values an array of {@code double} values.
       * @return the max value and its corresponding index.
       */
      private double[] argMax(double[] values) {
  
          double maxValue = Arrays.stream(values).max().getAsDouble();
          double indexMaxValue=-1;
  
          for (int i = 0; i < values.length; i++) {
              if (values[i]==maxValue) {
                  indexMaxValue=i;
              }
          }
          return new double[]{maxValue, indexMaxValue};
      }
  
      /**
       * Returns the replicated static set of variables
       * @param dynamicVariables a {@link DynamicVariables} object.
       * @param modelNumber a {@code int} that indicates the number of the model being constructed.
       * @return a {@link Variables} object.
       */
      private Variables obtainReplicatedStaticVariables(DynamicVariables dynamicVariables, int modelNumber) {
  
          Variables variables = new Variables();
  
          // REPLICATIONS OF THE MAP VARIABLE (EACH 'nMergedClassVars' CONSECUTIVE ARE GROUPED)
          int replicationsMAPVariable = (modelNumber==0 ? 0 : 1) + (nTimeSteps-modelNumber)/nMergedClassVars + ((nTimeSteps-modelNumber)%nMergedClassVars==0 ? 0 : 1);
  
          IntStream.range(0, replicationsMAPVariable).forEach(mergedClassVarIndex -> {
  
              int nStatesMAPVariable = (int) Math.pow(MAPvariable.getNumberOfStates(),nMergedClassVars);
  
              // If it is the first merged variable and not the first model (not 'complete')
              if ( (modelNumber!=0) && (mergedClassVarIndex==0) ) {
                  nStatesMAPVariable = (int) Math.pow(MAPvariable.getNumberOfStates(),modelNumber);
              }
              // If it is the last merged variable and not 'complete'
              if ( ((nTimeSteps-modelNumber)%nMergedClassVars!=0) && (mergedClassVarIndex==replicationsMAPVariable-1) ) {
                  nStatesMAPVariable = (int) Math.pow(MAPvariable.getNumberOfStates(),(nTimeSteps-modelNumber)%nMergedClassVars);
              }
  
              variables.newMultinomialVariable(groupedClassName + "_t" + Integer.toString(mergedClassVarIndex), nStatesMAPVariable);
          });
  
          // REPLICATIONS OF THE REST OF VARIABLES (EACH ONE REPEATED 'nTimeSteps' TIMES)
          dynamicVariables.getListOfDynamicVariables().stream()
                  .filter(var -> !var.equals(MAPvariable))
                  .forEach(dynVar ->
                          IntStream.range(0, nTimeSteps).forEach(i -> {
                              VariableBuilder aux = dynVar.getVariableBuilder();
                              aux.setName(dynVar.getName() + "_t" + Integer.toString(i));
                              variables.newVariable(aux);
                          })
                  );
          return variables;
      }
  
      /**
       * Returns the static DAG structure.
       * @param dynamicDAG a {@link DynamicDAG} object.
       * @param variables a {@link Variables} object.
       * @param modelNumber an integer
       * @return a {@link DAG} object.
       */
      private DAG obtainStaticDAG(DynamicDAG dynamicDAG, Variables variables, int modelNumber) {
  
          DAG dag = new DAG(variables);
          DynamicVariables dynamicVariables = dynamicDAG.getDynamicVariables();
  
          /*
           * PARENTS OF THE MAP VARIABLE (ONLY THE PREVIOUS TEMPORAL COPY OF ITSELF)
           */
          int replicationsMAPVariable = (modelNumber==0 ? 0 : 1) + (nTimeSteps-modelNumber)/nMergedClassVars + ((nTimeSteps-modelNumber)%nMergedClassVars==0 ? 0 : 1);
  
          IntStream.range(1, replicationsMAPVariable).forEach(mergedClassVarIndex -> {
              Variable staticVar = variables.getVariableByName(groupedClassName + "_t" + Integer.toString(mergedClassVarIndex));
              dag.getParentSet(staticVar).addParent(variables.getVariableByName(groupedClassName + "_t" + Integer.toString(mergedClassVarIndex - 1)));
          });
  
          /*
           * PARENTS OF THE REST OF VARIABLES
           */
          dynamicVariables.getListOfDynamicVariables().stream()
                  .filter(var -> !var.equals(MAPvariable))
                  .forEach(dynVar -> {
  
                      // ADD PARENTS AT TIME T=0
                      Variable staticVar0 = variables.getVariableByName(dynVar.getName() + "_t0");
  
                      List<Variable> parents0 = dynamicDAG.getParentSetTime0(dynVar).getParents();
  
                      parents0.stream().filter(parent -> parent.equals(MAPvariable)).forEach(parentaux2 -> dag.getParentSet(staticVar0).addParent(variables.getVariableByName(groupedClassName + "_t0")));
                      parents0.stream().filter(parent -> !parent.equals(MAPvariable)).forEach(parentaux2 -> dag.getParentSet(staticVar0).addParent(variables.getVariableByName(parentaux2.getName() + "_t0")));
  
                      // ADD PARENTS AT TIMES T>0
                      IntStream.range(1, nTimeSteps).forEach(timeStep -> {
  
                          Variable staticVar = variables.getVariableByName(dynVar.getName() + "_t" + Integer.toString(timeStep));
  
                          List<Variable> parents = dynamicDAG.getParentSetTimeT(dynVar).getParents();
  
                          int indexMAPReplication = (timeStep>=modelNumber) ? (modelNumber==0 ? 0 : 1) + (timeStep-modelNumber)/nMergedClassVars : (timeStep-modelNumber)/nMergedClassVars;
  
                          if (indexMAPReplication>=replicationsMAPVariable) {
                              System.out.println("Error in obtainStaticDAG: Bad MAP variable index");
                              System.exit(-50);
                          }
  
                          // PARENTS WHICH ARE INTERFACE VARIABLES
                          List<Variable> parentsInterface = parents.stream().filter(parentVar -> parentVar.isInterfaceVariable()).collect(Collectors.toList());
  
                          parentsInterface.stream().filter(parent -> parent.equals(MAPvariable)).forEach(parentVar -> dag.getParentSet(staticVar).addParent(variables.getVariableByName(groupedClassName + "_t" + Integer.toString(indexMAPReplication - 1))));
                          parentsInterface.stream().filter(parent -> !parent.equals(MAPvariable)).forEach(parentVar -> dag.getParentSet(staticVar).addParent(variables.getVariableByName(parentVar.getName().replace("_Interface", "_t" + Integer.toString(timeStep - 1)))));
  
                          // PARENTS WHICH ARE NOT INTERFACE VARIABLES
                          List<Variable> parentsNotInterface = parents.stream().filter(parentVar -> !parentVar.isInterfaceVariable()).collect(Collectors.toList());
  
                          parentsNotInterface.stream().filter(parent -> parent.equals(MAPvariable)).forEach(parentVar -> dag.getParentSet(staticVar).addParent(variables.getVariableByName(groupedClassName + "_t" + Integer.toString(indexMAPReplication))));
                          parentsNotInterface.stream().filter(parent -> !parent.equals(MAPvariable)).forEach(parentVar -> dag.getParentSet(staticVar).addParent(variables.getVariableByName(parentVar.getName() + "_t" + Integer.toString(timeStep))));
  
                      });
                  });
          return dag;
      }
  
      /**
       * Returns the grouped Distribution of the MAP Variable at Time 0.
       * @param dynVar the dynamic {@link Variable} object.
       * @param staticVar the static {@link Variable} object.
       * @param conDist0 the {@link ConditionalDistribution} at time 0.
       * @param conDistT the {@link ConditionalDistribution} at time T.
       * @return a {@link Multinomial} distribution.
       */
      private Multinomial groupedDistributionMAPVariableTime0(Variable dynVar, Variable staticVar, ConditionalDistribution conDist0, ConditionalDistribution conDistT, int modelNumber) {
  
          if(modelNumber == 1) {
              return (Multinomial) conDist0;
          }
  
          Assignment assignment0;
          assignment0 = new HashMapAssignment(1);
  
          int nStates = dynVar.getNumberOfStates();
          int nMergedStates = staticVar.getNumberOfStates();
  
          Multinomial multinomial = new Multinomial(staticVar);
  
          double[] probs = new double[nMergedStates];
          int probs_index=0;
  
          int repetitionsConDistT = (int)Math.round(Math.log(nMergedStates)/Math.log(nStates)-1);
  
          Assignment assignment1;
          for (int k = 0; k < nStates; k++) {
  
              // Probabilities at t=0
              assignment0.setValue(dynVar, k);
              double prob0 = conDist0.getConditionalProbability(assignment0);
  
              for (int m = 0; m < Math.pow(nStates,repetitionsConDistT); m++) {
                  String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(m), 10), nStates);
                  m_base_nStates = StringUtils.leftPad(m_base_nStates, repetitionsConDistT, '0');
                  //System.out.println(m_base_nStates);
  
                  double probT=1;
                  for ( int n=0; n < m_base_nStates.length(); n++) {
                      int currentState = Integer.parseInt(m_base_nStates.substring(n,n+1));
  
                      int previousState;
                      if(n>=1)
                          previousState = Integer.parseInt(m_base_nStates.substring(n-1,n));
                      else
                          previousState = k;
  
                      assignment1 = new HashMapAssignment(2);
                      assignment1.setValue(dynVar.getInterfaceVariable(), previousState);
                      assignment1.setValue(dynVar,currentState);
                      probT = probT * conDistT.getConditionalProbability(assignment1);
                  }
                  probs[probs_index] =  prob0 * probT;
                  probs_index++;
  
              }
          }
          multinomial.setProbabilities(probs);
          return multinomial;
      }
  
      /**
       * Returns the grouped Distribution of the MAP Variable at Time 0.
       * @param dynVar the dynamic {@link Variable} object.
       * @param staticVar the static {@link Variable} object.
       * @param nStatesStaticVarParent the number of static variable parents.
       * @param parents the {@code List} of parent {@link Variable}s.
       * @param conDistT the {@link ConditionalDistribution} at time T.
       * @return a {@link Multinomial_MultinomialParents} distribution.
       */
      private Multinomial_MultinomialParents groupedDistributionMAPVariableTimeT(Variable dynVar, Variable staticVar, int nStatesStaticVarParent, List<Variable> parents, ConditionalDistribution conDistT, int modelNumber) {
  
          Multinomial_MultinomialParents multinomial_multinomialParents = new Multinomial_MultinomialParents(staticVar, parents);
          Assignment assignment1;
          Multinomial multinomial;
  
          int nStates = dynVar.getNumberOfStates();
          int nMergedStates = staticVar.getNumberOfStates();
  
          int repetitionsConDistT = (int)Math.round(Math.log(nMergedStates)/Math.log(nStates));
  
          for (int s = 0; s < nStatesStaticVarParent; s++) {
              int parentState = s % nStates;
  
              double[] probs1  = new double[nMergedStates];
              int probs_index1 = 0;
  
              for (int m = 0; m < Math.pow(nStates,repetitionsConDistT); m++) {
                  String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(m), 10), nStates);
                  m_base_nStates = StringUtils.leftPad(m_base_nStates, repetitionsConDistT, '0');
  
                  double probT=1;
                  for ( int n=0; n < m_base_nStates.length(); n++) {
                      int currentState = Integer.parseInt(m_base_nStates.substring(n,n+1));
  
                      int previousState;
                      if(n>=1)
                          previousState = Integer.parseInt(m_base_nStates.substring(n-1,n));
                      else
                          previousState = parentState;
  
                      assignment1 = new HashMapAssignment(2);
                      assignment1.setValue(dynVar.getInterfaceVariable(), previousState);
                      assignment1.setValue(dynVar,currentState);
  
  
                      probT = probT * conDistT.getConditionalProbability(assignment1);
                  }
                  probs1[probs_index1] =  probT;
                  probs_index1++;
              }
  
              multinomial = new Multinomial(staticVar);
              multinomial.setProbabilities(probs1);
              multinomial_multinomialParents.setMultinomial(s, multinomial);
          }
          return multinomial_multinomialParents;
      }
  
      /**
       * Returns the {@link BayesianNetwork} related to the static grouped class.
       * @param dag a {@link DAG} object.
       * @param variables a {@link Variables} obejct.
       * @param modelNumber an integer
       * @return a {@link BayesianNetwork} object.
       */
      private BayesianNetwork obtainStaticMergedClassVarNetwork(DAG dag, Variables variables, int modelNumber) {
  
          DynamicDAG dynamicDAG = model.getDynamicDAG();
          BayesianNetwork bn = new BayesianNetwork(dag);
          Variable staticVar, dynVar;
          ConditionalDistribution conDist0, conDistT;
  
          int replicationsMAPVariable = (modelNumber==0 ? 0 : 1) + (nTimeSteps-modelNumber)/nMergedClassVars + ((nTimeSteps-modelNumber)%nMergedClassVars==0 ? 0 : 1);
  
  
          /*
           * OBTAIN AND SET THE CONDITIONAL (UNIVARIATE) DISTRIBUTION FOR THE GROUPED MAP/CLASS VARIABLE AT TIME T=0
           */
          staticVar = variables.getVariableByName(groupedClassName + "_t0");
          dynVar = model.getDynamicVariables().getVariableByName(MAPvarName);
  
          conDist0 = Serialization.deepCopy(model.getConditionalDistributionsTime0().get(dynVar.getVarID()));
          conDistT = Serialization.deepCopy(model.getConditionalDistributionsTimeT().get(dynVar.getVarID()));
  
          Multinomial multinomial = groupedDistributionMAPVariableTime0(dynVar, staticVar, conDist0, conDistT, modelNumber);
  
          multinomial.setVar(staticVar);
          bn.setConditionalDistribution(staticVar, multinomial);
  
  
          /*
           * CREATE THE GENERAL (TIME T) CONDITIONAL DISTRIBUTION OF THE GROUPED MAP/CLASS VARIABLE, IF NEEDED
           */
          Multinomial_MultinomialParents generalConditionalDistTimeT;
  
          // ToDo: Review Condition: nTimeSteps>= 4,5
          if (modelNumber==0 && (replicationsMAPVariable>2 || (replicationsMAPVariable==2 && nTimeSteps>=4))) {
              Variable staticVar_current = variables.getVariableByName(groupedClassName + "_t1");
              Variable staticVar_interface = variables.getVariableByName(groupedClassName + "_t0");
              List<Variable> parents = bn.getDAG().getParentSet(staticVar_current).getParents();
              ConditionalDistribution conDist_dynamic = Serialization.deepCopy(conDistT);
  
              generalConditionalDistTimeT = groupedDistributionMAPVariableTimeT(dynVar, staticVar_current, staticVar_interface.getNumberOfStates(), parents, conDist_dynamic, modelNumber);
  
          }
          else if (modelNumber>0 && (replicationsMAPVariable>3 || replicationsMAPVariable==3 && nTimeSteps>=5)) {
              Variable staticVar_current = variables.getVariableByName(groupedClassName + "_t2");
              Variable staticVar_interface = variables.getVariableByName(groupedClassName + "_t1");
              List<Variable> parents = bn.getDAG().getParentSet(staticVar_current).getParents();
              ConditionalDistribution conDist_dynamic = Serialization.deepCopy(conDistT);
  
              generalConditionalDistTimeT = groupedDistributionMAPVariableTimeT(dynVar, staticVar_current, staticVar_interface.getNumberOfStates(), parents, conDist_dynamic, modelNumber);
  
          }
          else { // In this case, 'generalConditionalDistTimeT' will never be used.
              generalConditionalDistTimeT = new Multinomial_MultinomialParents(staticVar, bn.getDAG().getParentSet(staticVar).getParents());
          }
  
  
          /*
           * ADD CONDITIONAL DISTRIBUTIONS FOR THE REPLICATIONS OF THE GROUPED MAP/CLASS VARIABLE
           */
          // FIRST CONDITIONAL DISTRIBUTION, t_1 | t_0, IF IT'S NOT THE GENERAL ONE
          if (modelNumber!=0) {
              Variable staticVar0 = variables.getVariableByName(groupedClassName + "_t1");
              Variable staticVar0_interface = variables.getVariableByName(groupedClassName + "_t0");
              List<Variable> parents = bn.getDAG().getParentSet(staticVar0).getParents();
              ConditionalDistribution conDist_dynamic = Serialization.deepCopy(model.getConditionalDistributionsTimeT().get(dynVar.getVarID()));
              ConditionalDistribution conditionalDistTime1 = groupedDistributionMAPVariableTimeT(dynVar, staticVar0, staticVar0_interface.getNumberOfStates(), parents, conDist_dynamic, modelNumber);
              conditionalDistTime1.setVar(staticVar0);
              bn.setConditionalDistribution(staticVar0, conditionalDistTime1);
          }
  
          // INTERMEDIATE COMPLETE CONDITIONAL DISTRIBUTIONS, t_i | t_{i-1}, FOLLOWING THE GENERAL CONDITIONAL DISTRIBUTION
          int initialTimeStep=2;
          int finalTimeStep=replicationsMAPVariable-1;
  
          if (modelNumber==0)
              initialTimeStep = 1;
          if ((nTimeSteps-modelNumber)%nMergedClassVars==0)
              finalTimeStep = replicationsMAPVariable;
  
          IntStream.range(initialTimeStep, finalTimeStep).forEach(timeStep -> {
              Variable staticVar1 = variables.getVariableByName(groupedClassName + "_t" + Integer.toString(timeStep));
              ConditionalDistribution conditionalDistribution = Serialization.deepCopy(generalConditionalDistTimeT);
              conditionalDistribution.setConditioningVariables(dag.getParentSet(staticVar1).getParents());
              conditionalDistribution.setVar(staticVar1);
              bn.setConditionalDistribution(staticVar1, conditionalDistribution);
  
          });
  
  
          // LAST CONDITIONAL DISTRIBUTION, t_{nTimeSteps} | t_{nTimeSteps-1}, IF IT'S NOT THE GENERAL ONE
          if ((nTimeSteps-modelNumber)%nMergedClassVars!=0 ) {
  
              Variable staticVar1 = variables.getVariableByName(groupedClassName + "_t" + Integer.toString(replicationsMAPVariable - 1));
              Variable staticVar1_interface = variables.getVariableByName(groupedClassName + "_t" + Integer.toString(replicationsMAPVariable - 2));
              List<Variable> parents1 = bn.getDAG().getParentSet(staticVar1).getParents();
  
              Multinomial_MultinomialParents lastConDist = groupedDistributionMAPVariableTimeT(dynVar, staticVar1, staticVar1_interface.getNumberOfStates(), parents1, conDistT, modelNumber);
  
              bn.setConditionalDistribution(staticVar1, lastConDist);
          }
  
          /*
           * ADD CONDITIONAL DISTRIBUTIONS FOR VARIABLES HAVING AS A PARENT THE GROUPED MAP/CLASS VARIABLE, AT TIME T=0
           */
          List<Variable> dynVariables = model.getDynamicVariables().getListOfDynamicVariables();
          List<Variable> dynVariablesWithClassParent = dynVariables.stream().filter(var -> !var.equals(MAPvariable)).filter(var -> dynamicDAG.getParentSetTime0(var).contains(MAPvariable)).collect(Collectors.toList());
          List<Variable> dynVariablesNoClassParent = dynVariables.stream().filter(var -> !var.equals(MAPvariable)).filter(var -> !dynamicDAG.getParentSetTime0(var).contains(MAPvariable)).collect(Collectors.toList());
  
  
  //        dynVariablesWithClassParent.stream().forEach(dynVariable -> {
  //            ConditionalDistribution conditionalDistribution = Serialization.deepCopy(model.getConditionalDistributionTime0(dynVariable));
  //
  //            Variable staticMAPVar1 = variables.getVariableByName(groupedClassName + "_t0");
  //            Variable staticVar1 = variables.getVariableByName(dynVariable.getName() + "_t0");
  //            List<Variable> thisVarParents = conditionalDistribution.getConditioningVariables();
  //            List<Variable> parentList0 = bn.getDAG().getParentSet(staticVar1).getParents();
  //            int indexMAPvariable = thisVarParents.indexOf(MAPvariable);
  //
  //            thisVarParents.set(indexMAPvariable, staticMAPVar1);
  //
  //
  //            conditionalDistribution.setConditioningVariables(parentList0);
  //            conditionalDistribution.setVar(staticVar1);
  //
  ////            if(modelNumber==1)
  ////                bn.setConditionalDistribution(staticVar1, conditionalDistribution);
  ////            else {
  //            BaseDistribution_MultinomialParents staticVar2Distribution = obtainDistributionOfMAPChildren(staticVar1, conditionalDistribution, parentList0, modelNumber, 0);
  //            bn.setConditionalDistribution(staticVar1, staticVar2Distribution);
  ////            }
  //
  //        });
  
  
          /*
           * ADD CONDITIONAL DISTRIBUTIONS FOR VARIABLES HAVING AS A PARENT THE GROUPED MAP/CLASS VARIABLE, AT ANY TIME T
           */
          dynVariablesWithClassParent.stream().forEach(dynVariable -> {
              IntStream.range(0, nTimeSteps).forEachOrdered(timeStep -> {
  
                  ConditionalDistribution dynamicConDist;
                  dynamicConDist = Serialization.deepCopy( timeStep==0 ? model.getConditionalDistributionTime0(dynVariable) : model.getConditionalDistributionTimeT(dynVariable) );
  //                )if(timeStep==0) {
  //                     = Serialization.deepCopy(model.getConditionalDistributionTime0(dynVariable));
  //                }
  //                else {
  //                    dynamicConDist = Serialization.deepCopy(model.getConditionalDistributionTimeT(dynVariable));
  //                }
                  Variable staticVar2 = variables.getVariableByName(dynVariable.getName() + "_t" + Integer.toString(timeStep));
                  List<Variable> parentList = bn.getDAG().getParentSet(staticVar2).getParents();
  
                  ConditionalDistribution staticVar2Distribution = obtainDistributionOfMAPChildren(staticVar2, dynamicConDist, parentList, modelNumber, timeStep);
                  bn.setConditionalDistribution(staticVar2, staticVar2Distribution);
              });
          });
  
  
          /*
           * ADD CONDITIONAL DISTRIBUTIONS FOR VARIABLES NOT HAVING AS A PARENT THE GROUPED MAP/CLASS VARIABLE, AT ANY TIME T
           */
          dynVariablesNoClassParent.stream().forEach(dynVariable -> {
  
              // TIME T=0
              ConditionalDistribution conditionalDistribution = Serialization.deepCopy(model.getConditionalDistributionTime0(dynVariable));
              Variable staticVar1 = variables.getVariableByName(dynVariable.getName() + "_t0");
              List<Variable> thisVarParents = conditionalDistribution.getConditioningVariables();
              thisVarParents = thisVarParents.stream().map(parent -> variables.getVariableByName(parent.getName() + "_t0")).collect(Collectors.toList());
  
              conditionalDistribution.setConditioningVariables(thisVarParents);
              conditionalDistribution.setVar(staticVar1);
              bn.setConditionalDistribution(staticVar1, conditionalDistribution);
  
              // TIMES T>0
              IntStream.range(1, nTimeSteps).forEach(i -> {
                  ConditionalDistribution conditionalDistribution1 = Serialization.deepCopy(model.getConditionalDistributionTimeT(dynVariable));
                  Variable staticVar2 = variables.getVariableByName(dynVariable.getName() + "_t" + Integer.toString(i));
                  List<Variable> thisVarParents1 = conditionalDistribution1.getConditioningVariables();
                  thisVarParents1 = thisVarParents1.stream().map(parent -> {
                      if(parent.getName().contains("_Interface")) {
                          return variables.getVariableByName(parent.getName().replace("_Interface","_t" + Integer.toString(i-1)));
                      }
                      else {
                          return variables.getVariableByName(parent.getName() + "_t" + Integer.toString(i));
                      }
                  }).collect(Collectors.toList());
                  conditionalDistribution1.setConditioningVariables(thisVarParents1);
                  conditionalDistribution1.setVar(staticVar2);
                  bn.setConditionalDistribution(staticVar2, conditionalDistribution1);
              });
          });
  
          return bn;
      }
  
      /**
       * Returns the distribution of MAP Children at time T.
       * @param staticVariable the static {@link Variable} object.
       * @param dynamicConditionalDistribution the dynamic {@link ConditionalDistribution} at time T.
       * @param parentList the {@code List} of parent {@link Variable}s.
       * @param modelNumber an integer
       * @param time_step an integer with the time step.
       * @return a {@link BaseDistribution_MultinomialParents} distribution.
       */
      private ConditionalDistribution obtainDistributionOfMAPChildren(Variable staticVariable, ConditionalDistribution dynamicConditionalDistribution, List<Variable> parentList, int modelNumber, int time_step) {
  
          boolean allParentsMultinomial = parentList.stream().allMatch(parent -> parent.isMultinomial());
          List<Variable> multinomialParents = parentList.stream().filter(parent -> parent.isMultinomial()).collect(Collectors.toList());
          List<Variable> continuousParents = parentList.stream().filter(parent -> !parent.isMultinomial()).collect(Collectors.toList());
  
          //BaseDistribution_MultinomialParents staticVarConDist = new BaseDistribution_MultinomialParents(staticVariable, parentList);
  
          ConditionalDistribution staticVarConDist;
  
          // In this method, all variables have at least one parent (either discrete or continuous)
          int distributionType = -1;
          if (staticVariable.isMultinomial()) {
              distributionType = 0;
              staticVarConDist = new Multinomial_MultinomialParents(staticVariable, parentList);
          }
          else if (staticVariable.isNormal()) {
  
              int nMultinomialParents = multinomialParents.size();
              int nNormalParents = continuousParents.size();
  
              if (nNormalParents>0 && nMultinomialParents==0) {
                  distributionType = 1;
                  staticVarConDist = new ConditionalLinearGaussian(staticVariable, parentList);
              }
              else if (nNormalParents==0 && nMultinomialParents>0) {
                  distributionType = 2;
                  staticVarConDist = new Normal_MultinomialParents(staticVariable, parentList);
              }
              else if (nNormalParents>0 && nMultinomialParents>0) {
                  distributionType = 3;
                  staticVarConDist = new Normal_MultinomialNormalParents(staticVariable, parentList);
              }
              else {
                  throw new IllegalArgumentException("Unrecognized DistributionType. ");
              }
          }
          else {
              throw new IllegalArgumentException("Unrecognized DistributionType. ");
          }
  
          int nStatesMultinomialParents = (int) Math.round(Math.exp(multinomialParents.stream().mapToDouble(parent -> Math.log(parent.getNumberOfStates())).sum()));
          int nStatesMAPVariable = MAPvariable.getNumberOfStates();
  
          for (int m = 0; m < nStatesMultinomialParents; m++) {
              Assignment staticParentsConfiguration = MultinomialIndex.getVariableAssignmentFromIndex(multinomialParents, m);
              Assignment dynamicParentsConfiguration = new HashMapAssignment(multinomialParents.size());
  
              IntStream.range(0, multinomialParents.size()).forEach(k -> {
                  Variable currentParent = multinomialParents.get(k);
                  int parentValue = (int)staticParentsConfiguration.getValue(currentParent);
                  String parentName;
  
                  if (currentParent.getName().contains(groupedClassName)) {
                      parentName = currentParent.getName().replace(groupedClassName, MAPvarName).replaceAll("_t\\d+", "");
                      Variable dynCurrentParent = model.getDynamicVariables().getVariableByName(parentName);
  
                      int dynParentValue;
  
  
                      int nMergedStates = currentParent.getNumberOfStates();
                      int repetitionsConDistT = (int)Math.round(Math.log(nMergedStates)/Math.log(nStatesMAPVariable));
                      int indexCurrentParentState;
                      if(time_step>=modelNumber)
                          indexCurrentParentState = (time_step - modelNumber)%nMergedClassVars;
                      else
                          indexCurrentParentState = time_step;
  
                      String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(parentValue), 10), nStatesMAPVariable);
                      m_base_nStates = StringUtils.leftPad(m_base_nStates, repetitionsConDistT, '0');
  
                      int dynamicParentState = Integer.parseInt(m_base_nStates.substring(indexCurrentParentState,indexCurrentParentState+1));
  
  
                      dynParentValue = dynamicParentState;
                      dynamicParentsConfiguration.setValue(dynCurrentParent, dynParentValue);
  
  //                    System.out.println("Variable: " + staticVariable.getName() + " with " + staticVariable.getNumberOfStates() + " states and " + parentList.size() + " parents");
  //                    System.out.println("Parent " + parentName + " with " + nMergedStates + " states");
  //                    System.out.println("Time step " + time_step + " and model number " + modelNumber);
  //                    System.out.println("Parent state number " + parentValue + " which is " + m_base_nStates);
  //                    System.out.println("Index parent state " + indexCurrentParentState);
  //                    System.out.println("Dynamic parent state number " + dynamicParentState);
  //                    System.out.println();
  //                    if (time_step==0) { // Variable at time t=0
  //                        if(modelNumber!=1) {
  //                            //dynParentValue = parentValue / (int) Math.pow(nStatesMAPVariable, nMergedClassVars - 1);
  ////                            System.out.println(currentParent.getNumberOfStates());
  ////                            System.out.println(nStatesMAPVariable);
  ////                            System.out.println(parentValue);
  ////                            System.out.println(parentValue / (currentParent.getNumberOfStates()/nStatesMAPVariable));
  //                            dynParentValue = parentValue / (int) Math.pow(nStatesMAPVariable, nMergedClassVars - 1);
  //                        }
  //                        else {
  //                            dynParentValue = parentValue;
  //                        }
  //                    } // Variable at time t=nTimeSteps-1 (last copy) and not complete
  //                    else {
  //
  //
  //                        if ((time_step - modelNumber) % nMergedClassVars != 0 && (time_step == nTimeSteps - 1)) {
  //                            dynParentValue = parentValue % nStatesMAPVariable;
  //                        } else {
  //                            if ((time_step - modelNumber) % nMergedClassVars == 0) {
  //                                dynParentValue = parentValue / (currentParent.getNumberOfStates() / nStatesMAPVariable);
  //                                ;
  //                            } else {
  //                                dynParentValue = parentValue % nStatesMAPVariable;
  //                            }
  //                        }
  //                    }
  //                        if ((!even_partition && nTimeSteps % 2 == 0 && (time_step == nTimeSteps - 1)) || (even_partition && nTimeSteps % 2 == 1 && (time_step == nTimeSteps - 1))) {
  //                            dynParentValue = parentValue;
  //                        } else {
  //                            if ((!even_partition && (time_step % 2 == 1)) || (even_partition && (time_step % 2 == 0))) {
  //                                dynParentValue = parentValue / MAPvariable.getNumberOfStates();
  //                            } else {
  //                                dynParentValue = parentValue % MAPvariable.getNumberOfStates();
  //                            }
  //                        }
                  }
                  else {
                      if (multinomialParents.get(k).getName().endsWith("_t" + Integer.toString(time_step - 1))) {
                          parentName = multinomialParents.get(k).getName().replaceFirst("_t\\d+", "");
                          Variable dynParent = model.getDynamicVariables().getVariableByName(parentName);
                          dynamicParentsConfiguration.setValue(dynParent.getInterfaceVariable(), parentValue);
                      }
                      else {
                          parentName = multinomialParents.get(k).getName().replaceFirst("_t\\d+", "");
                          Variable dynParent = model.getDynamicVariables().getVariableByName(parentName);
                          dynamicParentsConfiguration.setValue(dynParent, parentValue);
                      }
                  }
              });
  
  //            System.out.println(dynamicParentsConfiguration.outputString());
  
  //            if (allParentsMultinomial && staticVariable.isMultinomial()) {
  ////                try {
  //
  //                Multinomial_MultinomialParents multinomial_multinomialParents = (Multinomial_MultinomialParents) dynamicConditionalDistribution;
  //                Multinomial multinomial1 = (Multinomial) multinomial_multinomialParents.getMultinomial(dynamicParentsConfiguration);
  //
  //                multinomial1.setVar(staticVariable);
  //                multinomial1.setConditioningVariables(multinomialParents);
  //
  ////                System.out.println(multinomial1.toString()+"\n\n");
  //                staticVarConDist.setBaseDistribution(m, multinomial1);
  //                staticVarConDist.set
  ////                }
  ////                catch(Exception e) {
  ////                    System.out.println("Exception");
  ////                    System.out.println(e.getMessage());
  ////                    System.out.println(staticVariable.getName());
  ////                    System.out.println(dynamicParentsConfiguration.outputString());
  ////                }
  //            }
  //            else if (allParentsMultinomial && staticVariable.isNormal() ){
  //                Normal_MultinomialParents normal_multinomialParents = (Normal_MultinomialParents) dynamicConditionalDistribution;
  //                Normal clg = normal_multinomialParents.getNormal(dynamicParentsConfiguration);
  //                clg.setConditioningVariables(multinomialParents);
  //                //clg.setConditioningVariables(continuousParents);
  //                clg.setVar(staticVariable);
  //
  //                staticVarConDist.setBaseDistribution(m, clg);
  //            }
  //            else {
  //                Normal_MultinomialNormalParents normal_multinomialNormalParents = (Normal_MultinomialNormalParents) dynamicConditionalDistribution;
  //                ConditionalLinearGaussian clg = normal_multinomialNormalParents.getNormal_NormalParentsDistribution(dynamicParentsConfiguration);
  //                clg.setConditioningVariables(continuousParents);
  //                clg.setVar(staticVariable);
  //
  //                staticVarConDist.setBaseDistribution(m, clg);
  //            }
  
  
              if (distributionType==0) { // Multinomial_Multinomial
  
                  Multinomial_MultinomialParents multinomial_multinomialParents = (Multinomial_MultinomialParents) dynamicConditionalDistribution;
                  Multinomial multinomial1 = (Multinomial) multinomial_multinomialParents.getMultinomial(dynamicParentsConfiguration);
  
                  multinomial1.setVar(staticVariable);
                  multinomial1.setConditioningVariables(multinomialParents);
  
                  ((Multinomial_MultinomialParents)staticVarConDist).setMultinomial(m, multinomial1);
  
              }
              else if (distributionType==2 ){ // Normal_Multinomial
  
                  Normal_MultinomialParents normal_multinomialParents = (Normal_MultinomialParents) dynamicConditionalDistribution;
  
                  Normal normal1 = normal_multinomialParents.getNormal(dynamicParentsConfiguration);
                  normal1.setConditioningVariables(multinomialParents);
                  //clg.setConditioningVariables(continuousParents);
                  normal1.setVar(staticVariable);
  
                  ((Normal_MultinomialParents)staticVarConDist).setNormal(m, normal1);
  
              }
              else if (distributionType==3) { // Normal_MultinomialNormal
  
                  Normal_MultinomialNormalParents normal_multinomialNormalParents = (Normal_MultinomialNormalParents) dynamicConditionalDistribution;
                  ConditionalLinearGaussian clg = normal_multinomialNormalParents.getNormal_NormalParentsDistribution(dynamicParentsConfiguration);
                  clg.setConditioningVariables(continuousParents);
                  clg.setVar(staticVariable);
  
                  ((Normal_MultinomialNormalParents)staticVarConDist).setNormal_NormalParentsDistribution(m, clg);
  
              }
              else { // ConditionalLinearGaussian, distributionType==1
                  ConditionalLinearGaussian clg = (ConditionalLinearGaussian) dynamicConditionalDistribution;
                  //((ConditionalLinearGaussian)staticVarConDist)
                  staticVarConDist = clg;
              }
  
          }
  
  //        if (allParentsMultinomial && staticVariable.isNormal())
  //            return (Normal_MultinomialParents)staticVarConDist;
  //        else {
  //            return staticVarConDist;
  //        }
          return staticVarConDist;
      }
  
  
  
  //    public static Iterator<List<DynamicAssignment>> generateEvidence(DynamicBayesianNetwork dynamicBayesianNetwork, String mapVariableName, int numberOfSequences, int sequenceLength, double percentageOfEvidence, int seed) {
  //
  //        Random random = new Random(seed);
  //        Variable mapVariable = dynamicBayesianNetwork.getDynamicVariables().getVariableByName(mapVariableName);
  //        List<List<DynamicAssignment>> evidences = new ArrayList<>(numberOfSequences);
  //
  //        int nVarsEvidence = (int)(percentageOfEvidence * dynamicBayesianNetwork.getNumberOfVars());
  //
  //
  //        //DynamicBayesianNetworkSampler sampler = new DynamicBayesianNetworkSampler(dynamicBayesianNetwork);
  //        //DataStream<DynamicDataInstance> sample = sampler.sampleToDataBase(numberOfSequences,sequenceLength);
  //
  //        for (int i = 0; i < numberOfSequences; i++) {
  //
  //
  //            List<Variable> varsDynamicModel = dynamicBayesianNetwork.getDynamicVariables().getListOfDynamicVariables();
  //
  //            if (nVarsEvidence > varsDynamicModel.size()-1) {
  //                System.out.println("Too many variables to be observe");
  //                return null;
  //            }
  //
  //            List<Variable> varsEvidence = new ArrayList<>(nVarsEvidence);
  //
  //            int currentVarsEvidence=0;
  //            while (currentVarsEvidence < nVarsEvidence) {
  //                int indexVarEvidence = random.nextInt(dynamicBayesianNetwork.getNumberOfDynamicVars());
  //                Variable varEvidence = varsDynamicModel.get(indexVarEvidence);
  //
  //                if (varEvidence.equals(mapVariable) || varsEvidence.contains(varEvidence)) {
  //                    continue;
  //                }
  //                varsEvidence.add(varEvidence);
  //                currentVarsEvidence++;
  //            }
  //
  //            List<DynamicAssignment> fullEvidence, evidence;
  //            evidence = new ArrayList<>(sequenceLength);
  //
  //            DynamicBayesianNetworkSampler bayesianNetworkSampler0 = new DynamicBayesianNetworkSampler(dynamicBayesianNetwork);
  //            bayesianNetworkSampler0.setSeed(random.nextInt());
  //            DataStream<DynamicDataInstance> sample = bayesianNetworkSampler0.sampleToDataBase(1,sequenceLength);
  //
  //            //sample.getAttributes().getFullListOfAttributes().forEach(attribute -> System.out.println(attribute.getName()));
  //            fullEvidence = sample.stream().collect(Collectors.toList());
  //
  ////        System.out.println("Full Sample:");
  ////        fullEvidence.stream().forEachOrdered(dynass -> System.out.println(dynass.outputString(dynamicBayesianNetwork.getDynamicVariables().getListOfDynamicVariables())));
  ////        System.out.println();
  //
  //
  //            // Evidence in t=0, 1, ..., nTimeSteps-2 for variables in 'varsEvidence'
  //            IntStream.range(0,sequenceLength-1).forEachOrdered(t -> {
  //                HashMapDynamicAssignment dynamicAssignment = new HashMapDynamicAssignment(varsEvidence.size());
  //                dynamicAssignment.setSequenceID(0);
  //                dynamicAssignment.setTimeID(t);
  //                varsDynamicModel.stream().filter(variable -> varsEvidence.contains(variable)).forEach( variable -> dynamicAssignment.setValue(variable,fullEvidence.get(t).getValue(variable)));
  //                evidence.add(dynamicAssignment);
  //            });
  //
  //            // Evidence in t=(nTimeSteps-1) for all variables that are leaves in the DAG.
  //            for (Variable currentVar : varsDynamicModel) {
  //                if (!varsDynamicModel.stream().anyMatch(var -> dynamicBayesianNetwork.getDynamicDAG().getParentSetTimeT(var).getParents().contains(currentVar))) {
  //                    varsEvidence.add(currentVar);
  //                }
  //            }
  //            HashMapDynamicAssignment dynamicAssignment1 = new HashMapDynamicAssignment(varsEvidence.size());
  //            dynamicAssignment1.setSequenceID(0);
  //            dynamicAssignment1.setTimeID(sequenceLength-1);
  //            varsDynamicModel.stream().filter(variable -> varsEvidence.contains(variable)).forEach( variable -> dynamicAssignment1.setValue(variable,fullEvidence.get(sequenceLength-1).getValue(variable)));
  //            evidence.add(dynamicAssignment1);
  //
  ////        System.out.println("Evidence:");
  ////        evidence.stream().forEachOrdered(dynass -> System.out.println(dynass.outputString(dynamicBayesianNetwork.getDynamicVariables().getListOfDynamicVariables())));
  ////        System.out.println();
  //
  //            StringBuilder classVarSequenceBuilder = new StringBuilder();
  //            classVarSequenceBuilder.append("ClassVar: (");
  //            fullEvidence.stream().forEachOrdered(dynass -> classVarSequenceBuilder.append( Integer.toString((int)dynass.getValue(mapVariable)) + ","));
  //            classVarSequenceBuilder.replace(classVarSequenceBuilder.lastIndexOf(","),classVarSequenceBuilder.lastIndexOf(",")+1,"");
  //            classVarSequenceBuilder.append(")");
  //            System.out.println("Original sequence:");
  //            System.out.println(classVarSequenceBuilder.toString());
  //            System.out.println();
  //
  //
  //            evidences.add(evidence);
  //        }
  //
  //        return evidences.iterator();
  //    }
  
  
  
      public static void main(String[] arguments) throws IOException, ClassNotFoundException {
  
  //        String file = "./networks/CajamarDBN.dbn";
  //        DynamicBayesianNetwork cajamarDBN = DynamicBayesianNetworkLoader.loadFromFile(file);
  //
  //        DynamicMAPInference dynMAP = new DynamicMAPInference();
  //        dynMAP.setModel(cajamarDBN);
  //        dynMAP.setNumberOfTimeSteps(6);
  //
  //        cajamarDBN.getDynamicVariables().getListOfDynamicVariables().forEach(var -> System.out.println(var.getName()));
  //
  //        System.out.println(cajamarDBN.toString());
  //        System.out.println("CausalOrder: " + cajamarDBN.getDynamicDAG().toString());
  //
  //        System.out.println(cajamarDBN.getDynamicDAG().toString());
  //
  //        Variable mapVariable = cajamarDBN.getDynamicVariables().getVariableByName("DEFAULT");
  //        dynMAP.setMAPvariable(mapVariable);
  //
  //        dynMAP.computeDynamicMAPEvenModel();
  //        BayesianNetwork test = dynMAP.getStaticEvenModel();
  //
  //        System.out.println(test.getDAG().toString());
  //        System.out.println(cajamarDBN.toString());
  //
  //        dynMAP.runInference();
  
          DynamicMAPInference dynMAP;
          Variable mapVariable;
  
  
          DynamicBayesianNetworkGenerator.setNumberOfContinuousVars(2);
          DynamicBayesianNetworkGenerator.setNumberOfDiscreteVars(10);
          DynamicBayesianNetworkGenerator.setNumberOfStates(2);
          DynamicBayesianNetworkGenerator.setNumberOfLinks(5);
          int nStatesClassVar=2;
  
          DynamicBayesianNetwork dynamicBayesianNetwork = DynamicBayesianNetworkGenerator.generateDynamicNaiveBayes(new Random(50), nStatesClassVar, true);
  //
  //        System.out.println("ORIGINAL DYNAMIC NETWORK:");
  //
  //        System.out.println(dynamicBayesianNetwork.toString());
  //        System.out.println();
  
  
          dynMAP = new DynamicMAPInference();
          dynMAP.setModel(dynamicBayesianNetwork);
  
          // INITIALIZE THE MODEL
          int nTimeSteps = 5;
          dynMAP.setNumberOfTimeSteps(nTimeSteps);
          int nMergedClassVars = 3;
          dynMAP.setNumberOfMergedClassVars(nMergedClassVars);
  
          mapVariable = dynamicBayesianNetwork.getDynamicVariables().getVariableByName("ClassVar");
          dynMAP.setMAPvariable(mapVariable);
  
          System.out.println(dynamicBayesianNetwork.toString());
  //        System.out.println("ORIGINAL COND DISTRIBUTIONS MAP VARIABLE:");
  //        System.out.println(dynamicBayesianNetwork.getConditionalDistributionTime0(mapVariable).toString());
  //        System.out.println(dynamicBayesianNetwork.getConditionalDistributionTimeT(mapVariable).toString());
  //        System.out.println();
  
          dynMAP.computeMergedClassVarModels();
  
  //
  //        /*
  //         * GENERATE AN EVIDENCE FOR T=0,...,nTimeSteps-1
  //         */
  //        List<Variable> varsDynamicModel = dynamicBayesianNetwork.getDynamicVariables().getListOfDynamicVariables();
  //
  //        varsDynamicModel.forEach(var -> System.out.println("Var ID " + var.getVarID() + ": " + var.getName()));
  //        int indexVarEvidence1 = 2;
  //        int indexVarEvidence2 = 3;
  //        int indexVarEvidence3 = 8;
  //        Variable varEvidence1 = varsDynamicModel.get(indexVarEvidence1);
  //        Variable varEvidence2 = varsDynamicModel.get(indexVarEvidence2);
  //        Variable varEvidence3 = varsDynamicModel.get(indexVarEvidence3);
  //
  //        List<Variable> varsEvidence = new ArrayList<>(3);
  //        varsEvidence.add(0,varEvidence1);
  //        varsEvidence.add(1,varEvidence2);
  //        varsEvidence.add(2,varEvidence3);
  //
  //        double varEvidenceValue;
  //
  //        Random random = new Random(931234662);
  //
  //        List<DynamicAssignment> evidence = new ArrayList<>(nTimeSteps);
  //
  //        for (int t = 0; t < nTimeSteps; t++) {
  //            HashMapDynamicAssignment dynAssignment = new HashMapDynamicAssignment(varsEvidence.size());
  //
  //            for (int i = 0; i < varsEvidence.size(); i++) {
  //
  //                dynAssignment.setSequenceID(2343253);
  //                dynAssignment.setTimeID(t);
  //                Variable varEvidence = varsEvidence.get(i);
  //
  //                if (varEvidence.isMultinomial()) {
  //                    varEvidenceValue = random.nextInt(varEvidence1.getNumberOfStates());
  //                } else {
  //                    varEvidenceValue = -5 + 10 * random.nextDouble();
  //                }
  //                dynAssignment.setValue(varEvidence, varEvidenceValue);
  //            }
  //            evidence.add(dynAssignment);
  //        }
  //
  //        dynMAP.setEvidence(evidence);
  
          dynMAP.runInference();
  
  //
  //        System.out.println("\nMAP sequence: " + Arrays.toString(dynMAP.getMAPsequence()) + " with probability " + dynMAP.getMAPestimateProbability());
  //
  ////        Iterator<List<DynamicAssignment>> iterator = DynamicMAPInference.generateEvidence(dynamicBayesianNetwork,mapVariable.getName(),5,nTimeSteps,0.50,3634);
  ////        while(iterator.hasNext()) {
  ////            List<DynamicAssignment> evidence1 = iterator.next();
  ////            evidence1.forEach(assign -> System.out.println(assign.outputString()));
  ////        }
  
  
  //        int nStates = 3;
  //        int nRepets = 5;
  //
  //        for (int m = 0; m < Math.pow(nStates,nRepets); m++) {
  //            String m_base_nStates = Integer.toString(Integer.parseInt(Integer.toString(m), 10), nStates);
  //            m_base_nStates = StringUtils.leftPad(m_base_nStates, nRepets, '0');
  //            System.out.println(m_base_nStates);
  //            //System.out.println(String.format("%0" + nRepets + "d",m));
  //        }
  
      }
  
  
  }