/* * 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. */ package gov.nasa.ial.mde.solver; import gov.nasa.ial.mde.math.Bounds; import gov.nasa.ial.mde.properties.MdeSettings; import gov.nasa.ial.mde.solver.Solution.SolutionFlagsStateChange; import gov.nasa.ial.mde.solver.symbolic.AnalyzedData; import gov.nasa.ial.mde.solver.symbolic.AnalyzedEquation; import gov.nasa.ial.mde.solver.symbolic.AnalyzedItem; import java.util.ArrayList; import java.util.EventListener; import java.util.Iterator; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; /** * Solver is the Math Description Engine's <em>solution</em> engine. Solver * takes inputs to be graphed (equations or time-series data) and derives * graph-solutions that can be described, sonified or drawn by other MDE * components, or your own custom components. Solver internally stores graph * solutions as <code>Solution</code> objects. * <p> * <em>Equation and Data Inputs:</em> Solver accepts input equations as Java * <code>String</code> or MDE * <code>AnalyzedEquation</code> objects. Solver takes data inputs as * <code>AnalyzedData</code> objects. See the * <a href="http://prime.jsc.nasa.gov/MDE">MDE * Programmer's Guide </a> for valid equation and data input formats. * <p> * <em>Setting Graph Bounds:</em> Use Solver <code>setBounds and * setPreferredBounds</code> to change Solver bounds used for the current * Solution list. Default bounds of (-10, * 10, 10, -10) are provided. Note that setBounds methods do not invoke * <code>solve</code>, but calls to * <code>solve(Bounds b) and solve(double left, double right, double top, double * bottom)</code> <em>do</em> * reset the current bounds. * <p> * <em>Managing One or More Graph Solutions:</em> Solver can accept and manage * multiple equation and dataset inputs for simultaneous display. (You can add * both equations and data to the same solution list.) To support this, Solver * uses a two-step solution process (even if you're graphing one item at a * time): <blockquote>First, add the input(s) you want graphed to Solver's * solution list with an <code>add</code> method. * <p> * Second, call one of the <code>solve</code> methods to generate the * solution(s). </blockquote> * * <em>Clearing the Solution List:</em> When you want to clear the current * solution list, call <code>removeAll()</code>. * <p> * <em>Showing and Hiding Graph Solutions:</em> Solutions in the current list * can be given show or hide designations at input, using * <p> * <blockquote> * add(AnalyzedItem item, boolean enableGraph, boolean enableSonification) * </blockquote>. * These designations can be reset by accessing the Solution of interest with a * <code>get</code> method, and changing that Solution object's * Solution.setShowGraph(boolean visible) and/or Solution.setSonifyGraph(boolean * visible) method. * <p> * <em>Checking for Valid Solutions:</em> After Solver has solved your graph * inputs, you can check for solution validity with Solver's "any" methods: * <p> * <blockquote> * anyBadEquations<br> * anyDescribable<br> * anyGraphable<br> * anyDescribable</blockquote> * <em>Solution Synchronization Among Components:</em> Solver serves as a * Solution synchronizer for MDE's built-in description, graphing and * sonification components (Describer, Sounder, SoundControl, CartesianGraph, * etc.) - if you pass the same Solver object to each component. It can function * similarly for custom graphing components you may develop. * <p> * Solution-synchronized components can register as a Solver ChangeListener, * with the <code>addChangeListener</code> method. * <p> * <a href="http://prime.jsc.nasa.gov/MDE">See the MDE Programmer's Guide </a> * for more information on Solver. * <p> * * @author Dan Dexter, Dr. Robert Shelton, Terry Hodgson, Dat Truong * @see Solution */ public class Solver { /** * List of <code>Solution</code> objects this <code>Solver</code> object * is currently managing. Any solution updates requested through other * Solver methods will be applied to the items in this list. */ protected ArrayList<Solution> solutionList = new ArrayList<Solution>(5); /** * number of polar <code>Solution</code> objects in the * <code>solutionList</code> that have a <code>showGraph</code> value of * <code>true</code> */ protected int showPolarCount = 0; /** * number of Cartesian <code>Solution</code> objects in the * <code>solutionList</code> that have a <code>showGraph</code> value of * <code>true</code> */ protected int showCartesianCount = 0; /** * number of polar <code>Solution</code> objects in the * <code>solutionList</code> that have a <code>sonifyGraph</code> value * of <code>true</code> */ protected int sonifyPolarCount = 0; /** * Number of polar <code>Solution</code> objects in the * <code>solutionList</code> that have a <code>sonifyGraph</code> value * of <code>true</code> */ protected int sonifyCartesianCount = 0; /** * Default solution bounds for all Solution objects in the * <code>solutionList</code>. Value is derived from * AnalyzedItem.DEFAULT_LIMIT = 10.0, that is, left = -10, right = 10, top = * 10 and bottom = -10. */ public static final Bounds DEFAULT_BOUNDS = new Bounds( -AnalyzedItem.DEFAULT_BOUND_VALUE, AnalyzedItem.DEFAULT_BOUND_VALUE, AnalyzedItem.DEFAULT_BOUND_VALUE, -AnalyzedItem.DEFAULT_BOUND_VALUE); /** * Specifies the preferred x and y solution bounds (left, right, top and * bottom) for all Solution objects in the <code>solutionList</code>. * Initially set to DEFAULT_BOUNDS. Use <code>setPreferredBounds</code> * methods to change these values. */ protected Bounds preferredBounds = new Bounds(DEFAULT_BOUNDS); /** * Specifies the <em>current</em> x-y plane solution bounds (left, right, * top and bottom) for all Solution objects in the <code>solutionList</code>. * Initially set to DEFAULT_BOUNDS. Use <code>setBounds</code> methods to * change these values. */ protected Bounds bounds = new Bounds(DEFAULT_BOUNDS); /** * Listens for state changes to <code>showGraph</code> and * <code>sonifyGraph</code> for <em><code>Solution</code></em> objects * in the <code>solutionList</code>. Updates show counts accordingly. */ protected ChangeListener solutionFlagsChangeListener = new SolutionFlagsChangeListener(); /** * Cache of the last change event that occurred for this * <em><code>Solver</code></em> object. Updated by * {@link #fireStateChanged()}. */ protected ChangeEvent changeEvent = null; /** * List of components that have registered to be notified of * <code>Solver</code> change events, through a call to * <code>Solver.addChangeListener</code>. Solver change events are fired * by the <code>add</code> and <code>solve</code> methods. */ protected EventListenerList listenerList = new EventListenerList(); /** * Creates a new <code>Solver</code> object. */ public Solver() { super(); } // end Solver /** * Adds the input equation to this Solver object's solutionList. This add * method creates an AnalyzedEquation from the input equation and calls add. * * @param equation an equation we want solved, usually so we can graph, * describe, and/or sonify the solution with <code>Describer, Sounder, * SoundControl or CartesianGraph</code>. * @return gov.nasa.ial.mde.solver.symbolic.AnalyzedEquation */ public AnalyzedEquation add(String equation) { AnalyzedEquation analyzedEq = new AnalyzedEquation(equation); add(analyzedEq); return analyzedEq; } // end add /** * Creates a Solution object from the input item and adds it to the * <code>solutionList</code> list. This add method calls * {@link #add(AnalyzedItem, boolean, boolean)}. The enableGraph and * enableSonification flags are defaulted to true. * * @param item an analyzed item. */ public void add(AnalyzedItem item) { add(item, true, true); } /** * Creates a Solution object from the input item and adds it to the * <code>solutionList</code> list. Graphing and sonification flags for the * Solution object are set according to enablegraph and enableSonification * values. * * @param item an analyzed item. * @param enableGraph true to enable the graph, false to disable. * @param enableSonification true to enable sonification, false to disable. */ public void add(AnalyzedItem item, boolean enableGraph, boolean enableSonification) { // Handle the AnalyzedEquation special case. if ((item instanceof AnalyzedEquation) && (((AnalyzedEquation) item).isBad() || ((AnalyzedEquation) item) .hasMoreThanTwoVariables())) { if (MdeSettings.DEBUG) { System.out.println("AnalyzedEquation is bad"); } fireStateChanged(); return; } Solution solution = new Solution(item); solution.setShowGraph(enableGraph); solution.setSonifyGraph(enableSonification); solution.addChangeListener(solutionFlagsChangeListener); solutionList.add(solution); // Update our show and sonify graph counts. int showCountOffset = solution.isShowGraph() ? 1 : 0; int sonifyCountOffset = solution.isSonifyGraph() ? 1 : 0; updateShowSonifyCounts(solution, showCountOffset, sonifyCountOffset); // Change the show and sonify graph settings based on existing // solutions. applyShowSonifyGraphRule(); } // end add /** * Returns a Solution list Iterator. * * @return a Solution list Iterator. */ public Iterator<Solution> getSolutionIterator() { return solutionList.iterator(); } /** * Returns true if there are no Solution objects in solutionList. Returns * false otherwise. * * @return true if there are no Solution objects in solutionList. Returns * false otherwise. */ public boolean isEmpty() { return solutionList.isEmpty(); } /** * Returns the number of Solution objects in solutionList. * * @return true if the solution list is empty. false otherwise. */ public int size() { return solutionList.size(); } /** * Returns the Solution object at the specified position in solutionList. * * @param index the index of the solution to get. * @return the Solution object * @see java.util.ArrayList * @see gov.nasa.ial.mde.solver.Solution */ public Solution get(int index) { return (Solution) solutionList.get(index); } /** * Returns the solutionList object(s) with the specified name. It is * possible to have more than one solution for the same name, such as when * the data is segmented and is split across multiple AnalyzedData objects. * * @param name AnalyzedItem name * @return the Solution object with matching AnalyzedItem name * @see gov.nasa.ial.mde.solver.Solution * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedEquation * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedData */ public Solution[] get(String name) { if ((name == null) || solutionList.isEmpty()) { return null; } ArrayList<Solution> list = new ArrayList<Solution>(solutionList.size()); Solution solution; int len = solutionList.size(); for (int i = 0; i < len; i++) { solution = (Solution)solutionList.get(i); if (name.equals(solution.getAnalyzedItem().getName())) { list.add(solution); } } if (list.isEmpty()) { return null; } Solution[] solutionArray = (Solution[]) list.toArray(new Solution[list.size()]); list.clear(); return solutionArray; } /** * Returns the Solution object containing the specified item. * * @param item the analyzed item to get the solution for. * @return the requested Solution object, or null if a match is not found */ public Solution get(AnalyzedItem item) { if ((item == null) || solutionList.isEmpty()) { return null; } Solution solution; int len = solutionList.size(); for (int i = 0; i < len; i++) { solution = (Solution) solutionList.get(i); if (item.equals(solution.getAnalyzedItem())) { return solution; } } return null; } /** * Tests whether a Solution object for the given item is in the current * solutionList. * * @param item the analyzed item. * @return true if the solver contains the specified item. */ public boolean contains(AnalyzedItem item) { return (get(item) != null); } /** * Removes all Solution objects from the solutionList. Resets show and * sonify counts to zero. */ public void removeAll() { // It is very important that we dispose of each solution to free // resources // before we clear the solution list. int len = solutionList.size(); for (int i = 0; i < len; i++) { ((Solution) solutionList.get(i)).dispose(); } // Clear the list. solutionList.clear(); showPolarCount = 0; showCartesianCount = 0; sonifyPolarCount = 0; sonifyCartesianCount = 0; } /** * Generate solutions for all the items in the solutionList using the * current Solver bounds. */ public void solve() { solve(bounds); } // end solve /** * Generate solutions for all the items in the solutionList using the * specified Solver bounds, b. * * @param b the bounds to solve over. */ public void solve(Bounds b) { solve(b.left, b.right, b.top, b.bottom); } // end solve /** * Generate solutions for all the items in the solutionList using the * specified Solver bounds, left, right, top, bottom. * * @param left the left bound. * @param right the right bound. * @param top the top bound. * @param bottom the bottom bound. */ public void solve(double left, double right, double top, double bottom) { // Solve with with new bounds bounds.setBounds(left, right, top, bottom); if (solutionList.isEmpty()) { return; } Bounds prefBounds; Solution solution; AnalyzedItem analyzedItem; boolean maxBoundsChanged, recomputeSolutions; int index; int iteration = 0; int len = solutionList.size(); do { recomputeSolutions = false; for (index = 0; index < len; index++) { solution = (Solution) solutionList.get(index); // NOTE: We only update the solution if we are to graph/describe // or sonify it. if (solution.isShowGraph() || solution.isSonifyGraph()) { analyzedItem = solution.getAnalyzedItem(); // Optimization: Update solution for the first iteration, or // for later // iterations if the analyzed-item bounds do not match the // global bounds. if ((iteration == 0) || !bounds.equals(analyzedItem.getPreferredBounds())) { // Compute the points and graph-trails for the equation // for the given bounds. The fuction test will also be // run by this method. analyzedItem.computePoints(bounds); // Update the cached features. analyzedItem.updateFeatures(); } // Update the bounds if they are different for the first // iteration of the first item. prefBounds = analyzedItem.getPreferredBounds(); if ((iteration == 0) && (index == 0) && !bounds.equals(prefBounds)) { bounds.setBounds(prefBounds); } // Because the computePoints() method could implement // auto-scaling the // bounds could have changed so we need to check for that. maxBoundsChanged = bounds.maximize(prefBounds); if (maxBoundsChanged && (index > 0)) { // Recompute all the solutions if the bounds change and // it was // not for the first item in the list. System.out.println("maxBoundsChanged"); recomputeSolutions = true; } } } } while (recomputeSolutions && ((++iteration) < 10)); // Notify the listeners that we have a solution. fireStateChanged(); } // end solve /** * Apply the default rules for how we display and sonify a mix of Cartesian * and Polar equations. */ private void applyShowSonifyGraphRule() { Solution solution; for (int index = solutionList.size() - 1; index >= 0; index--) { solution = (Solution) solutionList.get(index); if (solution.isPolar() && solution.isSonifyGraph() && ((getShowCartesianCount() > 0) || (getSonifyPolarCount() > 1))) { solution.setSonifyGraph(false); } } } /** * Updates the sonify counts for the specified solution. * * @param s the solution. * @param showOffset the graph offset. * @param sonifyOffset the sonification offset. */ private void updateShowSonifyCounts(Solution s, int showOffset, int sonifyOffset) { if (s.isPolar()) { showPolarCount += showOffset; sonifyPolarCount += sonifyOffset; } else { showCartesianCount += showOffset; sonifyCartesianCount += sonifyOffset; } } /** * Returns the number of polar equations in the solutionList with * showGraph values of true. * * @return the number of polar equations in the solutionList with * showGraph values of true. */ public int getShowPolarCount() { return this.showPolarCount; } /** * Returns the number of Cartesian equations in the solutionList with * showGraph values of true. * * @return the number of Cartesian equations in the solutionList with * showGraph values of true. */ public int getShowCartesianCount() { return this.showCartesianCount; } /** * Returns the number of polar equations in the solutionList with * sonifyGraph values of true. * * @return the number of polar equations in the solutionList with * sonifyGraph values of true. */ public int getSonifyPolarCount() { return this.sonifyPolarCount; } /** * Returns the number of Cartesian equations in the solutionList with * sonifyGraph values of true. * * @return the number of Cartesian equations in the solutionList with * sonifyGraph values of true. */ public int getSonifyCartesianCount() { return this.sonifyCartesianCount; } /** * Determines if there are any AnalyzedData objects in the solution List. * * @return true if there are any AnalyzedData objects in the solution List. */ public boolean anyAnalyzedData() { if (solutionList.isEmpty()) { return false; } Solution solution; int len = solutionList.size(); for (int index = 0; index < len; index++) { solution = (Solution) solutionList.get(index); if (solution.getAnalyzedItem() instanceof AnalyzedData) { return true; } } return false; } /** * Determines if there are any unsolvable equations in the solutionList. * Unsolvable equations have bad syntax or are outside the set of equations * handled by the MDE library. * * @return true if there are any bad equations. */ public boolean anyBadEquations() { if (solutionList.isEmpty()) { return true; } Solution solution; int len = solutionList.size(); for (int index = 0; index < len; index++) { solution = (Solution) solutionList.get(index); if (solution.isBadEquation()) { return true; } } return false; } // end anyBadEquations /** * Determines if MDE can generate text descriptions for any items in the * solutionList. Solutions are not describable where syntax errors or * unsupported equations or data are input. * * @return true if any solution is describable. */ public boolean anyDescribable() { Solution solution; int len = solutionList.size(); for (int index = 0; index < len; index++) { solution = (Solution) solutionList.get(index); if (solution.isDescribable()) { return true; } } return false; } // end anyDescribable /** * Determines if MDE can generate drawn graphs for any items in the * solutionList. Solutions are not graphable where syntax errors or * unsupported equations or data are input. * * @return true if any solution is graphable. */ public boolean anyGraphable() { Solution solution; int len = solutionList.size(); for (int index = 0; index < len; index++) { solution = (Solution) solutionList.get(index); if (solution.isGraphable()) { return true; } } return false; } // end anyGraphable /** * Determines if MDE can generate sonifications for any items in the * solutionList. Solutions are not sonifiable where syntax errors or * unsupported equations or data are input. * * @return true if any solution can be sonified. */ public boolean anySonifiable() { Solution solution; int len = solutionList.size(); for (int index = 0; index < len; index++) { solution = (Solution) solutionList.get(index); if (solution.isSonifiable()) { return true; } } return false; } // end anySonifiable /** * Return the left bound value. * * @return the left bound value. */ public double getLeft() { return bounds.left; } // end getLeft /** * Return the right bound value. * * @return the right bound value. */ public double getRight() { return bounds.right; } // end getRight /** * Return the top bound value. * * @return the top bound value. */ public double getTop() { return bounds.top; } // end getTop /** * Return the bottom bound value. * * @return the bottom bound value. */ public double getBottom() { return bounds.bottom; } // end getBottom /** * Return the Solver object's current solution bounds. * * @return the Solver object's current solution bounds. */ public Bounds getBounds() { return bounds; } // end getBounds /** * Set the solution bounds only, to the specified Bounds, b. * <em>NOTE: Does not solve the solutionList items over the * input bounds.</em> * * @param b the bounds. */ public void setBounds(Bounds b) { bounds.setBounds(b); } // end setBounds /** * Set the solution bounds only, to the specified bounds left, right, top, * and bottom. <em>NOTE: Does not solve the solutionList items over the * new bounds.</em> * * @param left the left bound * @param right the right bound * @param top the top bound * @param bottom the bottom bound */ public void setBounds(double left, double right, double top, double bottom) { bounds.setBounds(left, right, top, bottom); } // end setBounds /** * Return the preferred bounds. * * @return the preferred bounds. */ public Bounds getPreferredBounds() { return preferredBounds; } // end getPreferredBounds /** * Set the solution preferred bounds only. * <em>NOTE: Does not solve the solutionList items over the * new bounds.</em> * * @param b the preferred bounds. */ public void setPreferredBounds(Bounds b) { preferredBounds.setBounds(b.left, b.right, b.top, b.bottom); } // end setPreferredBounds /** * Set the solution preferred bounds only. * <em>NOTE: Does not solve the solutionList items over the * new bounds.</em> * * @param left the left preferred bound. * @param right the right preferred bound. * @param top the top preferred bound. * @param bottom the bottom preferred bound. */ public void setPreferredBounds(double left, double right, double top, double bottom) { preferredBounds.setBounds(left, right, top, bottom); } // end setPreferredBounds /** * Register a component to listen for Solver state change events (adding a * new item to the solutionList or a new solve event). * * @param l the change listener. */ public void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } // end addChangeListener /** * Remove the specified component from the Solver change event listener * list. * * @param l the change listener. */ public void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } // end removeChangeListener /** * Remove all registered components from the Solver change event listener * list. */ public void removeAllChangeListeners() { EventListener[] listeners = listenerList.getListeners(ChangeListener.class); ChangeListener cl; for (int i = 0; i < listeners.length; i++) { // Added a typecast to pass Xlint: ROS 2/3/05 cl = (ChangeListener)listeners[i]; listenerList.remove(ChangeListener.class, cl); } } // end removeAllChangeListeners /** * Notify all registered listener components that a Solver change event has * occurred. */ protected void fireStateChanged() { // Create our cached change-event if it does not exist. if (changeEvent == null) { changeEvent = new ChangeEvent(this); } // notify all the listeners Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ChangeListener.class) { ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent); } } } // end fireStateChanged private class SolutionFlagsChangeListener implements ChangeListener { public void stateChanged(ChangeEvent e) { SolutionFlagsStateChange state = (SolutionFlagsStateChange)e.getSource(); Solution solution = state.solution; int showOffset = state.showGraphChanged ? (solution.isShowGraph() ? 1 : -1) : 0; int sonifyOffset = state.sonifyGraphChanged ? (solution.isSonifyGraph() ? 1 : -1) : 0; updateShowSonifyCounts(solution, showOffset, sonifyOffset); } } // public static void main(String[] args) { // Solver s = new Solver(); // // s.add(GraphUtilities.combineArgs(args)); // // System.out.println("getLeft(): " + s.getLeft() + " getRight(): " // + s.getRight()); // System.out.println("getTop(): " + s.getTop() + " getBottom(): " // + s.getBottom()); // // // Display information about each of the solutions. // Solution solution; // SolvedGraph features; // for (Iterator iter = s.getSolutionIterator(); iter.hasNext();) { // solution = (Solution) iter.next(); // // System.out.println("getInputEquation(): " // + solution.getInputEquation()); // // features = solution.getFeatures(); // if (features != null) { // System.out.println("Features: " + features); // } // // MultiPointXY[] points = solution.getPoints(); // if (points != null) { // for (int i = 0; i < points.length; i += 30) { // System.out.println("points[" + i + "]: " + points[i]); // } // } // // AnalyzedItem analyzedItem = solution.getAnalyzedItem(); // if (analyzedItem instanceof AnalyzedEquation) { // AnalyzedEquation ae = (AnalyzedEquation) analyzedItem; // boolean isFunctionOverInterval = (ae != null) // && ae.isFunctionOverInterval(); // // System.out.println("functionOverInterval?: " // + isFunctionOverInterval); // } // } // } // end main } // end class Solver