package org.geogebra.common.kernel.optimization;
import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.Function;
import org.geogebra.common.kernel.arithmetic.FunctionVariable;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.arithmetic.NumberValue;
/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
import org.geogebra.common.kernel.commands.EvalInfo;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoNumeric;
/**
* <h3>FitRealFunction</h3>
*
* <pre>
* Class with FitRealFunction which will be used when Fit[<list>,<function>] does
* nonlinear curve-fitting on a copy of <function> where gliders a,b,c,...
* are used as parameters.
*
* Implements:
*
* org.apache.commons.math.optimization.fitting.ParametricUnivariateFunction
* which can be given to org.apache....fitting.CurveFitter which
* does the rest of the job.
*
* Interface:
*
* FitRealFunction(Function) Makes a copy of Function with gliders replaced by mydouble parameters
* value(double,double[]) Evaluates for x and pars[]
* gradient(double,double[]) Evaluates a gradient for x and pars[] numerically
*
* For AlgoFitNL:
*
* getNumberOfParameters() Get number of gliders/parameters found and changed
* getStartParameters() Get array of startvalues for parameters.
* getGeoFunction(double[]) Get FitFunction as GeoFunction with parameters replaced
*
* For later extensions and external use:
*
* evaluate(double,double[]) As value(...), perhaps implementing other interfaces later?
* evaluate(double) As an ordinary function
* evaluate() Last value
* getFunction(double[]) Get as Function with parameters replaced
*
* ToDo: The gradient could be more sophisticated, but the Apache lib is quite robust :-)
* Some tuning of numerical precision both here and in the setup of LM-optimizer
*
* Should probably make an abstract, and make this a subclass,
* will do if the need arises.
* </pre>
*
* @author Hans-Petter Ulven
* @version 15.03.2011
*/
public class FitRealFunction implements ParametricUnivariateFunction {
// / --- Properties --- ///
private Kernel kernel = null;
private int numberOfParameters = 0;
private Object[] gliders = null; // Pointers to gliders, need for new
// startvalues
private Function newf = null;
private double lastvalue = 0.0d;
private MyDouble[] mydoubles = null;
private boolean parametersOK = true;
// / --- Interface --- ///
/** Probably not needed? */
public FitRealFunction() {
}// Constructor
/**
* Main constructor
*
* @param f
* Function to be copied and manipulated
*/
public FitRealFunction(Function f) {
super();
setFunction(f);
}// Constructor
/**
* Implementing org.apache...fitting.ParametricUnivariateFunction
*
* @param x
* double variable
* @param pars
* double[] parameters
* @return functionvalue
*/
@Override
public final double value(double x, double... pars) {
for (int i = 0; i < numberOfParameters; i++) {
mydoubles[i].set(pars[i]);
// mydoubles[i].setLabel("p_{"+i+"}");
} // for all parameter
lastvalue = newf.value(x);
return lastvalue;
}// evaluate(x,pars[])
/**
* Returns array of partial derivatives with respect to parameters.
*
* Derivatives are approximated numerically, the step for given slider is
*
* max(1E-5, 0.01 * slider step)
*
* @param x
* double variable
* @param pars
* double[] parameters
*/
@Override
public final double[] gradient(double x, double... pars) {
double oldf, newf1;
double deltap = 1.0E-5;// 1E-10 and 1E-15 is far too small, keep E-5
// until search algo is made
double[] gradient = new double[numberOfParameters];
for (int i = 0; i < numberOfParameters; i++) {
oldf = value(x, pars);
double old = pars[i];
double deltaI = deltap;
if (gliders[i] instanceof GeoNumeric) {
double step = ((GeoNumeric) gliders[i]).getAnimationStep();
if (step > 1E-13) {
deltaI = Math.min(step * 0.01, deltap);
}
}
pars[i] += deltaI;
newf1 = value(x, pars);
gradient[i] = (newf1 - oldf) / deltap;
pars[i] = old;
}
return gradient;
}
/**
* Converts function to FitRealFunction
*
* @param f
* function depending on GeoNumeric parameters
*/
public void setFunction(Function f) {
kernel = f.getKernel();
FunctionVariable fvar = f.getFunctionVariable();
java.util.HashSet<GeoElement> hash = f.getVariables(); // Get a,b,c,...
// to array
if (hash == null) {
// throw (new Exception("No gliders/parameters in
// fit-function..."));
this.parametersOK = false;
} else {
gliders = hash.toArray();
} // if no gliders
numberOfParameters = gliders.length;
mydoubles = new MyDouble[numberOfParameters]; // Make my own parameters
double temp;
for (int i = 0; i < numberOfParameters; i++) {
temp = ((NumberValue) gliders[i]).getDouble();
mydoubles[i] = new MyDouble(kernel);
mydoubles[i].set(temp); // Set mydoubles to start values from a,b,c
} // for all parameters
ExpressionNode node = f.getExpression();
ExpressionNode enf = node.deepCopy(kernel); // Make new
// tree
// for
// new
// function
// ExpressionNode enf=new ExpressionNode(kernel,evf);
for (int i = 0; i < numberOfParameters; i++) {
enf = enf
.replace((ExpressionValue) gliders[i],
mydoubles[i]
.evaluate(StringTemplate.defaultTemplate))
.wrap();
// System.out.println("Replaced:
// "+((NumberValue)pars[i]).toString()+"with:
// "+mydoubles[i].toString());
} // for all parameters
// System.out.println("enf(etter replace): "+enf.toString());
enf.resolveVariables(new EvalInfo(false));
// should we dispose this??? if(this.newf!=null)
this.newf = new Function(enf, fvar); // System.out.println("new
// function: "+newf.toString());
}// setFunction(Function)
public final int getNumberOfParameters() {
return numberOfParameters;
} // Needed by AlgoFitNL
public final double[] getStartValues() {
double[] startvalues = new double[numberOfParameters];
for (int i = 0; i < numberOfParameters; i++) {
startvalues[i] = ((NumberValue) gliders[i]).getDouble(); // Only
// first
// time:
// mydoubles[i].getDouble();
} // for all parameters
return startvalues;
}// getStartValues()
/** For other uses later? */
public final double evaluate(double x, double[] pars) {
return value(x, pars);
}
public final double evaluate() {
return lastvalue;
}
public final double value(double x) {
return newf.value(x);
}// evaluate(x);
public final Function getFunction() {
return newf;
}
public MyDouble[] getCoeffs() {
return mydoubles;
}
public boolean isParametersOK() {
return parametersOK;
}
}// Class FitRealFunction