/* * Copyright 2006, United States Government as represented by the Administrator * for the National Aeronautics and Space Administration. No copyright is * claimed in the United States under Title 17, U.S. Code. All Other Rights * Reserved. * * Created on Feb 16, 2004 */ package gov.nasa.ial.mde.solver.symbolic; import gov.nasa.ial.mde.math.Bounds; import gov.nasa.ial.mde.math.MultiPointXY; import gov.nasa.ial.mde.math.PointXY; import gov.nasa.ial.mde.solver.GraphTrail; import gov.nasa.ial.mde.solver.SolvedGraph; import gov.nasa.ial.mde.solver.classifier.MDEClassifier; import gov.nasa.ial.mde.util.PointsUtil; import gov.nasa.ial.mde.util.TrailUtil; import java.util.Arrays; import java.util.TreeMap; /** * Analyzes arrays of real number data to extract features associated with it. * * @author Dan Dexter * @version 1.0 * @since 1.0 */ public class AnalyzedData implements AnalyzedItem, Cloneable { // The name associated with the x and y data values. private String xName; private String yName; private double[] xData; private double[] yData; // The minimum and maximum X and Y data values. private double xMin; private double xMax; private double yMin; private double yMax; private Bounds preferredBounds = new Bounds(-DEFAULT_BOUND_VALUE, DEFAULT_BOUND_VALUE, DEFAULT_BOUND_VALUE, -DEFAULT_BOUND_VALUE); private SolvedGraph features = null; // The left and right indexes to the real-data points over the given bounds. private int leftIndexBound = -1; private int rightIndexBound = -1; // Used for quick x-value to point index lookup. private double[] xPointValues = null; private MultiPointXY[] points = null; private GraphTrail[] graphTrails = null; private double maxJump = 0.0; // tolerance for breaking a GraphTrail @SuppressWarnings("unused") private AnalyzedData() { throw new RuntimeException("Default constructor not allowed."); } /** * Creates an instance of <code>AnalyzedData</code> using the specified * X and Y-axis names and values. * <p> * The values of xData must be in ascending order and it is not checked. * * @param xName the name to use for the X-axis column of data. * @param yName the name to use for the Y-axis column of data. * @param xData the X-axis values. * @param yData the Y-axis values. */ public AnalyzedData(String xName, String yName, double[] xData, double[] yData) { if (xName == null) { throw new NullPointerException("Null X-data name."); } if (yName == null) { throw new NullPointerException("Null Y-data name."); } if (xData == null) { throw new NullPointerException("Null X-data array."); } if (yData == null) { throw new NullPointerException("Null Y-data array."); } // The lengths of the data arrays must be the same. if (xData.length != yData.length) { throw new IllegalArgumentException("X and Y data arrays are not the same length."); } if (xData.length <= 0) { throw new IllegalArgumentException("X and Y data arrays must contain data."); } // DONE: Do we need to check to make sure the x-data is in ascending order? // It's checked for file input. Why not here? //ANDREW: I'll do it then. It's an O(n) operation, so why not //ANDREW: Sorting should be a O(n log_2 n) // we would waste time checking if it's in order and then sorting. // we should just go ahead and sort UNLESS we know in advance this.xName = xName; this.yName = yName; sort(xData, yData); //this.xData = xData; //this.yData = yData; // Initialize the X and Y data statistics. initStatistics(); // Set the preferred bounds based on the min and max values of the data. preferredBounds.setBounds(xMin,xMax,yMax,yMin); } private void sort(double[] xData2, double[] yData2) { TreeMap<Double, Double> map = new TreeMap<Double, Double>(); //TreeMap map = new TreeMap(); for(int i = 0; i < xData2.length; i++) { map.put(xData2[i], yData2[i]); //System.out.println(xData2[i] + " " + yData2[i]); } //System.out.println(); //System.out.println(); int size = map.size(); for(int i = 0 ; i < size ; i++) { xData2[i] = map.firstKey(); yData2[i] = map.remove(xData2[i]); //System.out.println(xData2[i] + " " + yData2[i]); } this.xData = xData2; this.yData = yData2; } /** * Initialize the statistics for the X and Y data values. */ private void initStatistics() { double x,y; int len = xData.length; if (len > 0) { x = xData[0]; xMin = x; xMax = x; y = yData[0]; yMin = y; yMax = y; } else { xMin = -DEFAULT_BOUND_VALUE; xMax = DEFAULT_BOUND_VALUE; yMin = -DEFAULT_BOUND_VALUE; yMax = DEFAULT_BOUND_VALUE; } for (int i = 1; i < len; i++) { x = xData[i]; if (x < xMin) { xMin = x; } if (x > xMax) { xMax = x; } y = yData[i]; if (y < yMin) { yMin = y; } if (y > yMax) { yMax = y; } } } /** * Returns the number of data values this <code>AnalyzedData</code> has. * * @return the number of data values this <code>AnalyzedData</code> has. */ public int getDataSize() { return (xData != null) ? xData.length : 0; } /** * Returns the minimum X-axis value. * * @return the minimum X-axis value. */ public double getMinimumX() { return this.xMin; } /** * Returns the maximum X-axis value. * * @return the maximum X-axis value. */ public double getMaximumX() { return this.xMax; } /** * Returns the minimum Y-axis value. * * @return the minimum Y-axis value. */ public double getMinimumY() { return this.yMin; } /** * Returns the maximum Y-axis value. * * @return the maximum Y-axis value. */ public double getMaximumY() { return this.yMax; } /** * Returns the name associated with the X-axis values. * * @return the name associated with the X-axis values. */ public String getXName() { return this.xName; } /** * Returns the name associated with the Y-axis values. * * @return the name associated with the Y-axis values. */ public String getYName() { return this.yName; } /** * Returns the X value at the specified array index. * * @param index the index into the X data array. * @return the value at the specified array index. */ public double getXValueAt(int index) { return xData[index]; } /** * Returns the Y value at the specified array index. * * @param index the index into the Y data array. * @return the value at the specified array index. */ public double getYValueAt(int index) { return yData[index]; } /** * Returns the array of X values. * * @return the array of X values. */ public double[] getXValues() { return xData; } /** * Returns the array of Y values. * * @return the array of Y values. */ public double[] getYValues() { return yData; } /** * Returns the index to the data value associated with the left bound. * * @return the index to the data value associated with the left bound. */ public int getLeftIndexBound() { return leftIndexBound; } /** * Returns the index to the data value associated with the right bound. * * @return the index to the data value associated with the right bound. */ public int getRightIndexBound() { return rightIndexBound; } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#computePoints(gov.nasa.ial.mde.math.Bounds) */ public void computePoints(Bounds b) { computePoints(b.left,b.right,b.top,b.bottom); } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#computePoints(double, double, double, double) */ public void computePoints(double left, double right, double top, double bottom) { // Calculate the points and graph-trails for the given bounds int lastIndex = xData.length - 1; int leftIndex = Arrays.binarySearch(xData,left); if (leftIndex < 0) { leftIndex = -leftIndex - 1; } if (leftIndex >= xData.length) { leftIndex = lastIndex; } // Use the point to the left of the index found in the binary search, // which puts us to the left of the desired left position. while ((leftIndex > 0) && (xData[leftIndex] > left)) { leftIndex--; } int rightIndex = Arrays.binarySearch(xData,right); if (rightIndex < 0) { rightIndex = -rightIndex - 1; } if (rightIndex >= xData.length) { rightIndex = lastIndex; } // Use the index found in the binary search, which puts us to the right // of the desired right position. while ((rightIndex < lastIndex) && (xData[rightIndex] < right)) { rightIndex++; } if (leftIndex > rightIndex) { throw new IllegalArgumentException("Can not have left bound > right bound"); } // The left and right indexes for all the real data points in the given bounds. this.leftIndexBound = leftIndex; this.rightIndexBound = rightIndex; maxJump = Math.abs(top - bottom); int totalPts = (rightIndex - leftIndex) + 1; // NOTE: Our current model is to either interpolate or decimate the real data points. // TODO: Use a real data model. // Dispose of the current points array so we don't have a memory leak. disposePoints(); // Generate the points that will be used for sonifying the model. if (totalPts < NUM_POINTS) { // Interpolate the data to a length of NUM_POINTS. points = PointsUtil.interpolatePoints(leftIndex,rightIndex,NUM_POINTS,xData,yData); } else if (totalPts > NUM_POINTS) { // Decimate the data to a length of NUM_POINTS. points = PointsUtil.decimatePoints(leftIndex,rightIndex,NUM_POINTS,xData,yData); } else { // Make a copy of the points that is a length of NUM_POINTS. points = PointsUtil.copyPoints(leftIndex,rightIndex,xData,yData); } // Create the array of x-values of the points for quick x-value to index lookup. // We assume that the x values from the points array are sorted in ascending order. if (points == null) { xPointValues = null; } else { int len = points.length; if ((xPointValues == null) || (xPointValues.length != len)) { xPointValues = new double[len]; } for (int i = 0; i < len; i++) { xPointValues[i] = points[i].x; } } // Dispose of the current graph trails array so we don't have a memory leak. disposeGraphTrails(); // Generate the trails used for graphing the model of the real data. graphTrails = TrailUtil.getGraphTrailsFrom(points,maxJump); // Update the preferred bounds we keep for this analyzed data. preferredBounds.setBounds(left, right, top, bottom); } /** * Returns the bounds of the data which represents the minimum and maximum * X and Y values. * * @return the he bounds of the data which represents the minimum and * maximum X and Y values. */ public Bounds getDataBounds() { return new Bounds(xMin,xMax,yMax,yMin); } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getPreferredBounds() */ public Bounds getPreferredBounds() { return preferredBounds; } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getClassifier() */ public MDEClassifier getClassifier() { //TODO: get the real-data classifier return new MDEClassifier(); } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#updateFeatures() */ public void updateFeatures() { MDEClassifier c = getClassifier(); this.features = (c != null) ? c.getFeatures(this) : null; } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getFeatures() */ public SolvedGraph getFeatures() { return features; } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getPoint(double) */ public MultiPointXY getPoint(double position) { if ((points == null) || (points.length <= 0) || (position < 0.0) || (position > 1.0)) { return null; } int index = (int)Math.floor(position * (points.length-1)); return points[index]; } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getPoint(int) */ public MultiPointXY getPoint(int index) { return ((points != null) && (index >= 0) && (index < points.length)) ? points[index] : null; } /** * Returns the index in the array to the X-axis data value that is the closest * to the specified x-value. * * @param x the X-axis value to find the index for. * @return the index in the array to the X-axis data value that is the closest * to the specified x-value. */ public int getPointIndexNear(double x) { if ((xPointValues == null) || (xPointValues.length <= 0)) { return -1; } // Determine if the x-value is out of range. if ((x < xPointValues[0]) || (x > xPointValues[xPointValues.length-1])) { return -1; } // Find the index of the point close to the specified x-value. int index = Arrays.binarySearch(xPointValues,x); // For a positive index, we found the point with the exact same x value. if (index >= 0) { return index; } index = -index - 1; if (index >= xPointValues.length) { index = xPointValues.length - 1; } // Determine which of the two points the x-value is closer to the specified x-value. if ((index > 0) && (Math.abs(xPointValues[index-1] - x) < Math.abs(xPointValues[index] - x))) { index--; } return index; } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getPoints() */ public MultiPointXY[] getPoints() { return points; } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getGraphTrails() */ public GraphTrail[] getGraphTrails() { return graphTrails; } /** * Returns the real data point that has the specified x-value, otherwise return null. * * @param x the read data value to find the point for. * @return the real data point that has the specified x-value, otherwise return null. */ public PointXY getRealDataPoint(double x) { int index = Arrays.binarySearch(xData,x); return (index >= 0) ? new PointXY(xData[index],yData[index]) : null; } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#dispose() */ public void dispose() { xName = null; yName = null; xData = null; yData = null; preferredBounds = null; features = null; xPointValues = null; disposeGraphTrails(); disposePoints(); } private void disposeGraphTrails() { if (graphTrails != null) { int len = graphTrails.length; for (int i = 0; i < len; i++) { if (graphTrails[i] != null) { graphTrails[i].dispose(); graphTrails[i] = null; } } graphTrails = null; } } private void disposePoints() { if (points != null) { int len = points.length; for (int i = 0; i < len; i++) { if (points[i] != null) { points[i].dispose(); points[i] = null; } } points = null; } } /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getName() */ public String getName() { return getYName(); } /** * Creates a clone of this <code>AnalyzedData</code> object. * * @see java.lang.Object#clone() */ public Object clone() { return new AnalyzedData(getXName(),getYName(),getXValues(),getYValues()); } /** * Checks whether two <code>AnalyzedData</code> objects have equal values. * * @return true if the specified object and this <code>AnalyzedData</code> object are equal. * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof AnalyzedData) { AnalyzedData ad = (AnalyzedData)obj; return (this.getYName().equals(ad.getYName()) && this.getXName().equals(ad.getXName()) && Arrays.equals(this.yData,ad.yData) && Arrays.equals(this.xData,ad.xData)); } return false; } /** * Returns a string representation of this <code>AnalyzedData</code> object. * * @return a string representation of this <code>AnalyzedData</code> object * @see java.lang.Object#toString() */ public String toString() { StringBuffer strBuff = new StringBuffer(128); strBuff.append(getClass().getName()).append("[") .append("xDataName=").append(xName).append(",") .append("yDataName=").append(yName).append(",") .append("length=").append((xData != null) ? xData.length : 0).append(","); if ((xData == null) || (yData == null)) { strBuff.append("(null,null)"); } else { int len = xData.length; for (int i = 0; i < len; i++) { if (i > 0) { strBuff.append(","); } strBuff.append("(").append(xData[i]).append(",").append(yData[i]).append(")"); } } strBuff.append("]"); return strBuff.toString(); } }