/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.tools; import java.util.TreeMap; import org.opensourcephysics.controls.XML; import org.opensourcephysics.controls.XMLControl; import org.opensourcephysics.controls.XMLLoader; import org.opensourcephysics.numerics.MultiVarFunction; import org.opensourcephysics.numerics.ParsedMultiVarFunction; import org.opensourcephysics.numerics.ParserException; /** * A known function for which the expression and parameters are user-editable. * * @author Douglas Brown */ public class UserFunction implements KnownFunction, MultiVarFunction, Cloneable { // static constants protected static String[] dummyVars = {"'", "@", //$NON-NLS-1$ //$NON-NLS-2$ "`", "~", "#"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ // instance fields protected String name; protected String[] paramNames = new String[0]; protected double[] paramValues = new double[0]; protected String[] paramDescriptions = new String[0]; protected String[] functionNames = new String[0]; protected String expression = "0"; //$NON-NLS-1$ protected String inputString = "0"; //$NON-NLS-1$ protected ParsedMultiVarFunction function = null; protected String[] vars = {"x"}; //$NON-NLS-1$ protected UserFunction[] references = new UserFunction[0]; protected boolean nameEditable = true; protected String description; protected KnownPolynomial polynomial; /** * Constructor. * * @param name the function name */ public UserFunction(String name) { setName(name); try { function = new ParsedMultiVarFunction("0", new String[0]); //$NON-NLS-1$ functionNames = function.getFunctionNames(); } catch(ParserException ex) { /** empty block */ } } /** * Constructor that copies a KnownPolynomial. * * @param poly the KnownPolynomial */ public UserFunction(KnownPolynomial poly) { this(poly.getName()); polynomial = poly; // set up name and description setName(poly.getName()); setDescription(poly.getDescription()); // set up parameters String[] params = new String[poly.getParameterCount()]; double[] paramValues = new double[poly.getParameterCount()]; String[] desc = new String[poly.getParameterCount()]; for (int i=0; i<params.length; i++) { params[i] = poly.getParameterName(i); paramValues[i] = poly.getParameterValue(i); desc[i] = poly.getParameterDescription(i); } setParameters(params, paramValues, desc); // set expression setExpression(poly.getExpression("x"), new String[] {"x"}); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Gets the name. * * @return the name */ public String getName() { return name; } /** * Sets the name. * * @param name the name */ public void setName(String name) { if(!isNameEditable()) { return; } this.name = name; } /** * Returns true if the name is user-editable. * * @return true if editable */ public boolean isNameEditable() { return nameEditable; } /** * Sets the name editable property. * * @param editable true if editable */ public void setNameEditable(boolean editable) { nameEditable = editable; } /** * Gets the current independent variable. * * @return the variable name */ public String getIndependentVariable() { return vars[0]; } /** * Gets the current independent variables. * * @return the variable names */ public String[] getIndependentVariables() { return vars; } /** * Gets the expression. * * @return the expression */ public String getInputString() { // replace dummys with var names String s = inputString; for(int i = 0; i<vars.length; i++) { s = s.replaceAll(dummyVars[i], vars[i]); } return s; } /** * Gets the expression using the current variables. * * @return the expression */ public String getExpression() { // replace dummys with var names String s = expression; for(int i = 0; i<vars.length; i++) { s = s.replaceAll(dummyVars[i], vars[i]); } return s; } /** * Gets the expression and sets the independent variable. * * @param indepVarName the name of the independent variable * @return the expression */ public String getExpression(String indepVarName) { vars = new String[] {indepVarName}; return getExpression(); } /** * Gets the expression and sets the independent variables. * * @param varNames the name of the independent variables * @return the expression */ public String getExpression(String[] varNames) { vars = varNames; return getExpression(); } /** * Gets the full expression using the current variables. * * @param varNames the name of the independent variables * @return the expression */ public String getFullExpression(String[] varNames) { String s = getExpression(varNames); // replace references with their full expressions in parentheses for(UserFunction f : references) { s = s.replaceAll(f.getName(), "("+f.getFullExpression(varNames)+")"); //$NON-NLS-1$//$NON-NLS-2$ } return s; } /** * Sets the expression. * * @param exp a parsable expression of the parameters and variables * @param varNames the names of the independent variables * @return true if successfully parsed */ public boolean setExpression(String exp, String[] varNames) { vars = varNames; String[] names = new String[references.length+paramNames.length+vars.length]; for(int i = 0; i<paramNames.length; i++) { names[i+vars.length] = paramNames[i]; } for(int i = 0; i<references.length; i++) { names[i+vars.length+paramNames.length] = references[i].getName(); } // replace indep vars with dummys starting with longest names java.util.ArrayList<String> sorted = new java.util.ArrayList<String>(); sorted.add(vars[0]); // fill and sort the sorted list for(int i = 1; i<vars.length; i++) { int size = sorted.size(); for(int j = 0; j<size; j++) { if(vars[i].length()>sorted.get(j).toString().length()) { sorted.add(j, vars[i]); break; } else if(j==size-1) { sorted.add(vars[i]); } } } // replace strings in both expression and names in sorted list order for(int k = 0; k<sorted.size(); k++) { String next = sorted.get(k).toString(); for(int i = 0; i<vars.length; i++) { if(next.equals(vars[i])) { exp = exp.replaceAll(vars[i], dummyVars[i]); names[i] = dummyVars[i]; for(int j = vars.length; j<names.length; j++) { names[j] = names[j].replaceAll(vars[i], dummyVars[i]); } // replace modified function names with originals for(int j = 0; j<functionNames.length; j++) { String modified = functionNames[j].replaceAll(vars[i], dummyVars[i]); if(!modified.equals(functionNames[j])) { exp = exp.replaceAll(modified, functionNames[j]); } } } } } // replace modified names with originals for(int i = 0; i<paramNames.length; i++) { exp = exp.replaceAll(names[vars.length+i], paramNames[i]); names[vars.length+i] = paramNames[i]; } for(int i = 0; i<references.length; i++) { exp = exp.replaceAll(names[vars.length+paramNames.length+i], references[i].getName()); names[vars.length+paramNames.length+i] = references[i].getName(); } inputString = exp; // try to parse expression try { function = new ParsedMultiVarFunction(exp, names); // successful, so save expression unless it contains "=" if(exp.indexOf("=")==-1) { //$NON-NLS-1$ expression = exp; return true; } } catch(ParserException ex) { try { function = new ParsedMultiVarFunction("0", names); //$NON-NLS-1$ } catch(ParserException ex2) { /** empty block */ } expression = "0"; //$NON-NLS-1$ } return false; } /** * Gets the parameter count. * @return the number of parameters */ public int getParameterCount() { return paramNames.length; } /** * Gets a parameter name. * * @param i the parameter index * @return the name of the parameter */ public String getParameterName(int i) { return paramNames[i]; } /** * Gets a parameter value. * * @param i the parameter index * @return the value of the parameter */ public double getParameterValue(int i) { return paramValues[i]; } /** * Sets a parameter value. * * @param i the parameter index * @param value the value */ public void setParameterValue(int i, double value) { paramValues[i] = value; } /** * Sets the parameters. * * @param names the parameter names * @param values the parameter values */ public void setParameters(String[] names, double[] values) { paramNames = names; paramValues = values; } /** * Sets the parameters. * * @param names the parameter names * @param values the parameter values * @param descriptions the parameter descriptions */ public void setParameters(String[] names, double[] values, String[] descriptions) { paramNames = names; paramValues = values; if (descriptions!=null) { paramDescriptions = descriptions; } } /** * Sets the parameters of reference functions to those of this function. */ public void updateReferenceParameters() { for(UserFunction next : references) { next.setParameters(paramNames, paramValues, paramDescriptions); next.updateReferenceParameters(); } } /** * Sets the reference functions. * * @param functions the functions referenced by this one */ public void setReferences(UserFunction[] functions) { references = functions; } /** * Gets the description of this function. May return null. * * @return the description */ public String getDescription() { return description; } /** * Sets the description of this function. * * @param desc the description */ public void setDescription(String desc) { description = desc; } /** * Gets a parameter description. May be null. * * @param i the parameter index * @return the description of the parameter (may be null) */ public String getParameterDescription(int i) { if (i>=paramDescriptions.length) return null; return paramDescriptions[i]; } /** * Returns function names. * Added by D. Brown 10 Dec 2015 * * @return array of parser function names */ public String[] getFunctionNames() { return functionNames; } /** * Evaluates the function for a single variable x. * * @param x * @return f(x) */ public double evaluate(double x) { if(function==null) { return Double.NaN; } double[] supportValues = evaluateSupportFunctions(x); int n = supportValues.length+paramValues.length+1; double[] values = new double[n]; values[0] = x; System.arraycopy(paramValues, 0, values, 1, paramValues.length); System.arraycopy(supportValues, 0, values, 1+paramValues.length, supportValues.length); return function.evaluate(values); } /** * Evaluates the function for a variables array x. * * @param x * @return f(x) */ public double evaluate(double[] x) { if(function==null) { return Double.NaN; } double[] support = evaluateSupportFunctions(x); int n = support.length+paramValues.length+x.length; double[] values = new double[n]; System.arraycopy(x, 0, values, 0, x.length); System.arraycopy(paramValues, 0, values, x.length, paramValues.length); System.arraycopy(support, 0, values, x.length+paramValues.length, support.length); return function.evaluate(values); } /** * Determines if last evaluation resulted in NaN. * * @return true if result was converted from NaN to zero */ public boolean evaluatedToNaN() { return function==null? false: function.evaluatedToNaN(); } /** * Returns a clone of this UserFunction. * * @return the clone */ public UserFunction clone() { UserFunction f = new UserFunction(name); f.setDescription(description); f.setNameEditable(nameEditable); f.setParameters(paramNames, paramValues, paramDescriptions); UserFunction[] refs = new UserFunction[references.length]; for(int i = 0; i<refs.length; i++) { refs[i] = references[i].clone(); } f.setReferences(refs); f.setExpression(inputString, vars); f.polynomial = polynomial==null? null: polynomial.clone(); return f; } /** * Determines if another KnownFunction is the same as this one. * * @param f the KnownFunction to test * @return true if equal */ @Override public boolean equals(Object f) { if (!(f instanceof UserFunction)) return false; UserFunction uf = (UserFunction)f; if (!getName().equals(uf.getName())) return false; if (!getInputString().equals(uf.getInputString())) return false; int n = getParameterCount(); if (n!=uf.getParameterCount()) return false; for (int i=0; i<n; i++) { if (!getParameterName(i).equals(uf.getParameterName(i))) return false; } // ignore descriptions and parameter values return true; } /** * Updates the associated polynomial, if any, with this functions current properties. * * @return true if updated */ public boolean updatePolynomial() { if (polynomial==null) return false; // update name and description // see if function name is different than default polynomial name polynomial.setName(this.getName()); polynomial.setDescription(this.getDescription()); // update parameters polynomial.setParameters(paramNames, paramValues, paramDescriptions); return true; } /** * Replaces a parameter name with a new one in the function expression. * * @param oldName the existing parameter name * @param newName the new parameter name * @return the modified expression, or null if failed */ protected String replaceParameterNameInExpression(String oldName, String newName) { String[] varNames = getIndependentVariables(); String expression = getInputString(); TreeMap<String, String> replacements = new TreeMap<String, String>(); expression = replaceInExpression(oldName, newName, expression, replacements); if (expression==null) return null; // restore dummy names for (String key: replacements.keySet()) { if (key.equals(newName)) continue; expression = expression.replaceAll(key, replacements.get(key)); } if (setExpression(expression, varNames)) return expression; return null; } /** * Replaces a parameter name with a new one in a specified expression. * * @param oldName the existing parameter name * @param newName the new parameter name * @param expression the expression to modify * @param replacements a map of parameter replacements already made * @return the modified expression, or null if failed */ private String replaceInExpression(String oldName, String newName, String expression, TreeMap<String, String> replacements) { if (replacements.values().contains(oldName)) return expression; for (int i=0; i<getParameterCount(); i++) { String nextParam = getParameterName(i); if (oldName.equals(nextParam) || newName.equals(nextParam)) continue; if (nextParam.contains(oldName)) { // replace nextParam with dummy name int k = 0; for (int j=0; j<dummyVars.length; j++) { if (dummyVars[j].equals(newName)) { k = j+1; break; } } if (k>=dummyVars.length) return null; expression = replaceInExpression(nextParam, dummyVars[k], expression, replacements); if (expression==null) return null; } } expression = expression.replaceAll(oldName, newName); replacements.put(newName, oldName); return expression; } /** * Evaluates the support functions for a single variable x. * * @param x * @return double[] of values */ protected double[] evaluateSupportFunctions(double x) { double[] values = new double[references.length]; for(int i = 0; i<values.length; i++) { values[i] = references[i].evaluate(x); } return values; } /** * Evaluates the support functions for a variables array x. * * @param x * @return double[] of values */ protected double[] evaluateSupportFunctions(double[] x) { double[] values = new double[references.length]; for(int i = 0; i<values.length; i++) { values[i] = references[i].evaluate(x); } return values; } /** * Returns the XML.ObjectLoader for this class. * * @return the object loader */ public static XML.ObjectLoader getLoader() { return new Loader(); } /** * A class to save and load UserFunction data in an XMLControl. */ protected static class Loader extends XMLLoader { public void saveObject(XMLControl control, Object obj) { UserFunction f = (UserFunction) obj; control.setValue("name", f.getName()); //$NON-NLS-1$ control.setValue("description", f.getDescription()); //$NON-NLS-1$ control.setValue("name_editable", f.isNameEditable()); //$NON-NLS-1$ control.setValue("parameter_names", f.paramNames); //$NON-NLS-1$ control.setValue("parameter_values", f.paramValues); //$NON-NLS-1$ control.setValue("parameter_descriptions", f.paramDescriptions); //$NON-NLS-1$ control.setValue("variables", f.getIndependentVariables()); //$NON-NLS-1$ control.setValue("expression", f.getInputString()); //$NON-NLS-1$ if (f.polynomial!=null) { control.setValue("polynomial", f.polynomial.getCoefficients()); //$NON-NLS-1$ } } public Object createObject(XMLControl control) { String name = control.getString("name"); //$NON-NLS-1$ return new UserFunction(name); } public Object loadObject(XMLControl control, Object obj) { UserFunction f = (UserFunction) obj; f.setName(control.getString("name")); //$NON-NLS-1$ f.setDescription(control.getString("description")); //$NON-NLS-1$ if(control.getPropertyNames().contains("name_editable")) { //$NON-NLS-1$ f.setNameEditable(control.getBoolean("name_editable")); //$NON-NLS-1$ } String[] names = (String[]) control.getObject("parameter_names"); //$NON-NLS-1$ if(names!=null) { double[] values = (double[]) control.getObject("parameter_values"); //$NON-NLS-1$ String[] desc = (String[]) control.getObject("parameter_descriptions"); //$NON-NLS-1$ f.setParameters(names, values, desc); } String[] vars = (String[]) control.getObject("variables"); //$NON-NLS-1$ if(vars==null) { // for legacy code String var = control.getString("variable"); //$NON-NLS-1$ vars = new String[] {var}; } f.setExpression(control.getString("expression"), vars); //$NON-NLS-1$ double[] coeff = (double[])control.getObject("polynomial"); //$NON-NLS-1$ if (coeff!=null) { f.polynomial = new KnownPolynomial(coeff); } return obj; } } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */