// Copyright (c) 2001, 2003, Oracle Corporation.  All rights reserved.  
// File: kMeansDemo.java

/**
 * This sample program describes how to use the ODM Java API to build, test 
 * and apply a clustering model using the kMeans 
 * algorithm. This program uses the "MINING_DATA_BUILD_V" dataset 
 * for building a k-Means model to cluster the data records based on their
 * Eucledian distance similarity. The clustering model provides the user 
 * with insight about the discovered groups. The clusters are described 
 * in terms of centroids, rules and histograms.
 * It then  applies the built model on "MINING_DATA_APPLY_V" dataset. 
 *  
 * NOTE:
 *   Before executing this program you need to provide DB URL, username 
 *   and password in login method and a valid data schema name
 *   by changing the DATA_SCHEMA_NAME constant value in the program.
 */

// Generic Java Imports
import java.io.*;
import java.util.*;
import java.sql.SQLException;

// ODM Imports
import oracle.dmt.odm.*;
import oracle.dmt.odm.data.*;
import oracle.dmt.odm.settings.function.*;
import oracle.dmt.odm.settings.algorithm.*;
import oracle.dmt.odm.task.*;
import oracle.dmt.odm.model.*;
import oracle.dmt.odm.result.*;
import oracle.dmt.odm.rule.*;


public class kMeansDemo extends Object {

  private static DataMiningServer m_dms = null;
  private static oracle.dmt.odm.Connection m_dmsConn = null;
  //provide schema name that has the data tables/views
  private static final String DATA_SCHEMA_NAME = "schema name";
  
  public static void main( String args[] ) { 
    try {      
      //1. Login to the Data Mining Server
          //Create a DMS object
          m_dms = new DataMiningServer(
            "put DB URL here", //JDBC URL jdbc:oracle:thin:@<Host name>:<Port>:<SID>
            "user name", //User Name
            "password" //Password 
          );
          //Login to the DMS and create a DMS Connection
          m_dmsConn = m_dms.login();
          //Remove the objects to avoid name conflicts
          cleanup();
      //2. Build a model
      buildModel();
      //2. Get clustering model details
      getModelDetails();
      //3. Apply the model
      applyModel();
    } catch(Exception anyExp) {
      anyExp.printStackTrace(System.out);
    } finally {
      try {
      //6. Logout from the Data Mining Server
      m_dms.logout(m_dmsConn);
      } catch(Exception anyExp1) { }//Ignore
    }
  }

  /**
   * To build a mining model, the build dataset location details and 
   * the mining function settings are required. 
   * In this example, we will build a clustering model using the 
   * build dataset "MINING_DATA_BUILD_V" in the user schema, with the 
   * kMeans algorithm.
   * After completing the build task, the model named "kMeans_Model" will 
   * be created in the DMS.
   */
  public static void buildModel() throws ODMException, SQLException, IOException 
  {
    //1. Create PhysicalDataSpecification
        LocationAccessData lad = new LocationAccessData( 
                                       "MINING_DATA_BUILD_V", //Build Table Name
                                       DATA_SCHEMA_NAME //Schema Name
                                     );
        PhysicalDataSpecification pds = 
          new NonTransactionalDataSpecification( lad );
    //2. Create & Store Mining Function Settings
        ClusteringAlgorithmSettings clAlgo = new KMeansAlgorithmSettings(10, 0.01f, DistanceFunction.euclidean);
        ClusteringFunctionSettings mfs = 
          ClusteringFunctionSettings.create (
                   m_dmsConn,       //DMS Connection
                   clAlgo,          //Clustering algorithm settings
                   pds,             //Build data specification
                   DataPreparationStatus.unprepared, //data preparation status
                   5, // number of clusters
                   null,//optional list of attributes which should be interpreted as categorical
                   null,//optional list of attributes which should be interpreted as numeric
                   null  //optional list of attributes which should be interpreted as having
                         // integer data type
                   );
        // all categorical attributes and CUST_ID should marked as inactive
        mfs.adjustAttributeUsage( new String[]{
          "CUST_ID", "COUNTRY_NAME", "CUST_GENDER","CUST_MARITAL_STATUS",
          "CUST_INCOME_LEVEL","EDUCATION","OCCUPATION","HOUSEHOLD_SIZE"
          }, AttributeUsage.inactive);
          
        mfs.store( m_dmsConn, "kMeansDemo_MFS");
    //3. Create, store & execute Build Task
        MiningBuildTask buildTask = new MiningBuildTask(
                     pds, //Build data specification
                     "kMeansDemo_MFS", //Mining function settings name
                     "kMeansDemo_Model" //Mining model name
                     );
        executeTask(buildTask, "kMeansDemo_BuildTask" );
  }

