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.latentvariablemodels.staticmodels.classifiers;
  
  import eu.amidst.core.datastream.Attributes;
  import eu.amidst.core.datastream.DataInstance;
  import eu.amidst.core.datastream.DataOnMemory;
  import eu.amidst.core.datastream.DataStream;
  import eu.amidst.core.distribution.Multinomial;
  import eu.amidst.core.inference.InferenceAlgorithm;
  import eu.amidst.core.inference.messagepassing.VMP;
  import eu.amidst.core.models.DAG;
  import eu.amidst.core.utils.DataSetGenerator;
  import eu.amidst.core.utils.Utils;
  import eu.amidst.core.variables.StateSpaceTypeEnum;
  import eu.amidst.core.variables.Variable;
  import eu.amidst.latentvariablemodels.staticmodels.exceptions.WrongConfigurationException;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.stream.Collectors;
  
  /**
   * This class implements the latent classification models. For more details, see
   * Langseth, H., and Nielsen, T. D. (2005). Latent classification models. Machine Learning, 59(3), 237-265.
   * Created by rcabanas on 10/03/16.
   *
   */
  public class LatentClassificationModel extends Classifier<LatentClassificationModel> {
  
  
      /** number of continuous hidden variables */
      private int numContinuousHidden;
  
      /** states of the multinomial hidden variable */
      private int numStatesHidden;
  
      /** multinomial hidden variable */
      private Variable hiddenMultinomial;
  
      /** set of continuous hidden variables*/
      private List<Variable> contHiddenList;
  
  
      /**
       * Constructor of classifier from a list of attributes.
       * The default parameters are used: the class variable is the last one and the
       * diagonal flag is set to false (predictive variables are NOT independent).
       * @param attributes list of attributes of the classifier (i.e. its variables)
       * @throws WrongConfigurationException
       */
      public LatentClassificationModel(Attributes attributes) throws WrongConfigurationException {
          super(attributes);
  
          //default values
          this.numContinuousHidden = 2;
          this.numStatesHidden = 2;
  
  
      }
  
  
      /**
       * Builds the DAG over the set of variables given with the naive Bayes structure
       */
      @Override
      protected void buildDAG() {
  
  
  
          //Obtain the predictive attributes
          List<Variable> attrVars = vars.getListOfVariables().stream()
                  .filter(v -> !v.equals(classVar)).collect(Collectors.toList());
  
  
          //Create the hidden variables
          hiddenMultinomial = vars.newMultinomialVariable("M", numStatesHidden);
  
          contHiddenList = new ArrayList<Variable>();
          for(int i=0; i<numContinuousHidden; i++) {
              contHiddenList.add(vars.newGaussianVariable("Z"+Integer.toString(i)));
          }
  
  
          dag = new DAG(vars);
  
          //arcs from the class to the hidden variables
          dag.getParentSet(hiddenMultinomial).addParent(classVar);
          contHiddenList.stream().forEach(z -> dag.getParentSet(z).addParent(classVar));
  
  
          //arcs from the hidden vars to each attribute
          attrVars.stream().forEach(x->dag.getParentSet(x).addParent(hiddenMultinomial));
  
          for (Variable z : contHiddenList) {
              attrVars.stream().forEach(x->dag.getParentSet(x).addParent(z));
          }
  
  
      }
      /*
      * tests if the attributes passed as an argument in the constructor are suitable for this classifier
      * @return boolean value with the result of the test.
              */
      public boolean isValidConfiguration(){
          boolean isValid = true;
  
  
          long numReal = vars.getListOfVariables().stream()
                  .filter( v -> v.getStateSpaceTypeEnum().equals(StateSpaceTypeEnum.REAL))
                  .count();
  
          long numFinite = vars.getListOfVariables().stream()
                  .filter( v -> v.getStateSpaceTypeEnum().equals(StateSpaceTypeEnum.FINITE_SET))
                  .count();
  
  
          if(numFinite != 1 || numReal != vars.getNumberOfVars()-1) {
              isValid = false;
              String errorMsg = "Invalid configuration: wrong number types of variables domains. It should contain 1 discrete variable and the rest shoud be real";
              this.setErrorMessage(errorMsg);
  
          }
  
          return  isValid;
  
      }
  
  
  
      //////// Getters and setters /////////
  
      /**
       * Method to obtain the number of continuous hidden variables
       * @return integer value
       */
      public int getNumContinuousHidden() {
          return numContinuousHidden;
      }
  
      /**
       * method for getting number of states of the hidden multinomial variable
       * @return integer value
       */
      public int getNumStatesHidden() {
          return numStatesHidden;
      }
  
      /**
       * sets the number of continuous hidden variables
       * @param numContinuousHidden integer value
       */
      public LatentClassificationModel setNumContinuousHidden(int numContinuousHidden) {
          this.numContinuousHidden = numContinuousHidden;
          dag = null;
          return this;
      }
  
      /**
       * sets the number of states of the hidden multinomial variable
       * @param numStatesHidden integer value
       */
      public LatentClassificationModel setNumStatesHidden(int numStatesHidden) {
          this.numStatesHidden = numStatesHidden;
          dag = null;
          return this;
      }
  
      /**
       * method for getting the hidden multinomial variable
       * @return object of type Variable
       */
      public Variable getHiddenMultinomial() {
          return hiddenMultinomial;
      }
  
      /**
       * method for getting the list of continuous hidden variables
       * @return list of variables
       */
      public List<Variable> getContHiddenList() {
          return contHiddenList;
      }
  
      ///////// main class (example of use) //////
  
      public static void main(String[] args) throws WrongConfigurationException {
  
          DataStream<DataInstance> data = DataSetGenerator.generate(1234,1000, 1, 10);
          System.out.println(data.getAttributes().toString());
  
          //Parameters of the classifier
          String classVarName = "DiscreteVar0";
          int numContinuousHidden = 4;
          int numStatesHidden = 3;
  
          //Initializes the classifier
          LatentClassificationModel lcm =
                  new LatentClassificationModel(data.getAttributes())
                          .setClassName(classVarName)
                          .setNumContinuousHidden(numContinuousHidden)
                          .setNumStatesHidden(numStatesHidden);
  
  
          //Learning
  
          lcm.updateModel(data);
          for (DataOnMemory<DataInstance> batch : data.iterableOverBatches(100)) {
              lcm.updateModel(batch);
          }
  
          //Shows the resulting model
          System.out.println(lcm.getModel());
          System.out.println(lcm.getDAG());
  
  
          // Uncomment the following 2 lines to get the bug
          InferenceAlgorithm algo = new VMP();
          lcm.setInferenceAlgoPredict(algo);
  
  
          // predict the class of one instances
          System.out.println("Predicts some instances, i.e. computes the posterior probability of the class");
          List<DataInstance> dataTest = data.stream().collect(Collectors.toList()).subList(0,100);
  
  
          int i = 1;
          for(DataInstance d : dataTest) {
  
                  d.setValue(lcm.getClassVar(), Utils.missingValue());
                  Multinomial posteriorProb = lcm.predict(d);
                  System.out.println(posteriorProb.toString());
  
          }
  
  
  
      }
  
  
  
  }
  