content: /*
   *    This program is free software; you can redistribute it and/or modify
   *    it under the terms of the GNU General Public License as published by
   *    the Free Software Foundation; either version 2 of the License, or
   *    (at your option) any later version.
   *
   *    This program is distributed in the hope that it will be useful,
   *    but WITHOUT ANY WARRANTY; without even the implied warranty of
   *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *    GNU General Public License for more details.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   *   RemoteBoundaryVisualizerSubTask.java
   *   Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.boundaryvisualizer;
  
  import weka.classifiers.Classifier;
  import weka.classifiers.AbstractClassifier;
  import weka.core.Instance;
  import weka.core.DenseInstance;
  import weka.core.Instances;
  import weka.core.Utils;
  import weka.experiment.Task;
  import weka.experiment.TaskStatusInfo;
  
  import java.util.Random;
  
  /**
   * Class that encapsulates a sub task for distributed boundary
   * visualization. Produces probability distributions for each pixel
   * in one row of the visualization.
   *
   * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
   * @version $Revision: 5987 $
   * @since 1.0
   * @see Task
   */
  public class RemoteBoundaryVisualizerSubTask implements Task {
  
    // status information for this sub task
    private TaskStatusInfo m_status = new TaskStatusInfo();
  
    // the result of this sub task
    private RemoteResult m_result;
  
    // which row are we doing
    private int m_rowNumber;
  
    // width and height of the visualization
    private int m_panelHeight;
    private int m_panelWidth;
  
    // the classifier to use
    private Classifier m_classifier;
  
    // the kernel density estimator
    private DataGenerator m_dataGenerator;
  
    // the training data
    private Instances m_trainingData;
  
    // attributes for visualizing on (fixed dimensions)
    private int m_xAttribute;
    private int m_yAttribute;
  
    // pixel width and height in terms of attribute values
    private double m_pixHeight;
    private double m_pixWidth;
  
    // min, max of these attributes
    private double m_minX;
    private double m_minY;
    private double m_maxX;
    private double m_maxY;
  
    // number of samples to take from each region in the fixed dimensions
    private int m_numOfSamplesPerRegion = 2;
  
    // number of samples per kernel = base ^ (# non-fixed dimensions)
    private int m_numOfSamplesPerGenerator;
    private double m_samplesBase = 2.0;
  
    // A random number generator 
    private Random m_random;
  
    private double [] m_weightingAttsValues;
    private boolean [] m_attsToWeightOn;
    private double [] m_vals;
    private double [] m_dist;
    private Instance m_predInst;
    
    /**
     * Set the row number for this sub task
     *
     * @param rn the row number
     */
    public void setRowNumber(int rn) {
      m_rowNumber = rn;
    }
  
    /**
     * Set the width of the visualization
     *
     * @param pw the width
     */
    public void setPanelWidth(int pw) {
      m_panelWidth = pw;
    }
  
    /**
     * Set the height of the visualization
     *
     * @param ph the height
     */
    public void setPanelHeight(int ph) {
      m_panelHeight = ph;
    }
  
    /**
     * Set the height of a pixel
     *
     * @param ph the height of a pixel
     */
    public void setPixHeight(double ph) {
      m_pixHeight = ph;
    }
  
    /**
     * Set the width of a pixel
     *
     * @param pw the width of a pixel
     */
    public void setPixWidth(double pw) {
      m_pixWidth = pw;
    }
  
    /**
     * Set the classifier to use
     *
     * @param dc the classifier
     */
    public void setClassifier(Classifier dc) {
      m_classifier = dc;
    }
  
    /**
     * Set the density estimator to use
     *
     * @param dg the density estimator
     */
    public void setDataGenerator(DataGenerator dg) {
      m_dataGenerator = dg;
    }
  
    /**
     * Set the training data
     *
     * @param i the training data
     */
    public void setInstances(Instances i) {
      m_trainingData = i;
    }
  
    /**
     * Set the minimum and maximum values of the x axis fixed dimension
     *
     * @param minx a <code>double</code> value
     * @param maxx a <code>double</code> value
     */
    public void setMinMaxX(double minx, double maxx) {
      m_minX = minx; m_maxX = maxx;
    }
  
    /**
     * Set the minimum and maximum values of the y axis fixed dimension
     *
     * @param miny a <code>double</code> value
     * @param maxy a <code>double</code> value
     */
    public void setMinMaxY(double miny, double maxy) {
      m_minY = miny; m_maxY = maxy;
    }
  
    /**
     * Set the x axis fixed dimension
     *
     * @param xatt an <code>int</code> value
     */
    public void setXAttribute(int xatt) {
      m_xAttribute = xatt;
    }
  
    /**
     * Set the y axis fixed dimension
     *
     * @param yatt an <code>int</code> value
     */
    public void setYAttribute(int yatt) {
      m_yAttribute = yatt;
    }
  
    /**
     * Set the number of points to uniformly sample from a region (fixed
     * dimensions).
     *
     * @param num an <code>int</code> value
     */
    public void setNumSamplesPerRegion(int num) {
      m_numOfSamplesPerRegion = num;
    }
  
    /**
     * Set the base for computing the number of samples to obtain from each
     * generator. number of samples = base ^ (# non fixed dimensions)
     *
     * @param ksb a <code>double</code> value
     */
    public void setGeneratorSamplesBase(double ksb) {
      m_samplesBase = ksb;
    }
  
    /**
     * Perform the sub task
     */
    public void execute() {
  
      m_random = new Random(m_rowNumber * 11);
      m_dataGenerator.setSeed(m_rowNumber * 11);
      m_result = new RemoteResult(m_rowNumber, m_panelWidth);
      m_status.setTaskResult(m_result);
      m_status.setExecutionStatus(TaskStatusInfo.PROCESSING);
  
      try {
        m_numOfSamplesPerGenerator = 
  	(int)Math.pow(m_samplesBase, m_trainingData.numAttributes()-3);
        if (m_trainingData == null) {
  	throw new Exception("No training data set (BoundaryPanel)");
        }
        if (m_classifier == null) {
  	throw new Exception("No classifier set (BoundaryPanel)");
        }
        if (m_dataGenerator == null) {
  	throw new Exception("No data generator set (BoundaryPanel)");
        }
        if (m_trainingData.attribute(m_xAttribute).isNominal() || 
  	m_trainingData.attribute(m_yAttribute).isNominal()) {
  	throw new Exception("Visualization dimensions must be numeric "
  			    +"(RemoteBoundaryVisualizerSubTask)");
        }
        
        m_attsToWeightOn = new boolean[m_trainingData.numAttributes()];
        m_attsToWeightOn[m_xAttribute] = true;
        m_attsToWeightOn[m_yAttribute] = true;
        
        // generate samples
        m_weightingAttsValues = new double [m_attsToWeightOn.length];
        m_vals = new double[m_trainingData.numAttributes()];
        m_predInst = new DenseInstance(1.0, m_vals);
        m_predInst.setDataset(m_trainingData);
  
        System.err.println("Executing row number "+m_rowNumber);
        for (int j = 0; j < m_panelWidth; j++) {
  	double [] preds = calculateRegionProbs(j, m_rowNumber);
  	m_result.setLocationProbs(j, preds);
  	m_result.
  	  setPercentCompleted((int)(100 * ((double)j / (double)m_panelWidth)));
        }
      } catch (Exception ex) {
        m_status.setExecutionStatus(TaskStatusInfo.FAILED);
        m_status.setStatusMessage("Row "+m_rowNumber+" failed.");
        System.err.print(ex);
        return;
      }
  
      // finished
      m_status.setExecutionStatus(TaskStatusInfo.FINISHED);
      m_status.setStatusMessage("Row "+m_rowNumber+" completed successfully.");
    }
  
  
    private double [] calculateRegionProbs(int j, int i) throws Exception {
      double [] sumOfProbsForRegion = 
        new double [m_trainingData.classAttribute().numValues()];
  
      for (int u = 0; u < m_numOfSamplesPerRegion; u++) {
        
        double [] sumOfProbsForLocation = 
  	new double [m_trainingData.classAttribute().numValues()];
        
        m_weightingAttsValues[m_xAttribute] = getRandomX(j);
        m_weightingAttsValues[m_yAttribute] = getRandomY(m_panelHeight-i-1);
        
        m_dataGenerator.setWeightingValues(m_weightingAttsValues);
        
        double [] weights = m_dataGenerator.getWeights();
        double sumOfWeights = Utils.sum(weights);
        int [] indices = Utils.sort(weights);
        
        // Prune 1% of weight mass
        int [] newIndices = new int[indices.length];
        double sumSoFar = 0; 
        double criticalMass = 0.99 * sumOfWeights;
        int index = weights.length - 1; int counter = 0;
        for (int z = weights.length - 1; z >= 0; z--) {
  	newIndices[index--] = indices[z];
  	sumSoFar += weights[indices[z]];
  	counter++;
  	if (sumSoFar > criticalMass) {
  	  break;
  	}
        }
        indices = new int[counter];
        System.arraycopy(newIndices, index + 1, indices, 0, counter);
        
        for (int z = 0; z < m_numOfSamplesPerGenerator; z++) {
          
  	m_dataGenerator.setWeightingValues(m_weightingAttsValues);
  	double [][] values = m_dataGenerator.generateInstances(indices);
          
  	for (int q = 0; q < values.length; q++) {
  	  if (values[q] != null) {
  	    System.arraycopy(values[q], 0, m_vals, 0, m_vals.length);
  	    m_vals[m_xAttribute] = m_weightingAttsValues[m_xAttribute];
  	    m_vals[m_yAttribute] = m_weightingAttsValues[m_yAttribute];
              
  	    // classify the instance
  	    m_dist = m_classifier.distributionForInstance(m_predInst);
  
  	    for (int k = 0; k < sumOfProbsForLocation.length; k++) {
  	      sumOfProbsForLocation[k] += (m_dist[k] * weights[q]); 
  	    }
  	  }
  	}
        }
        
        for (int k = 0; k < sumOfProbsForRegion.length; k++) {
  	sumOfProbsForRegion[k] += (sumOfProbsForLocation[k] * sumOfWeights); 
        }
      }
      
      // average
      Utils.normalize(sumOfProbsForRegion);
  
      // cache
      double [] tempDist = new double[sumOfProbsForRegion.length];
      System.arraycopy(sumOfProbsForRegion, 0, tempDist, 
  		     0, sumOfProbsForRegion.length);
  		
      return tempDist;
    }
  
    /**
     * Return a random x attribute value contained within
     * the pix'th horizontal pixel
     *
     * @param pix the horizontal pixel number
     * @return a value in attribute space
     */
    private double getRandomX(int pix) {
  
      double minPix =  m_minX + (pix * m_pixWidth);
  
      return minPix + m_random.nextDouble() * m_pixWidth;
    }
  
    /**
     * Return a random y attribute value contained within
     * the pix'th vertical pixel
     *
     * @param pix the vertical pixel number
     * @return a value in attribute space
     */
    private double getRandomY(int pix) {
      
      double minPix = m_minY + (pix * m_pixHeight);
      
      return minPix +  m_random.nextDouble() * m_pixHeight;
    }
    
    /**
     * Return status information for this sub task
     *
     * @return a <code>TaskStatusInfo</code> value
     */
    public TaskStatusInfo getTaskStatus() {
      return m_status;
    }
  }
  