/* * 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.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.math.Roots; import gov.nasa.ial.mde.properties.MdeSettings; 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.solver.classifier.PolarClassifier; import gov.nasa.ial.mde.solver.classifier.PolynomialClassifier; import gov.nasa.ial.mde.solver.classifier.QuadraticClassifier; import gov.nasa.ial.mde.solver.classifier.TrigClassifier; import gov.nasa.ial.mde.solver.numeric.PolynomialModel; import gov.nasa.ial.mde.solver.numeric.QuadraticModel; import gov.nasa.ial.mde.util.MathUtil; import gov.nasa.ial.mde.util.PointsUtil; import gov.nasa.ial.mde.util.TrailUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Hashtable; /** * Analyzes an input string to extract features associated with equation(s). * Determines variables, coordinate system, and type of equation e.g. Cartesian, * Polar or Parametric. Acts as a screener to determine which classifier to apply. * * @author Dan Dexter * @author Dr. Robert Shelton * @version 1.0 * @since 1.0 */ public class AnalyzedEquation implements AnalyzedItem { /** Constant corresponds to an equation that did not parse correctly. */ public static final int UNKNOWN = 0; /** Constant corresponds to a Cartesian equation. */ public static final int CARTESIAN = 1; /** Constant corresponds to a Polar equation. */ public static final int POLAR = 2; /** Constant corresponds to a Parametric equation. */ public static final int PARAMETRIC = 3; /** Property of the equation. */ public static final int GENERIC = 0, CONSTANT = 1, FUNCTION = 2, POLYNOMIAL = 4, QUADRATIC = 8, MORE_THAN_TWO_VARIABLES = 16, UNDEFINED = 32, NO_SOLUTION = 64; private Hashtable<String, Double> parameterHash = new Hashtable<String, Double>(); private int equationType = UNKNOWN; private int equationProperties = GENERIC; private Equation theEquation; private String inputEquation; private Polynomial lhs; private Polynomial dvp; // dependent variable polynomial private int dvpDegree; // degree of dvp private Expression[] dvpCoefficients; // coefficients of dvp private double[] doubleCoefficients; // dvp coefficients as a double array private double[] reducedDVPCoefficients; //Workspace for Roots solution methods private double et, et2; // tolerances for EZ and EZ2 private String[] actualVariables = { "x", "y" }; private String independentVariable, dependentVariable; private Bounds preferredBounds = new Bounds(-DEFAULT_BOUND_VALUE, DEFAULT_BOUND_VALUE, DEFAULT_BOUND_VALUE, -DEFAULT_BOUND_VALUE); private MultiPointXY[] points; private GraphTrail[] graphTrails; private SolvedGraph features = null; private double maxJump = 0.0; //tolerance for breaking a GraphTrail // Are the computed points a function. private boolean functionOverInterval = false; // Use this in case the equation defines a constant; leave null otherwise private MultiPointXY constantSolution = null; private MultiPointXY[] savedR = null; private double savedRXLow = 0.0; private double savedRXHigh = 0.0; private boolean bad = false; private int degree; public int getDegree() { return degree; } @SuppressWarnings("unused") private AnalyzedEquation() { throw new RuntimeException("Default constructor not allowed."); } /** * Creates an instance of <code>AnalyzedEquation</code> using the * specified equation string. * * @param eqString the String representation of the input equation. */ public AnalyzedEquation(String eqString) { super(); if ((theEquation = new Equation(eqString)).bad) { bad = true; return; } this.inputEquation = eqString; doInit(); } // end AnalyzedEquation public Polynomial getLhs() { return lhs; } // Do initialization of the class. private void doInit() { lhs = theEquation.getPolynomial(); checkVariables(); /* If there are more than 2 variables, quit */ if ((equationProperties & MORE_THAN_TWO_VARIABLES) != 0) { return; } switch (equationType) { case CARTESIAN: /* if it's a polynomial */ if (lhs.hasConstantCoefficients()) { equationProperties |= POLYNOMIAL; if (lhs.getDegree() <= 2) equationProperties |= QUADRATIC; } // end if polynomial independentVariable = actualVariables[0]; dependentVariable = actualVariables[1]; dvp = theEquation.getOneVariablePolynomial(dependentVariable); degree = lhs.getDegree(); //System.out.println(degree); if (checkForSolvable()) switch (dvpDegree) { case 0: equationProperties |= UNDEFINED; break; case 1: equationProperties |= FUNCTION; default: checkForConstant(); break; } // end switch break; case POLAR: dependentVariable = "r"; independentVariable = "theta"; dvp = theEquation.getOneVariablePolynomial(dependentVariable); if (checkForSolvable()) checkForConstant(); break; default: throw new IllegalStateException("Unimplemented equation type: " + equationType); } // end switch } // end doInit /** * Returns the String representation of the input equation. * * @return returns the input equation. */ public String getInputEquation() { return inputEquation; } /** * Returns the dependent variable of the equation. * * @return the dependent variable of the equation. */ public String getDependentVariable() { return dependentVariable; } // end getDependentVariable /** * Returns the independent variable of the equation. * * @return the independent variable of the equation. */ public String getIndependentVariable() { return independentVariable; } // end getIndependentVariable /** * Returns a <code>Hashtable</code> of the parameters in the equation. * <p> * The <code>Hashtable</code> key is the parameter as it shows up in the * equation and the <code>Hashtable</code> value is the value of the parameter. * * @return a <code>Hashtable</code> of the parameters in the equation. * @see #getParameters() * @see #getParameterValue(String) */ public Hashtable<String, Object> getParameterHash() { Hashtable<String, Object> r = new Hashtable<String, Object>(); Enumeration<String> k = parameterHash.keys(); String key; Object value; while (k.hasMoreElements()) { value = parameterHash.get(key = k.nextElement()); r.put(key, value); } // end while return r; } // end getParameterHash /** * Returns an array of <code>String</code>s of all the parameters that exist * in the input equation. * * @return an array of all the parameters that exist in the input equation. * @see #getAllPossibleParameters() */ public String[] getParameters() { ArrayList<String> pList = new ArrayList<String>(); Enumeration<String> k = parameterHash.keys(); while (k.hasMoreElements()) { pList.add(k.nextElement()); } String[] r = pList.toArray(new String[pList.size()]); Arrays.sort(r); return r; } // end getParameters /** * Returns an array of <code>String</code>s of all the supported parameter * names. * * @return an array of <code>String</code>s of all the supported parameter * names. */ public static String[] getAllPossibleParameters() { int n = MdeSettings.PARAMETER_STRINGS.length; String[] r = new String[n]; for (int i = 0; i < n; i++) { r[i] = MdeSettings.PARAMETER_STRINGS[i][0]; } return r; } // end getAllPossibleParameters /** * Returns true if the specified parameter name exists in the input * equation, otherwise false. * * @param paramName the parameter name. * @return true if the specified parameter name exists in the input * equation, otherwise false. */ public boolean containsParameter(String paramName) { return parameterHash.containsKey(paramName); } // end containsParameter /** * Returns the parameter value for the specified parameter name. * * @param paramName the parameter name. * @return the parameter value. * @exception IllegalArgumentException is thrown if the specified parameter * name does not exist in the input equation. * @see #setParameterValue(String, double) */ public double getParameterValue(String paramName) { Double paramValue = (Double)parameterHash.get(paramName); if (paramValue == null) { throw new IllegalArgumentException("Attempt to access undefined parameter."); } return paramValue.doubleValue(); } // end getParameterValue /** * Sets the value for the specified parameter. * * @param name the parameter name. * @param value the parameter value. * @exception IllegalArgumentException is thrown if the specified parameter * name does not exist in the input equation. * @see #getParameterValue(String) */ public void setParameterValue(String name, double value) { if (!parameterHash.containsKey(name)) { throw new IllegalArgumentException("Attempt to change nonexistent parameter"); } parameterHash.put(name, new Double(value)); } // end setParameterValue /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#dispose() */ public void dispose() { parameterHash.clear(); parameterHash = null; theEquation = null; lhs = null; dvp = null; // dependent variable polynomial dvpCoefficients = null; // coefficients of dvp doubleCoefficients = null; // dvp coefficients as a double array reducedDVPCoefficients = null; //Workspace for Roots solution methods actualVariables = null; independentVariable = null; dependentVariable = null; preferredBounds = null; disposePoints(); disposeGraphTrails(); features = null; constantSolution = null; savedR = null; bad = true; } // Dispose of the graph trails. 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; } } // Dispose of the points. 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 printOriginalEquation(); return getInputEquation(); } /** * Returns true if the input equation is bad, otherwise false. * * @return true if the input equation is bad, otherwise false. */ public boolean isBad() { return bad; } /** * Returns the input equation as a <code>String</code> that was passed to * the constructor of this class. * * @return the input equation that was passed to the constructor of this class. */ public String printOriginalEquation() { return theEquation.toString(); } /** * Returns the analyzed equation as a <code>String</code>. * * @return the analyzed equation as a <code>String</code>. */ public String printEquation() { Expression L = new Expression(theEquation.left.root); Expression R = new Expression(theEquation.right.root); L.setParameterHash(parameterHash); R.setParameterHash(parameterHash); return L.toString() + " = " + R.toString(); } // end printEquation /* (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() { if (isQuadratic()) { //System.out.println("I got a quadratic formula!"); return new QuadraticClassifier(lhs); } if (isPolar()) { //System.out.println("I got a polar formula!"); return new PolarClassifier(solveForPoints(0.0, 2.0 * Math.PI)); } //TODO: put trig here //isTrig()-->return new TrigClassfier if((lhs.toString().contains("sin"))||(lhs.toString().contains("tan"))||(lhs.toString().contains("cos"))){ //System.out.println("DEBUG FOR TRIGCLASSIFIER: LHS IS AT THIS POINT: " +lhs); return new TrigClassifier(); } // If we have a non-null savedR of function points with a good Low and // High bounds for X then we will use those bounds. PolynomialClassifier pc; //System.out.println("I got a polynomial... maybe!"); if ((savedR != null) && (savedRXLow < savedRXHigh)) { pc = new PolynomialClassifier(solveForPoints(savedRXLow, savedRXHigh)); } else { // Use the default bounds. pc = new PolynomialClassifier(solveForPoints(-DEFAULT_BOUND_VALUE, DEFAULT_BOUND_VALUE)); } PolynomialModel pm = pc.getBestGuess(); //System.out.println("Best Guess entered!"); if (pm == null) { //System.out.println("polynmial model is null!"); return new MDEClassifier(); } if (pm instanceof QuadraticModel) { //System.out.println("pm is instanceof of quadratic model!"); QuadraticModel q = (QuadraticModel)pm; if (q.getAnalyzedEquation().isFunction()) { //System.out.println("quadractic classifier!"); return new QuadraticClassifier(q.getPolynomial()); } } // end if //System.out.println("I'm a polynomial!"); return pc; } // end getClassifier /* (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; } // Get the cached features. /* (non-Javadoc) * @see gov.nasa.ial.mde.solver.symbolic.AnalyzedItem#getFeatures() */ public SolvedGraph getFeatures() { return features; } /** * Returns the type of the equation as a coded integer which can be compared to * the constants <code>UNKNOWN</code>, <code>CARTESIAN</code>, <code>POLAR</code> * or <code>PARAMETRIC</code> defined in this class. * * @return an integer corresponding to UNKNOWN, CARTESIAN, POLAR or PARAMETRIC. * Might be useful as a variable in a case statement. The value UNKNOWN * corresponds to an equation that did not parse correctly. The alternate * isUnknown|Cartesian|Polar|Parametric methods are provided. Currently, * parametric equations are not supported. * @see #UNKNOWN * @see #CARTESIAN * @see #POLAR * @see #PARAMETRIC */ public int getEquationType() { return equationType; } // end getEquationType /** * Returns an integer that codes boolean properties of the equation. * * @return an integer code for the various equation properties to check against * the enumeration constants CONSTANT|FUNCTION|POLYNOMIAL of this class. * Might be useful as a variable in a case statement. * @see #GENERIC * @see #CONSTANT * @see #FUNCTION * @see #POLYNOMIAL * @see #QUADRATIC * @see #MORE_THAN_TWO_VARIABLES * @see #UNDEFINED * @see #NO_SOLUTION */ public int getEquationProperties() { return equationProperties; } // end getEquationProperties /** * Returns true if the equation string did not parse, otherwise false. * * @return true if the equation string did not parse, otherwise false. * @see #UNKNOWN * @see #getEquationType() */ public boolean isUnknown() { return (equationType == UNKNOWN); } // end isUnknown /** * Returns true if the equation is Cartesian, otherwise false. * * @return true if the equation is understood to be described by Cartesian * coordinates, false otherwise. * @see #CARTESIAN * @see #getEquationType() */ public boolean isCartesian() { return (equationType == CARTESIAN); } // end isCartesian /** * Returns true if the equation is Polar, otherwise false. * * @return true if the equation is understood to be described by Polar * coordinates; false otherwise. * @see #POLAR * @see #getEquationType() */ public boolean isPolar() { return (equationType == POLAR); } // end isPolar /** * Determine if the input string describe a set of parametric equations. * <p> * <i>NOTE:</i> Currently we do not recognize parametric equations, so this * will return false until this feature is implemented. * * @return true if the input string describes a pair of parametric equations. * @see #PARAMETRIC * @see #getEquationType() */ public boolean isParametric() { return (equationType == PARAMETRIC); } // end isParametric /** * Does the input string describe an equation with no reference to an independent * variable, does it define a constant. * * @return true if the value(s) of the dependent variable do not depend on the * independent variable. Does not strictly mean constant, in the sense * that the input string "y^3 = y" would evoke a return value of true, * even though this relation does not define a function. The meaning of * "constant" in this context is that we need only compute the dependent * variable once. * @see #CONSTANT * @see #getEquationProperties() */ public boolean isConstant() { return ((equationProperties & CONSTANT) != 0); } // end isConstant /** * Determines syntactically if the input string defines a function. It may return * false in situations for which the input string is sufficiently obfuscated that * the syntactic tests fail, for example "exp(x^2 - y) = 1" which is * algebraically equivalent to y = x^2 -- the code will not discover such an * equivalence. * * @return true if and only if the equation is guaranteed to define a function, * independent of the value of the independent variable. A return value * of "false" should suggest testing points over the indicated domain. * @see #FUNCTION * @see #getEquationProperties() */ public boolean isFunction() { return ((equationProperties & FUNCTION) != 0); } // end isFunction /** * Does the input string define a polynomial equation? * * @return true if and only if the input equation is a polynomial. * @see #POLYNOMIAL * @see #getEquationProperties() */ public boolean isPolynomial() { return ((equationProperties & POLYNOMIAL) != 0); } // end isPolynomial /** * Indicates that the equation is a quadratic equation. * * @return true if and only if the equation is a quadratic equation. * @see #QUADRATIC * @see #getEquationProperties() */ public boolean isQuadratic() { return ((equationProperties & QUADRATIC) != 0); } // end isQuadratic /** * Indicates that the equation should not be considered for further processing. * * @return true if and only if the equation contains more than two variables. * @see #MORE_THAN_TWO_VARIABLES * @see #getEquationProperties() */ public boolean hasMoreThanTwoVariables() { return ((equationProperties & MORE_THAN_TWO_VARIABLES) != 0); } // end hasMoreThanTwoVariables /** * Indicates no explicit dependent variable. * * @return true if and only if the dependent variable does not appear in the * equation. The resulting graph will always be zero or more vertical lines. * @see #UNDEFINED * @see #getEquationProperties() */ public boolean isUndefined() { return ((equationProperties & UNDEFINED) != 0); } // end isUndefined /** * Indicates whether or not the solution algorithm will succeed on the input * equation. * * @return true if and only if the solution algorithm will fail on this equation. * @see #NO_SOLUTION * @see #getEquationProperties() */ public boolean cannotBeSolved() { return ((equationProperties & NO_SOLUTION) != 0); } // end cannotBeSolved /** * Convenience method to determine if the equation is a solvable function. * * @return true if and only if <code>!cannotBeSolved() && isFunction()</code>. */ public boolean isSolvableFunction() { return isFunction() && (!cannotBeSolved()); } // end isSolvableFunction /** * Gets the function f if the equation defines y = f(x). * * @return an expression representing the function f, if this equation * defines a function; null otherwise. */ public Expression getFunction() { return isSolvableFunction() ? Expression.negate(dvpCoefficients[1].quotient(dvpCoefficients[0])) : null; } // end getFunction /** * Fills in the double values for the coefficients of the polynomial we will * solve for the dependent variable. * * @param x is the value of the independent variable for which we will want * corresponding values of the dependent variable. * @return the coefficients as an array of doubles. */ public double[] getCoefficients(double x) { Polynomial.evaluateCoefficients(dvpCoefficients, independentVariable, x, doubleCoefficients); // The EZ et al methods are sensitive to the current value of x, so the // tolerances must be re-computed with every new value of x setTolerances(); return doubleCoefficients; } // end getCoefficients /** * Finds all real values of the dependent variable which correspond to a given * value of the independent variable. * * @param x the given value of the independent variable. * @return A <code>MultiPointXY</code> in which the x field contains values of * the independent variable, and the yArray field contains all corresponding * values of the dependent variable. */ public MultiPointXY findRealSolutions(double x) { if (constantSolution != null) { return new MultiPointXY(x, constantSolution.yArray); } // end if if (isConstant()) { constantSolution = getSolution(x); return constantSolution; } return getSolution(x); } // end findRealSolutions /** * Returns the two variables used in the equation if the equation only contains * one variable, makes a reasonable guess as to the other implied variable. * * @return two-element string array containing the equation variables. */ public String[] getActualVariables() { return new String[] { actualVariables[0], actualVariables[1] }; } // end getActualVariables /** * Returns an array of expressions corresponding to the coefficients of the * polynomial for the dependent variable. * <p> * Note that this <code>AnalyzedEquation</code> is solvable if and only if * each DVP coefficient contains at most one variable, and that variable is * identical to the independent variable. * * @return an array of expressions c[k], where c[k] is the coefficient * of y^k in the polynomial for y. */ public Expression[] getDVPCoefficients() { int i, n = dvpCoefficients.length; Expression[] r = new Expression[n]; for (i = 0; i < n; i++) r[i] = dvpCoefficients[i]; return r; } // end getDVPCoefficients // Set the default tolerances. private void setTolerances () { setTolerances(dvpDegree, doubleCoefficients); } // end setTolerances // Use the specified tolerances. private void setTolerances(int dvpd, double[] dpc) { et = et2 = 0.0; for (int i = 0; i <= dvpd; i++) { et += Math.abs(dpc[i]); et2 += (dpc[i] * dpc[i]); } // end for i et = et * 1.0e-8 + Double.MIN_VALUE; et2 = et2 * 1.0e-16 + Double.MIN_VALUE; } // end setTolerances private boolean EZ(double x) { return (Math.abs(x) < et); } // end EZ private boolean EZ2(double x) { return (Math.abs(x) < et2); } // end EZ2 private boolean LZ2(double x) { if (EZ2(x)) return false; if (x > 0.0) return false; return true; } // end LZ2 private MultiPointXY getSolution(double x) { if (cannotBeSolved() || isUndefined()) { return null; } // No need to capture return value because the coefficients are stored getCoefficients(x); return actuallySolve(dvpDegree, x); } // end getSolution private MultiPointXY actuallySolve(int deg, double[] coeffs, double x) { int d, i, n; MultiPointXY r = new MultiPointXY(x); /* check for NaNs */ for (i = 0; i <= deg; i++) { if (Double.isNaN(coeffs[i])) { r.yArray = new double[0]; return r; } // end if } // Find effective degree of dependent variable polynomial setTolerances (deg, coeffs); //ROS: 1/25/05 for (n = 0; n <= deg; n++) if (!EZ(coeffs[n])) break; switch (d = deg - n) { case -1: return new MultiPointXY(x, new double[0]); case 0: case 1: if (!EZ(coeffs[deg - 1])) { double[] y = { -coeffs[deg] / coeffs[deg - 1] }; r.yArray = y; } // end if else r.yArray = new double[0]; return r; case 2: { // start of block double t0 = -0.5 * coeffs[deg - 1] / coeffs[deg - 2]; double t1 = coeffs[deg] / coeffs[deg - 2]; double d2 = t0 * t0 - t1; if (LZ2(d2)) r.yArray = new double[0]; else if (EZ2(d2)) { double[] y = { t0, t0 }; r.yArray = y; } // end if else { double disc = Math.sqrt(d2); double[] y = { t0 - disc, t0 + disc }; r.yArray = y; } // end else return r; } // end block default: if (reducedDVPCoefficients.length <= d) reducedDVPCoefficients = new double[d + 1]; for (i = n; i <= deg; i++) reducedDVPCoefficients[i - n] = coeffs[i]; r.yArray = Roots.getRealRoots(reducedDVPCoefficients, d); return r; } // end switch } // end actuallySolve private MultiPointXY actuallySolve(int deg, double x) { return actuallySolve(deg, doubleCoefficients, x); } // end actuallySolve private void determineParameters(String[] allVariables) { Double tempObj; Hashtable<String, Double> temp = new Hashtable<String, Double>(); int i, n = MdeSettings.PARAMETER_STRINGS.length; for (i = 0; i < n; i++) temp.put(MdeSettings.PARAMETER_STRINGS[i][0], new Double(MdeSettings.PARAMETER_STRINGS[i][1])); n = allVariables.length; for (i = 0; i < n; i++) { String v = allVariables[i].toLowerCase(); //System.out.println("Variables:" +v); if ((tempObj = temp.get(v)) != null) parameterHash.put(v, tempObj); } // end for i } // end determineParameters private void checkVariables() { // System.out.println("DEBUG: in checkVaribles."); String[] temp; ArrayList<String> realVariables = new ArrayList<String>(); temp = lhs.toExpression().varStrings; determineParameters(temp); lhs.setParameterHash(parameterHash); for (int i = 0; i < temp.length; i++) if (parameterHash.get(temp[i].toLowerCase()) == null) realVariables.add(temp[i]); temp = realVariables.toArray(new String[realVariables.size()]); switch (temp.length) { case 0: break; case 1: /* handle the polar case */ if (temp[0].equals("r") || temp[0].equals("theta")) { actualVariables[0] = "r"; actualVariables[1] = "theta"; break; } // end if // if there's just one variable in the equation entered by the user, and // that variable is not ``y'', then the variable in the equation is the // abscissa, and ``y'' is the ordinate. if (!temp[0].equals(actualVariables[1])) actualVariables[0] = temp[0]; break; case 2: actualVariables = temp; // adopt variables entered by user break; default: actualVariables = temp; equationProperties |= MORE_THAN_TWO_VARIABLES; return; } // end switch if (actualVariables[0].equals("r") && actualVariables[1].equals("theta")) equationType = POLAR; else equationType = CARTESIAN; } // end checkVariables private boolean checkForSolvable() { computeCoefficients(); for (int i = 0; i <= dvpDegree; i++) switch (dvpCoefficients[i].varStrings.length) { case 0: break; case 1: if (dvpCoefficients[i].varStrings[0].equals(independentVariable)) break; default: equationProperties |= NO_SOLUTION; return false; } // end switch return true; } // end checkForSolvable private void checkForConstant() { int i; // System.out.println("DEBUG: in check for constant."); for (i = 0; i <= dvpDegree; i++) if (dvpCoefficients[i].varStrings.length != 0) break; if (i > dvpDegree) equationProperties |= CONSTANT; } // end checkForConstant private void computeCoefficients() { dvpDegree = dvp.getDegree(); dvpCoefficients = Polynomial.getCoefficientsAsExpressions(dvp, dependentVariable); for (int i = 0; i <= dvpDegree; i++) dvpCoefficients[i].setParameterHash(parameterHash); doubleCoefficients = new double[dvpDegree + 1]; reducedDVPCoefficients = new double[dvpDegree + 1]; } // end computeCoefficients private MultiPointXY findBoundary(MultiPointXY mp0, MultiPointXY mp1) { double[] y0 = mp0.yArray, y1 = mp1.yArray; double x0 = mp0.x, x1 = mp1.x; int n0 = y0.length, n1 = y1.length; if (x1 - x0 < 1.0e-8) { return (n0 > n1) ? mp0 : mp1; } double x = 0.5 * (x0 + x1); if (n1 > n0) { if (hasMultiples(y1)) { return mp1; } MultiPointXY mp = findRealSolutions(x); return (mp.yArray.length != n1) ? findBoundary(mp, mp1) : findBoundary(mp0, mp); } // end if if (n1 < n0) { if (hasMultiples(y0)) { return mp0; } MultiPointXY mp = findRealSolutions(x); return (mp.yArray.length != n0) ? findBoundary(mp0, mp) : findBoundary(mp, mp1); } // end if return mp0; } // end findBoundary private boolean hasMultiples(double[] y) { int i, n = y.length; if (n < 2) { return false; } for (i = 1; i < n; i++) { if (y[i] == y[i - 1]) { return true; } } return false; } // end hasMultiples private MultiPointXY[] generateVerticalLine(double low, double high, double top, double bottom) { int i, j, k; int len = NUM_POINTS; double delta = (top - bottom) / (len - 1.0); double y = bottom; MultiPointXY[] r = new MultiPointXY[len]; double[] x = getXIntercepts(); for (i = 0; i < len; i++, y += delta) { k = 0; for (j = 0; j < x.length; j++) { if (x[j] >= low && x[j] <= high) { double[] Y = new double[1]; Y[0] = y; r[i++] = new MultiPointXY(x[j], Y); k++; } } if (k > 0) { y = y + (delta * (k)); i--; } } return r; } private GraphTrail[] getVerticalGraphTrails(MultiPointXY[] p) { double[] xIntercepts = getXIntercepts(); int[] foundX = new int[xIntercepts.length]; int i, j; for (i = 0; i < xIntercepts.length; i++) foundX[i] = 0; for (i = 0; i < xIntercepts.length; i++) for (j = 0; j < p.length; j++) if (p[j] != null && p[j].x == xIntercepts[i]) foundX[i] = foundX[i] + 1; int cnt = 0; for (i = 0; i < xIntercepts.length; i++) { if (MdeSettings.DEBUG) { System.out.println("x intercept =" + xIntercepts[i]); } if (foundX[i] > 0) { cnt++; } } int gcnt = 0; GraphTrail[] g = new GraphTrail[cnt]; for (i = 0; i < xIntercepts.length; i++) { int pcnt = 0; if (foundX[i] > 0) { double[][] pts = new double[foundX[i]][2]; for (j = 0; j < p.length; j++) if (p[j] != null && p[j].x == xIntercepts[i]) { pts[pcnt][0] = xIntercepts[i]; pts[pcnt++][1] = p[j].yArray[0]; } g[gcnt++] = new GraphTrail(pts); } } return g; } // end getVerticalGraphTrails private MultiPointXY[] solveForPoints(double low, double high) { int i; int n = NUM_POINTS; double delta = (high - low) / (n - 1.0); double deltaError = 0.9 * delta; // account for roundoff error in step size double x = low; MultiPointXY[] r = new MultiPointXY[n]; boolean found = false; int l = 0; // Determine if the low and high range has changes which means we can // not reuse the savedR array. if (low != savedRXLow) { savedRXLow = low; // New lower range on X, so clear the cached real points. savedR = null; } if (high != savedRXHigh) { savedRXHigh = high; // New upper range on X, so clear the cached real points. savedR = null; } // Find the real values and used cached values if we have one. for (i = 0; i < n; i++, x += delta) { found = false; if ((savedR != null) && (savedR[i] != null)) { if ((x >= savedR[0].x) && (x <= savedR[savedR.length - 1].x)) { for (; l < n; l++) { if (Math.abs(savedR[l].x - x) < deltaError) { found = true; r[i] = savedR[l]; break; } } } } if (!found) { r[i] = findRealSolutions(x); } } int[] sb = TrailUtil.getSegmentBoundariesFrom(r, maxJump); n = sb.length - 1; for (i = 1; i < n; i++) { MultiPointXY newR = findBoundary(r[sb[i] - 1], r[sb[i]]); if (newR.yArray.length == r[sb[i] - 1].yArray.length) { r[sb[i] - 1] = newR; } if (r[sb[i]].yArray.length == newR.yArray.length) { r[sb[i]] = newR; } } // end for i // Create the cached array of real values. savedR = new MultiPointXY[r.length]; for (i = 0; i < r.length; i++) { if (r[i] != null) { savedR[i] = new MultiPointXY(r[i].x, r[i].yArray); } } return r; } // end solveForPoints /** * Returns the Y-intercept values. * * @return the Y-intercept values. */ public double[] getYIntercepts() { if (cannotBeSolved()) return new double[0]; MultiPointXY p = findRealSolutions(0.0); if (p == null) return new double[0]; return p.yArray; } /** * Returns the X-intercept values. * * @return the X-intercept values. */ public double[] getXIntercepts() { Polynomial p = theEquation.getPolynomial(); Expression[] ec = p.getCoefficientsAsExpressions(independentVariable); int deg = ec.length - 1; if (deg == 0) return new double[0]; for (int i = 0; i <= deg; i++) ec[i].setParameterHash(parameterHash); double[] c = new double[deg + 1]; try { Polynomial.evaluateCoefficients(ec, dependentVariable, 0.0, c); return actuallySolve(deg, c, 0.0).yArray; } // end try catch (Exception e) { return new double[0]; } // end catch } // end getXIntercepts /* (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; } /* (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; } /* (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) { maxJump = Math.abs(top - bottom); if (isPolar()) { /* get the polar points */ points = solveForPoints(0.0, 2.0 * Math.PI); /* account for possible multiple values of r */ graphTrails = TrailUtil.getGraphTrailsFrom(points, maxJump); int i, j, n = graphTrails.length; PointXY[][] p = new PointXY[n][0]; ArrayList<MultiPointXY> newPoints = new ArrayList<MultiPointXY>(); left = Double.POSITIVE_INFINITY; right = Double.NEGATIVE_INFINITY; top = Double.NEGATIVE_INFINITY; bottom = Double.POSITIVE_INFINITY; for (i = 0; i < n; i++) { p[i] = PointsUtil.toCartesian(graphTrails[i].getPoints()); for (j = 0; j < graphTrails[i].getLength(); j++) { double[] y = { p[i][j].y }; right = Math.max(right, Math.abs(p[i][j].x)); top = Math.max(top, Math.abs(p[i][j].y)); newPoints.add(new MultiPointXY(p[i][j].x, y)); } // end for j } // end for i right = Math.max(right, top); // Limit the bounds to the maximum value of the DEFAULT_LIMIT right = Math.min(right, DEFAULT_BOUND_VALUE); top = right; left = -right; bottom = -top; // Create the new points points = newPoints.toArray(new MultiPointXY[newPoints.size()]); graphTrails = new GraphTrail[n]; for (i = 0; i < n; i++) { graphTrails[i] = new GraphTrail(p[i]); } } // end if polar else { if (isUndefined()) { points = generateVerticalLine(left, right, top, bottom); graphTrails = getVerticalGraphTrails(points); } else { points = solveForPoints(left, right); graphTrails = TrailUtil.getGraphTrailsFrom(points, maxJump); } } // Update the bounds we keep for this analyzed equation. preferredBounds.setBounds(left, right, top, bottom); // Now do the fuction test. functionTest(); } //end computePoints /** * Returns true if the equation is a function over the interval, otherwise false. * * @return true if the equation is a function over the interval, otherwise false. */ public boolean isFunctionOverInterval() { return functionOverInterval; } // end isFunctionOverInterval // The computePoints() method calls this method to determine of the // equation is a function over the computed points. private void functionTest() { int i, j, m; int len = points.length; boolean foundPoints = false; functionOverInterval = true; for (i = 0; i < len; i++) { if (points[i] != null) { if ((m = points[i].yArray.length) > 0) { foundPoints = true; if (m == 1) { continue; } for (j = 1; j < m; j++) { if (Math.abs(points[i].yArray[j] - points[i].yArray[j - 1]) > 1.0e-3) { functionOverInterval = false; return; } // end if } } // end outer if if (!foundPoints) { functionOverInterval = false; } } } } // end functionTest /** * Checks whether two <code>AnalyzedEquation</code> objects have equal values. * * @return true if the specified object and this <code>AnalyzedEquation</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 AnalyzedEquation) { AnalyzedEquation ae = (AnalyzedEquation)obj; return this.printOriginalEquation().equals(ae.printOriginalEquation()); } return false; } /** * Returns a string representation of this <code>AnalyzedEquation</code> object. * Intended primarily for test/debug. * * @return a string representation of this <code>AnalyzedEquation</code> object. * @see java.lang.Object#toString() */ public String toString() { StringBuffer b = new StringBuffer(); switch (equationType) { case CARTESIAN: b.append("Cartesian equation"); b.append("\nX intercepts: "); double[] X = getXIntercepts(); if (X != null) { if (X.length > 0) for (int i = 0; i < X.length; i++) b.append(" " + MathUtil.trimDouble(X[i], 4)); } // end if break; case POLAR: b.append("Polar equation"); break; default: b.append("Unknown equation format."); return b.toString(); } // end switch if (isConstant()) b.append("\nConstant"); if (isFunction()) b.append("\nIt's a function"); if (isPolynomial()) b.append("\nIt's a polynomial"); if (isQuadratic()) b.append("\nIt's a quadratic"); if (hasMoreThanTwoVariables()) b.append("\nIt has more than two variables"); if (isUndefined()) b.append("\nDependent variable is undefined."); if (cannotBeSolved()) b.append("\nIt can't be solved"); b.append("\nIndependent variable = " + independentVariable); b.append("\nDependent variable = " + dependentVariable); return b.toString(); } // end toString // public static void main(String[] args) { // AnalyzedEquation iea = new AnalyzedEquation(MathUtil.combineArgs(args)); // System.out.println("" + iea); // // /* // * long d1=System.currentTimeMillis(); for (int i = 0; i < 600; i++) // * iea.findRealSolutions(Math.random()); long milli=System.currentTimeMillis() - // * d1.getTime(); System.out.println ("Elapsed time = " + milli + " milli // * seconds"); // doTest (iea); System.out.println ("Solutions:\n" + // * iea.findRealSolutions(0.0)); MDEClassifier c; c = iea.getClassifier(); // * System.out.println (c.toString()); c = iea.getClassifier(); // * System.out.println (c.toString()); // */ // } // end main // // private static void doTest(AnalyzedEquation e) { // byte[] b = new byte[80]; // // while (true) // try { // System.in.read(b); // System.out.println("Solutions:\n" + e.findRealSolutions(new Double(new String(b)).doubleValue())); // } // end try // catch (java.io.IOException ioe) { // break; // } // end catch // catch (NumberFormatException nfe) { // break; // } // end catch // } // end doTest } // end class AnalyzedEquation