  /**
   * This method stores the given task with the specified name in the DMS 
   * and submits the task for asynchronous execution in the DMS. After 
   * completing the task successfully it returns true. If there is a task 
   * failure, then it prints error description and returns false.
   * 
   * @param taskObj task object
   * @param taskName name of the task
   * 
   * @return boolean returns true when the task is successful
   */
  public static boolean executeTask(MiningTask taskObj, String taskName)
  throws ODMException, SQLException {
        boolean isTaskSuccess = false;
        taskObj.store(m_dmsConn, taskName); //Store the task
        taskObj.execute(m_dmsConn); //Submit the task for asynchronous execution
        System.out.print(taskName + " is started, please wait. ");
        //Wait for completion of the task
        MiningTaskStatus taskStatus = taskObj.waitForCompletion(m_dmsConn);
        //Check the status of the task after completion
        isTaskSuccess = taskStatus.getTaskState().equals(MiningTaskState.success);
        if( isTaskSuccess ) {
          //Task completed successfully
          System.out.println("It is successful.");
        } else {//Task failed
          throw new ODMException("It is failed.\nFailure Description: " + 
            taskStatus.getStateDescription() );
        }
        return isTaskSuccess;
    }

  /**
   * To apply a mining model to a data, apply dataset location details, 
   * input model and the output table location details are required. 
   * In this example, we apply the kMeansDemo_Model to the "MINING_DATA_APPLY_V"  
   * dataset and produce probabilistic assignments for each record for the existing 
   * clusters.
   * After completing the apply task, OClusterDemo_ApplyResults object will be  
   * created in the DMS and an apply output table will be created at the  
   * specfied location.
   */
  public static void applyModel() throws ODMException, SQLException, IOException 
  {
    //1. Create PhysicalDataSpecification
        LocationAccessData lad = new LocationAccessData( 
                                 "MINING_DATA_APPLY_V", //Apply table/view Name
                                 DATA_SCHEMA_NAME //Schema Name
                               );
        PhysicalDataSpecification pds = 
          new NonTransactionalDataSpecification( lad );
    //2. Create MiningApplyOutput & apply output table/view details
        // Create MiningApplyOutput object
        MiningApplyOutput mao = MiningApplyOutput.createDefault();
        // Add all the source attributes to be returned with the scored result.
        // In this example, attribute "CUST_ID" from the source table will be 
        // returned with the result aliased as "CUST_ID"
        MiningAttribute sourceAttribute =
                new MiningAttribute("CUST_ID",DataType.intType,
                                    AttributeType.notApplicable);
        Attribute destinationAttribute = new Attribute("ATTR1",DataType.intType);

        ApplySourceAttributeItem applySourceAttributeItem =
           new ApplySourceAttributeItem(sourceAttribute,destinationAttribute);
        mao.addItem(applySourceAttributeItem);

        //Output table location details
        LocationAccessData outputTable =
                 new LocationAccessData("kMeansDemo_ApplyOutput", DATA_SCHEMA_NAME);      
    //3. Create, store & execute apply Task
        MiningApplyTask applyTask = new MiningApplyTask(
                     pds, //test data specification
                     "kMeansDemo_Model", //Input model name
                     mao, //MiningApplyOutput object
                     outputTable, //Apply output table location details
                     "kMeansDemo_ApplyResults" //Apply results name
                     );
        //Store & execute the task
        boolean isTaskSuccess = executeTask(applyTask, "kMeansDemo_ApplyTask"); 
        if( isTaskSuccess ) {
          //Restore & display apply results
          MiningApplyResult applyResult = 
            MiningApplyResult.restore(m_dmsConn, "kMeansDemo_ApplyResults");
          System.out.println(applyResult.toString());
        }
  }

