<commit_before>package eu.amidst.dynamic.inference;

import eu.amidst.core.datastream.DataStream;
import eu.amidst.core.distribution.*;
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.datastream.DynamicDataInstance;
import eu.amidst.dynamic.models.DynamicBayesianNetwork;
import eu.amidst.dynamic.models.DynamicDAG;
import eu.amidst.dynamic.utils.DynamicBayesianNetworkGenerator;
import eu.amidst.dynamic.utils.DynamicBayesianNetworkSampler;
import eu.amidst.dynamic.variables.DynamicAssignment;
import eu.amidst.dynamic.variables.DynamicVariables;
import eu.amidst.dynamic.variables.HashMapDynamicAssignment;
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 {

    /**
     * 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 staticModel;

    /** 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__";


//    /**
//     * Sets the evidence for this DynamicMAPInference.
//     * @param evidence a list of {@link DynamicAssignment} objects.
//     */
//    public void setEvidence(List<DynamicAssignment> evidence) {
//
//        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 (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 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);
//                }
//
//            });
//        }
//    }

    /**
     * Sets the model for this DynamicMAPInference.
     * @param model a {@link DynamicBayesianNetwork} object.
     */
    public void setModel(DynamicBayesianNetwork model) {
        this.model = model;
    }

    /**
     * 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;
    }

    /**
     * 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);
        }
        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 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());
    }

    /**
     * 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();
        });
    }

//
//    /**
//     * 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.staticOddModel == null) {
//            this.computeDynamicMAPOddModel();
//        }
//        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.staticOddModel == null) {
//            this.computeDynamicMAPOddModel();
//        }
//        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);
//                }
//
//            });
//        }
//
//        InferenceAlgorithm evenModelInference, oddModelInference;
//        switch(searchAlgorithm) {
//            case VMP:
//                //        long timeStart = System.nanoTime();
//                evenModelInference = new VMP();
//                oddModelInference = new VMP();
//
//                break;
//
//            case IS:
//            default:
//
//                evenModelInference = new ImportanceSampling();
//                oddModelInference = new ImportanceSampling();
//
//                Random random = new Random((this.seed));
//                oddModelInference.setSeed(random.nextInt());
//                evenModelInference.setSeed(random.nextInt());
//                this.seed = random.nextInt();
//
//                ((ImportanceSampling)oddModelInference).setKeepDataOnMemory(true);
//                ((ImportanceSampling)evenModelInference).setKeepDataOnMemory(true);
//
//                ((ImportanceSampling)oddModelInference).setSampleSize(sampleSize);
//                ((ImportanceSampling)evenModelInference).setSampleSize(sampleSize);
//
//                break;
//        }
//
//        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<UnivariateDistribution> posteriorMAPDistributionsEvenModel = new ArrayList<>();
//        List<UnivariateDistribution> posteriorMAPDistributionsOddModel = new ArrayList<>();
//
//        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)));
//
//        List<double[]> conditionalDistributionsMAPvariable = getCombinedConditionalDistributions(posteriorMAPDistributionsEvenModel, posteriorMAPDistributionsOddModel);
//        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.staticModel == null) {
//            staticModel = DynamicToStaticBNConverter.convertDBNtoBN(model,nTimeSteps);
//        }
//
//        if (evidence!=null && staticEvidence==null) {
//
//            staticEvidence = new HashMapAssignment(staticModel.getNumberOfVars());
//
//            evidence.stream().forEach(dynamicAssignment -> {
//                int time = (int) dynamicAssignment.getTimeID();
//                Set<Variable> dynAssigVariables = dynamicAssignment.getVariables();
//                for (Variable dynVariable : dynAssigVariables) {
//                    Variable staticVariable = staticModel.getVariables().getVariableByName(dynVariable.getName() + "_t" + Integer.toString(time));
//                    double varValue = dynamicAssignment.getValue(dynVariable);
//                    staticEvidence.setValue(staticVariable, varValue);
//                }<commit_msg>Dynamic MAP with arbitrary number of class variables merged<commit_after>package eu.amidst.dynamic.inference;

import eu.amidst.core.datastream.DataStream;
import eu.amidst.core.distribution.*;
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.utils.Utils;
import eu.amidst.core.variables.*;
import eu.amidst.dynamic.datastream.DynamicDataInstance;
import eu.amidst.dynamic.models.DynamicBayesianNetwork;
import eu.amidst.dynamic.models.DynamicDAG;
import eu.amidst.dynamic.utils.DynamicBayesianNetworkGenerator;
import eu.amidst.dynamic.utils.DynamicBayesianNetworkSampler;
import eu.amidst.dynamic.variables.DynamicAssignment;
import eu.amidst.dynamic.variables.DynamicVariables;
import eu.amidst.dynamic.variables.HashMapDynamicAssignment;
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 {

    /**
     * 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 staticModel;

    /** 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__";


    /**
     * Sets the evidence for this DynamicMAPInference.
     * @param evidence a list of {@link DynamicAssignment} objects.
     */
    public void setEvidence(List<DynamicAssignment> evidence) {

        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 (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 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);
//                }
//
//            });
//        }
    }

    /**
     * Sets the model for this DynamicMAPInference.
     * @param model a {@link DynamicBayesianNetwork} object.
     */
    public void setModel(DynamicBayesianNetwork model) {
        this.model = model;
    }

    /**
     * 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;
    }

    /**
     * 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);
        }
        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 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());
    }

    /**
     * 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();
        });
    }


    /**
     * 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.staticOddModel == null) {
//            this.computeDynamicMAPOddModel();
//        }
//        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.staticOddModel == null) {
//            this.computeDynamicMAPOddModel();
//        }
//        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);
//                }
//
//            });
//        }
//
//        InferenceAlgorithm evenModelInference, oddModelInference;
//        switch(searchAlgorithm) {
//            case VMP:
//                //        long timeStart = System.nanoTime();
//                evenModelInference = new VMP();
//                oddModelInference = new VMP();
//
//                break;
//
//            case IS:
//            default:
//
//                evenModelInference = new ImportanceSampling();
//                oddModelInference = new ImportanceSampling();
//
//                Random random = new Random((this.seed));
//                oddModelInference.setSeed(random.nextInt());
//                evenModelInference.setSeed(random.nextInt());
//                this.seed = random.nextInt();
//
//                ((ImportanceSampling)oddModelInference).setKeepDataOnMemory(true);
//                ((ImportanceSampling)evenModelInference).setKeepDataOnMemory(true);
//
//                ((ImportanceSampling)oddModelInference).setSampleSize(sampleSize);
//                ((ImportanceSampling)evenModelInference).setSampleSize(sampleSize);
//
//                break;
//        }
//
//        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<UnivariateDistribution> posteriorMAPDistributionsEvenModel = new ArrayList<>();
//        List<UnivariateDistribution> posteriorMAPDistributionsOddModel = new ArrayList<>();
//
//        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)));
//
//        List<double[]> conditionalDistributionsMAPvariable = getCombinedConditionalDistributions(posteriorMAPDistributionsEvenModel, posteriorMAPDistributionsOddModel);
//        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.staticModel == null) {
//            staticModel = DynamicToStaticBNConverter.convertDBNtoBN(model,nTimeSteps);
//        }
//
//        if (evidence!=null && staticEvidence==null) {
//
//            staticEvidence = new HashMapAssignment(staticModel.getNumberOfVars());
//
//            evidence.stream().forEach(dynamicAssignment -> {
//                int time = (int) dynamicAssignment.getTimeID();
//                Set<Variable> dynAssigVariables = dynamicAssignment.getVariables();
//                for (Variable dynVariable : dynAssigVariables) {
//                    Variable staticVariable = staticModel.getVariables().getVariableByName(dynVariable.getName() + "_t" + Integer.toString(time));
//                    double varValue = dynamicAssignment.getValue(dynVariable);
//                    staticEvidence.setValue(staticVariable, varValue);
//                }
{'fraction_non_alphanumeric': 0.1207577753013879, 'fraction_numerical': 0.004254888055921386, 'mean_word_length': 2.3727684291449562, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 81, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6002927', 'n_tokens_mistral': 10706, 'n_tokens_neox': 10252, 'n_words': 2176}