  /*
  * Restores the model and prints out the cluster information, rules 
  * and attribute histogram
  */
  public static void getModelDetails()
    throws SQLException, ODMException, InvalidArgumentException
  {
    // Restore model
    ClusteringModel clModel = 
      (ClusteringModel)ClusteringModel.restore(m_dmsConn, "kMeansDemo_Model");

    printClusterHierarchy(clModel);

    // Get Leaf Clusters
    Vector vLeafClusters = clModel.getLeaves( m_dmsConn );

    // Display cluster information
    System.out.println("Leaf Cluster information:");
    for ( int na = 0; na < vLeafClusters.size(); na++){
      Cluster clCluster = (Cluster)vLeafClusters.elementAt(na);
      int clusterId = clCluster.getId();
      System.out.println("    Cluster : ");
      System.out.println("        ID: " + clusterId );
      Cluster clParent = clCluster.getParent();
      System.out.println("        Parent: " + 
        ( clParent != null ?  (new Integer(clParent.getId())).toString(): "null" ) );
      System.out.println("        Level: " + clCluster.getLevel() );
      System.out.println("        RecordCount: " + clCluster.getRecordCount() );

      ClusterCentroid clCentroid = clCluster.getClusterCentroid();
      Vector vCentroidEntries = clCentroid.getEntries();
      System.out.println("        Centroid Values\n");
      for ( int nj = 0; nj < vCentroidEntries.size(); nj++ ){
        ClusterCentroidEntry clCentEntry = (ClusterCentroidEntry)vCentroidEntries.elementAt(nj);
        Category value = clCentEntry.getValue();
        System.out.print("            " +clCentEntry.getAttribute().getName());
        System.out.print(" : " + value.getDisplayName()+"\n");
      }
    }

    System.out.println("   Print rules for leaf clusters:");
    MiningRuleSet ruleSet = clModel.getClusterRules(m_dmsConn);
    printRules(ruleSet, clModel.getLeafClusterIDs(m_dmsConn));

    System.out.println("\n   Get Attribute Histogram for the AGE attribute");
    Cluster cluster = (Cluster)vLeafClusters.elementAt(vLeafClusters.size()-1);
    System.out.println("     Cluster ID: " + cluster.getId());
    System.out.println("     Attribute Name: " + "AGE" );

    AttributeHistogram aHistogram = clModel.getAttributeHistogram(
        m_dmsConn, cluster.getId(), "AGE" );
    int numberOfBeans = aHistogram.getNumberOfBins();
    System.out.println("      BIN\tDISPLAY NAME\tFREQUENCY");
    for(int i=1; i<=numberOfBeans; i++)
      System.out.println("      " + i + "   " + aHistogram.getDisplayName(i) + "    " + aHistogram.getFrequency(i));
  } 

  /**
   * Prints out rules in the given MiningRuleSet for the leaf clusters.
   */
  private static void printRules(MiningRuleSet ruleSet, int[] arLeafIds)
    throws SQLException, MiningObjectException, ODMException
  {
      MiningRule[] rules = ruleSet.getRules();
      for ( int ni = 0; ni < rules.length; ni++){
        int ruleID = rules[ni].getRuleId();
        for ( int nr = 0; nr < arLeafIds.length; nr++ )
          if ( ruleID == arLeafIds[nr] ){
            System.out.println("\nRule [" + (ni+1) + "]: " + rules[ni].toString());
            break;
          }
      }
      ruleSet.close();
  }

  private static int indentLevel;
  private static String SPACE = "\t";
  
  /**
   * Prints out cluster hierarchy.
   */
  private static void printClusterHierarchy(ClusteringModel clModel)
    throws SQLException, InvalidArgumentException, ODMException
  {
    Vector vClusters = clModel.getClusters( m_dmsConn );
    Cluster rootCluster = (Cluster)vClusters.elementAt(0);
    System.out.println("Cluster : " + rootCluster.getId() );
    traverseTree(rootCluster);
  }
  /**
   * Recursively traverses the cluster hierarchy
   */
  private static void traverseTree(Cluster clParent){
    indentLevel++;
    Vector vChildClusters = clParent.getChildren();
    if ( vChildClusters == null || vChildClusters.isEmpty() ){
      indentLevel--;
      return;
    }
    StringBuffer indent = new StringBuffer();
    for ( int ni = 0; ni < indentLevel; ni++ )
      indent.append(SPACE);
      
    for ( int nc = 0; nc < vChildClusters.size(); nc++ ){
      Cluster childCluster = (Cluster)vChildClusters.elementAt(nc);
      
      System.out.println(indent.toString() + "Cluster : " + childCluster.getId() );
      traverseTree(childCluster);
    }
    indentLevel--;
  }


  /**
   * Cleanup the previously created objects if any. 
   */
  public static void cleanup() throws ODMException, SQLException
  {
    //Remove the MiningFunctionSettings
    try {
      ClusteringFunctionSettings.remove(m_dmsConn, "kMeansDemo_MFS");
    }catch(Exception anyExp1) { }//Ignore
    //Remove the MiningBuildTask
    try {
      MiningBuildTask.remove(m_dmsConn, "kMeansDemo_BuildTask");
    }catch(Exception anyExp2) { }//Ignore
    //Remove the MiningModel
    try {
      MiningModel.remove(m_dmsConn, "kMeansDemo_Model");
    }catch(Exception anyExp3) { }//Ignore
    //Remove the MiningApplyTask
    try {
      MiningApplyTask.remove(m_dmsConn, "kMeansDemo_ApplyTask");
    }catch(Exception anyExp8) { }//Ignore
    //Remove the MiningApplyResult and apply output table
    try {
      MiningApplyResult.remove(m_dmsConn, "kMeansDemo_ApplyResults", true);
    }catch(Exception anyExp9) { }//Ignore    
  }
}
