merged curve fitting from mantissa into commons-math
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@786479 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
6463532544
commit
2a1842f3ef
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.math.FunctionEvaluationException;
|
||||||
|
import org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction;
|
||||||
|
import org.apache.commons.math.analysis.MultivariateMatrixFunction;
|
||||||
|
import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
|
||||||
|
import org.apache.commons.math.optimization.OptimizationException;
|
||||||
|
import org.apache.commons.math.optimization.VectorialPointValuePair;
|
||||||
|
|
||||||
|
/** Fitter for parametric univariate real functions y = f(x).
|
||||||
|
* <p>When a univariate real function y = f(x) does depend on some
|
||||||
|
* unknown parameters p<sub>0</sub>, p<sub>1</sub> ... p<sub>n-1</sub>,
|
||||||
|
* this class can be used to find these parameters. It does this
|
||||||
|
* by <em>fitting</em> the curve so it remains very close to a set of
|
||||||
|
* observed points (x<sub>0</sub>, y<sub>0</sub>), (x<sub>1</sub>,
|
||||||
|
* y<sub>1</sub>) ... (x<sub>k-1</sub>, y<sub>k-1</sub>). This fitting
|
||||||
|
* is done by finding the parameters values that minimizes the objective
|
||||||
|
* function ∑(y<sub>i</sub>-f(x<sub>i</sub>))<sup>2</sup>. This is
|
||||||
|
* really a least squares problem.</p>
|
||||||
|
* @version $Revision$ $Date$
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public class CurveFitter {
|
||||||
|
|
||||||
|
/** Optimizer to use for the fitting. */
|
||||||
|
private final DifferentiableMultivariateVectorialOptimizer optimizer;
|
||||||
|
|
||||||
|
/** Observed points. */
|
||||||
|
private final List<WeightedObservedPoint> observations;
|
||||||
|
|
||||||
|
/** Simple constructor.
|
||||||
|
* @param optimizer optimizer to use for the fitting
|
||||||
|
*/
|
||||||
|
public CurveFitter(final DifferentiableMultivariateVectorialOptimizer optimizer) {
|
||||||
|
this.optimizer = optimizer;
|
||||||
|
observations = new ArrayList<WeightedObservedPoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add an observed (x,y) point to the sample with unit weight.
|
||||||
|
* <p>Calling this method is equivalent to call
|
||||||
|
* <code>addObservedPoint(1.0, x, y)</code>.</p>
|
||||||
|
* @param x abscissa of the point
|
||||||
|
* @param y observed value of the point at x, after fitting we should
|
||||||
|
* have f(x) as close as possible to this value
|
||||||
|
* @see #addObservedPoint(double, double, double)
|
||||||
|
* @see #addObservedPoint(WeightedObservedPoint)
|
||||||
|
* @see #getObservations()
|
||||||
|
*/
|
||||||
|
public void addObservedPoint(double x, double y) {
|
||||||
|
addObservedPoint(1.0, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add an observed weighted (x,y) point to the sample.
|
||||||
|
* @param weight weight of the observed point in the fit
|
||||||
|
* @param x abscissa of the point
|
||||||
|
* @param y observed value of the point at x, after fitting we should
|
||||||
|
* have f(x) as close as possible to this value
|
||||||
|
* @see #addObservedPoint(double, double)
|
||||||
|
* @see #addObservedPoint(WeightedObservedPoint)
|
||||||
|
* @see #getObservations()
|
||||||
|
*/
|
||||||
|
public void addObservedPoint(double weight, double x, double y) {
|
||||||
|
observations.add(new WeightedObservedPoint(weight, x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add an observed weighted (x,y) point to the sample.
|
||||||
|
* @param observed observed point to add
|
||||||
|
* @see #addObservedPoint(double, double)
|
||||||
|
* @see #addObservedPoint(double, double, double)
|
||||||
|
* @see #getObservations()
|
||||||
|
*/
|
||||||
|
public void addObservedPoint(WeightedObservedPoint observed) {
|
||||||
|
observations.add(observed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the observed points.
|
||||||
|
* @return observed points
|
||||||
|
* @see #addObservedPoint(double, double)
|
||||||
|
* @see #addObservedPoint(double, double, double)
|
||||||
|
* @see #addObservedPoint(WeightedObservedPoint)
|
||||||
|
*/
|
||||||
|
public WeightedObservedPoint[] getObservations() {
|
||||||
|
return observations.toArray(new WeightedObservedPoint[observations.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fit a curve.
|
||||||
|
* <p>This method compute the coefficients of the curve that best
|
||||||
|
* fit the sample of weighted pairs previously given through calls
|
||||||
|
* to the {@link #addWeightedPair addWeightedPair} method.</p>
|
||||||
|
* @param f parametric function to fit
|
||||||
|
* @param initialGuess first guess of the function parameters
|
||||||
|
* @return fitted parameters
|
||||||
|
* @exception FunctionEvaluationException if the objective function throws one during
|
||||||
|
* the search
|
||||||
|
* @exception OptimizationException if the algorithm failed to converge
|
||||||
|
* @exception IllegalArgumentException if the start point dimension is wrong
|
||||||
|
*/
|
||||||
|
public double[] fit(final ParametricRealFunction f,
|
||||||
|
final double[] initialGuess)
|
||||||
|
throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
|
||||||
|
|
||||||
|
// prepare least squares problem
|
||||||
|
double[] target = new double[observations.size()];
|
||||||
|
double[] weights = new double[observations.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (WeightedObservedPoint point : observations) {
|
||||||
|
target[i] = point.getY();
|
||||||
|
weights[i] = point.getWeight();
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform the fit
|
||||||
|
VectorialPointValuePair optimum =
|
||||||
|
optimizer.optimize(new TheoreticalValuesFunction(f), target, weights, initialGuess);
|
||||||
|
|
||||||
|
// extract the coefficients
|
||||||
|
return optimum.getPointRef();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vectorial function computing function theoretical values. */
|
||||||
|
private class TheoreticalValuesFunction
|
||||||
|
implements DifferentiableMultivariateVectorialFunction {
|
||||||
|
|
||||||
|
/** Function to fit. */
|
||||||
|
private final ParametricRealFunction f;
|
||||||
|
|
||||||
|
/** Simple constructor.
|
||||||
|
* @param f function to fit.
|
||||||
|
*/
|
||||||
|
public TheoreticalValuesFunction(final ParametricRealFunction f) {
|
||||||
|
this.f = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public MultivariateMatrixFunction jacobian() {
|
||||||
|
return new MultivariateMatrixFunction() {
|
||||||
|
public double[][] value(double[] point)
|
||||||
|
throws FunctionEvaluationException, IllegalArgumentException {
|
||||||
|
|
||||||
|
final double[][] jacobian = new double[observations.size()][];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (WeightedObservedPoint observed : observations) {
|
||||||
|
jacobian[i++] = f.gradient(observed.getX(), point);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jacobian;
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public double[] value(double[] point)
|
||||||
|
throws FunctionEvaluationException, IllegalArgumentException {
|
||||||
|
|
||||||
|
// compute the residuals
|
||||||
|
final double[] values = new double[observations.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (WeightedObservedPoint observed : observations) {
|
||||||
|
values[i++] = f.value(observed.getX(), point);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,300 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import org.apache.commons.math.optimization.OptimizationException;
|
||||||
|
|
||||||
|
/** This class guesses harmonic coefficients from a sample.
|
||||||
|
|
||||||
|
* <p>The algorithm used to guess the coefficients is as follows:</p>
|
||||||
|
|
||||||
|
* <p>We know f (t) at some sampling points t<sub>i</sub> and want to find a,
|
||||||
|
* ω and φ such that f (t) = a cos (ω t + φ).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>From the analytical expression, we can compute two primitives :
|
||||||
|
* <pre>
|
||||||
|
* If2 (t) = ∫ f<sup>2</sup> = a<sup>2</sup> × [t + S (t)] / 2
|
||||||
|
* If'2 (t) = ∫ f'<sup>2</sup> = a<sup>2</sup> ω<sup>2</sup> × [t - S (t)] / 2
|
||||||
|
* where S (t) = sin (2 (ω t + φ)) / (2 ω)
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>We can remove S between these expressions :
|
||||||
|
* <pre>
|
||||||
|
* If'2 (t) = a<sup>2</sup> ω<sup>2</sup> t - ω<sup>2</sup> If2 (t)
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>The preceding expression shows that If'2 (t) is a linear
|
||||||
|
* combination of both t and If2 (t): If'2 (t) = A × t + B × If2 (t)
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>From the primitive, we can deduce the same form for definite
|
||||||
|
* integrals between t<sub>1</sub> and t<sub>i</sub> for each t<sub>i</sub> :
|
||||||
|
* <pre>
|
||||||
|
* If2 (t<sub>i</sub>) - If2 (t<sub>1</sub>) = A × (t<sub>i</sub> - t<sub>1</sub>) + B × (If2 (t<sub>i</sub>) - If2 (t<sub>1</sub>))
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>We can find the coefficients A and B that best fit the sample
|
||||||
|
* to this linear expression by computing the definite integrals for
|
||||||
|
* each sample points.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>For a bilinear expression z (x<sub>i</sub>, y<sub>i</sub>) = A × x<sub>i</sub> + B × y<sub>i</sub>, the
|
||||||
|
* coefficients A and B that minimize a least square criterion
|
||||||
|
* ∑ (z<sub>i</sub> - z (x<sub>i</sub>, y<sub>i</sub>))<sup>2</sup> are given by these expressions:</p>
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* ∑y<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
|
||||||
|
* A = ------------------------
|
||||||
|
* ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
|
||||||
|
*
|
||||||
|
* ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub>
|
||||||
|
* B = ------------------------
|
||||||
|
* ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <p>In fact, we can assume both a and ω are positive and
|
||||||
|
* compute them directly, knowing that A = a<sup>2</sup> ω<sup>2</sup> and that
|
||||||
|
* B = - ω<sup>2</sup>. The complete algorithm is therefore:</p>
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* for each t<sub>i</sub> from t<sub>1</sub> to t<sub>n-1</sub>, compute:
|
||||||
|
* f (t<sub>i</sub>)
|
||||||
|
* f' (t<sub>i</sub>) = (f (t<sub>i+1</sub>) - f(t<sub>i-1</sub>)) / (t<sub>i+1</sub> - t<sub>i-1</sub>)
|
||||||
|
* x<sub>i</sub> = t<sub>i</sub> - t<sub>1</sub>
|
||||||
|
* y<sub>i</sub> = ∫ f<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
|
||||||
|
* z<sub>i</sub> = ∫ f'<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
|
||||||
|
* update the sums ∑x<sub>i</sub>x<sub>i</sub>, ∑y<sub>i</sub>y<sub>i</sub>, ∑x<sub>i</sub>y<sub>i</sub>, ∑x<sub>i</sub>z<sub>i</sub> and ∑y<sub>i</sub>z<sub>i</sub>
|
||||||
|
* end for
|
||||||
|
*
|
||||||
|
* |--------------------------
|
||||||
|
* \ | ∑y<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
|
||||||
|
* a = \ | ------------------------
|
||||||
|
* \| ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* |--------------------------
|
||||||
|
* \ | ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
|
||||||
|
* ω = \ | ------------------------
|
||||||
|
* \| ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
|
||||||
|
* <p>Once we know ω, we can compute:
|
||||||
|
* <pre>
|
||||||
|
* fc = ω f (t) cos (ω t) - f' (t) sin (ω t)
|
||||||
|
* fs = ω f (t) sin (ω t) + f' (t) cos (ω t)
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
|
||||||
|
* <p>It appears that <code>fc = a ω cos (φ)</code> and
|
||||||
|
* <code>fs = -a ω sin (φ)</code>, so we can use these
|
||||||
|
* expressions to compute φ. The best estimate over the sample is
|
||||||
|
* given by averaging these expressions.
|
||||||
|
* </p>
|
||||||
|
|
||||||
|
* <p>Since integrals and means are involved in the preceding
|
||||||
|
* estimations, these operations run in O(n) time, where n is the
|
||||||
|
* number of measurements.</p>
|
||||||
|
|
||||||
|
* @version $Revision$ $Date$
|
||||||
|
* @since 2.0
|
||||||
|
|
||||||
|
*/
|
||||||
|
public class HarmonicCoefficientsGuesser {
|
||||||
|
|
||||||
|
/** Sampled observations. */
|
||||||
|
private final WeightedObservedPoint[] observations;
|
||||||
|
|
||||||
|
/** Guessed amplitude. */
|
||||||
|
private double a;
|
||||||
|
|
||||||
|
/** Guessed pulsation ω. */
|
||||||
|
private double omega;
|
||||||
|
|
||||||
|
/** Guessed phase φ. */
|
||||||
|
private double phi;
|
||||||
|
|
||||||
|
/** Simple constructor.
|
||||||
|
* @param observations sampled observations
|
||||||
|
*/
|
||||||
|
public HarmonicCoefficientsGuesser(WeightedObservedPoint[] observations) {
|
||||||
|
this.observations = observations.clone();
|
||||||
|
a = Double.NaN;
|
||||||
|
omega = Double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Estimate a first guess of the coefficients.
|
||||||
|
* @exception OptimizationException if the sample is too short or if
|
||||||
|
* the first guess cannot be computed (when the elements under the
|
||||||
|
* square roots are negative).
|
||||||
|
* */
|
||||||
|
public void guess() throws OptimizationException {
|
||||||
|
sortObservations();
|
||||||
|
guessAOmega();
|
||||||
|
guessPhi();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sort the observations with respect to the abscissa.
|
||||||
|
*/
|
||||||
|
private void sortObservations() {
|
||||||
|
|
||||||
|
// Since the samples are almost always already sorted, this
|
||||||
|
// method is implemented as an insertion sort that reorders the
|
||||||
|
// elements in place. Insertion sort is very efficient in this case.
|
||||||
|
WeightedObservedPoint curr = observations[0];
|
||||||
|
for (int j = 1; j < observations.length; ++j) {
|
||||||
|
WeightedObservedPoint prec = curr;
|
||||||
|
curr = observations[j];
|
||||||
|
if (curr.getX() < prec.getX()) {
|
||||||
|
// the current element should be inserted closer to the beginning
|
||||||
|
int i = j - 1;
|
||||||
|
WeightedObservedPoint mI = observations[i];
|
||||||
|
while ((i >= 0) && (curr.getX() < mI.getX())) {
|
||||||
|
observations[i + 1] = mI;
|
||||||
|
if (i-- != 0) {
|
||||||
|
mI = observations[i];
|
||||||
|
} else {
|
||||||
|
mI = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observations[i + 1] = curr;
|
||||||
|
curr = observations[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Estimate a first guess of the a and ω coefficients.
|
||||||
|
* @exception OptimizationException if the sample is too short or if
|
||||||
|
* the first guess cannot be computed (when the elements under the
|
||||||
|
* square roots are negative).
|
||||||
|
*/
|
||||||
|
private void guessAOmega() throws OptimizationException {
|
||||||
|
|
||||||
|
// initialize the sums for the linear model between the two integrals
|
||||||
|
double sx2 = 0.0;
|
||||||
|
double sy2 = 0.0;
|
||||||
|
double sxy = 0.0;
|
||||||
|
double sxz = 0.0;
|
||||||
|
double syz = 0.0;
|
||||||
|
|
||||||
|
double currentX = observations[0].getX();
|
||||||
|
double currentY = observations[0].getY();
|
||||||
|
double f2Integral = 0;
|
||||||
|
double fPrime2Integral = 0;
|
||||||
|
final double startX = currentX;
|
||||||
|
for (int i = 1; i < observations.length; ++i) {
|
||||||
|
|
||||||
|
// one step forward
|
||||||
|
final double previousX = currentX;
|
||||||
|
final double previousY = currentY;
|
||||||
|
currentX = observations[i].getX();
|
||||||
|
currentY = observations[i].getY();
|
||||||
|
|
||||||
|
// update the integrals of f<sup>2</sup> and f'<sup>2</sup>
|
||||||
|
// considering a linear model for f (and therefore constant f')
|
||||||
|
final double dx = currentX - previousX;
|
||||||
|
final double dy = currentY - previousY;
|
||||||
|
final double f2StepIntegral =
|
||||||
|
dx * (previousY * previousY + previousY * currentY + currentY * currentY) / 3;
|
||||||
|
final double fPrime2StepIntegral = dy * dy / dx;
|
||||||
|
|
||||||
|
final double x = currentX - startX;
|
||||||
|
f2Integral += f2StepIntegral;
|
||||||
|
fPrime2Integral += fPrime2StepIntegral;
|
||||||
|
|
||||||
|
sx2 += x * x;
|
||||||
|
sy2 += f2Integral * f2Integral;
|
||||||
|
sxy += x * f2Integral;
|
||||||
|
sxz += x * fPrime2Integral;
|
||||||
|
syz += f2Integral * fPrime2Integral;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the amplitude and pulsation coefficients
|
||||||
|
double c1 = sy2 * sxz - sxy * syz;
|
||||||
|
double c2 = sxy * sxz - sx2 * syz;
|
||||||
|
double c3 = sx2 * sy2 - sxy * sxy;
|
||||||
|
if ((c1 / c2 < 0.0) || (c2 / c3 < 0.0)) {
|
||||||
|
throw new OptimizationException("unable to first guess the harmonic coefficients");
|
||||||
|
}
|
||||||
|
a = Math.sqrt(c1 / c2);
|
||||||
|
omega = Math.sqrt(c2 / c3);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Estimate a first guess of the φ coefficient.
|
||||||
|
*/
|
||||||
|
private void guessPhi() {
|
||||||
|
|
||||||
|
// initialize the means
|
||||||
|
double fcMean = 0.0;
|
||||||
|
double fsMean = 0.0;
|
||||||
|
|
||||||
|
double currentX = observations[0].getX();
|
||||||
|
double currentY = observations[0].getY();
|
||||||
|
for (int i = 1; i < observations.length; ++i) {
|
||||||
|
|
||||||
|
// one step forward
|
||||||
|
final double previousX = currentX;
|
||||||
|
final double previousY = currentY;
|
||||||
|
currentX = observations[i].getX();
|
||||||
|
currentY = observations[i].getY();
|
||||||
|
final double currentYPrime = (currentY - previousY) / (currentX - previousX);
|
||||||
|
|
||||||
|
double omegaX = omega * currentX;
|
||||||
|
double cosine = Math.cos(omegaX);
|
||||||
|
double sine = Math.sin(omegaX);
|
||||||
|
fcMean += omega * currentY * cosine - currentYPrime * sine;
|
||||||
|
fsMean += omega * currentY * sine + currentYPrime * cosine;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
phi = Math.atan2(-fsMean, fcMean);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the guessed amplitude a.
|
||||||
|
* @return guessed amplitude a;
|
||||||
|
*/
|
||||||
|
public double getGuessedAmplitude() {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the guessed pulsation ω.
|
||||||
|
* @return guessed pulsation ω
|
||||||
|
*/
|
||||||
|
public double getGuessedPulsation() {
|
||||||
|
return omega;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the guessed phase φ.
|
||||||
|
* @return guessed phase φ
|
||||||
|
*/
|
||||||
|
public double getGuessedPhase() {
|
||||||
|
return phi;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import org.apache.commons.math.FunctionEvaluationException;
|
||||||
|
import org.apache.commons.math.MathRuntimeException;
|
||||||
|
import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
|
||||||
|
import org.apache.commons.math.optimization.OptimizationException;
|
||||||
|
|
||||||
|
/** This class implements a curve fitting specialized for sinusoids.
|
||||||
|
* <p>Harmonic fitting is a very simple case of curve fitting. The
|
||||||
|
* estimated coefficients are the amplitude a, the pulsation ω and
|
||||||
|
* the phase φ: <code>f (t) = a cos (ω t + φ)</code>. They are
|
||||||
|
* searched by a least square estimator initialized with a rough guess
|
||||||
|
* based on integrals.</p>
|
||||||
|
* @version $Revision$ $Date$
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public class HarmonicFitter {
|
||||||
|
|
||||||
|
/** Fitter for the coefficients. */
|
||||||
|
private final CurveFitter fitter;
|
||||||
|
|
||||||
|
/** Values for amplitude, pulsation ω and phase φ. */
|
||||||
|
private double[] parameters;
|
||||||
|
|
||||||
|
/** Simple constructor.
|
||||||
|
* @param optimizer optimizer to use for the fitting
|
||||||
|
*/
|
||||||
|
public HarmonicFitter(final DifferentiableMultivariateVectorialOptimizer optimizer) {
|
||||||
|
this.fitter = new CurveFitter(optimizer);
|
||||||
|
parameters = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Simple constructor.
|
||||||
|
* <p>This constructor can be used when a first guess of the
|
||||||
|
* coefficients is already known.</p>
|
||||||
|
* @param optimizer optimizer to use for the fitting
|
||||||
|
* @param initialGuess guessed values for amplitude (index 0),
|
||||||
|
* pulsation ω (index 1) and phase φ (index 2)
|
||||||
|
*/
|
||||||
|
public HarmonicFitter(final DifferentiableMultivariateVectorialOptimizer optimizer,
|
||||||
|
final double[] initialGuess) {
|
||||||
|
this.fitter = new CurveFitter(optimizer);
|
||||||
|
this.parameters = initialGuess.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add an observed weighted (x,y) point to the sample.
|
||||||
|
* @param weight weight of the observed point in the fit
|
||||||
|
* @param x abscissa of the point
|
||||||
|
* @param y observed value of the point at x, after fitting we should
|
||||||
|
* have P(x) as close as possible to this value
|
||||||
|
*/
|
||||||
|
public void addObservedPoint(double weight, double x, double y) {
|
||||||
|
fitter.addObservedPoint(weight, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fit an harmonic function to the observed points.
|
||||||
|
* @return harmonic function best fitting the observed points
|
||||||
|
* @throws OptimizationException if the sample is too short or if
|
||||||
|
* the first guess cannot be computed
|
||||||
|
*/
|
||||||
|
public HarmonicFunction fit() throws OptimizationException {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// shall we compute the first guess of the parameters ourselves ?
|
||||||
|
if (parameters == null) {
|
||||||
|
final WeightedObservedPoint[] observations = fitter.getObservations();
|
||||||
|
if (observations.length < 4) {
|
||||||
|
throw new OptimizationException("sample contains {0} observed points, at least {1} are required",
|
||||||
|
observations.length, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
HarmonicCoefficientsGuesser guesser = new HarmonicCoefficientsGuesser(observations);
|
||||||
|
guesser.guess();
|
||||||
|
parameters = new double[] {
|
||||||
|
guesser.getGuessedAmplitude(),
|
||||||
|
guesser.getGuessedPulsation(),
|
||||||
|
guesser.getGuessedPhase()
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
double[] fitted = fitter.fit(new ParametricHarmonicFunction(), parameters);
|
||||||
|
return new HarmonicFunction(fitted[0], fitted[1], fitted[2]);
|
||||||
|
|
||||||
|
} catch (FunctionEvaluationException fee) {
|
||||||
|
// this should never happen
|
||||||
|
throw MathRuntimeException.createInternalError(fee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parametric harmonic function. */
|
||||||
|
private static class ParametricHarmonicFunction implements ParametricRealFunction {
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public double value(double x, double[] parameters) {
|
||||||
|
final double a = parameters[0];
|
||||||
|
final double omega = parameters[1];
|
||||||
|
final double phi = parameters[2];
|
||||||
|
return a * Math.cos(omega * x + phi);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public double[] gradient(double x, double[] parameters) {
|
||||||
|
final double a = parameters[0];
|
||||||
|
final double omega = parameters[1];
|
||||||
|
final double phi = parameters[2];
|
||||||
|
final double alpha = omega * x + phi;
|
||||||
|
final double cosAlpha = Math.cos(alpha);
|
||||||
|
final double sinAlpha = Math.sin(alpha);
|
||||||
|
return new double[] { cosAlpha, -a * x * sinAlpha, -a * sinAlpha };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
|
||||||
|
|
||||||
|
/** Harmonic function of the form <code>f (t) = a cos (ω t + φ)</code>.
|
||||||
|
* @version $Revision$ $Date$
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public class HarmonicFunction implements DifferentiableUnivariateRealFunction {
|
||||||
|
|
||||||
|
/** Amplitude a. */
|
||||||
|
private final double a;
|
||||||
|
|
||||||
|
/** Pulsation ω. */
|
||||||
|
private final double omega;
|
||||||
|
|
||||||
|
/** Phase φ. */
|
||||||
|
private final double phi;
|
||||||
|
|
||||||
|
/** Simple constructor.
|
||||||
|
* @param a amplitude
|
||||||
|
* @param omega pulsation
|
||||||
|
* @param phi phase
|
||||||
|
*/
|
||||||
|
public HarmonicFunction(double a, double omega, double phi) {
|
||||||
|
this.a = a;
|
||||||
|
this.omega = omega;
|
||||||
|
this.phi = phi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public double value(double x) {
|
||||||
|
return a * Math.cos(omega * x + phi);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public HarmonicFunction derivative() {
|
||||||
|
return new HarmonicFunction(a * omega, omega, phi + Math.PI / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the amplitude a.
|
||||||
|
* @return amplitude a;
|
||||||
|
*/
|
||||||
|
public double getAmplitude() {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the pulsation ω.
|
||||||
|
* @return pulsation ω
|
||||||
|
*/
|
||||||
|
public double getPulsation() {
|
||||||
|
return omega;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the phase φ.
|
||||||
|
* @return phase φ
|
||||||
|
*/
|
||||||
|
public double getPhase() {
|
||||||
|
return phi;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import org.apache.commons.math.FunctionEvaluationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface representing a real function that depends on one independent
|
||||||
|
* variable plus some extra parameters.
|
||||||
|
*
|
||||||
|
* @version $Revision$ $Date$
|
||||||
|
*/
|
||||||
|
public interface ParametricRealFunction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the value of the function.
|
||||||
|
* @param x the point for which the function value should be computed
|
||||||
|
* @param parameters function parameters
|
||||||
|
* @return the value
|
||||||
|
* @throws FunctionEvaluationException if the function evaluation fails
|
||||||
|
*/
|
||||||
|
public double value(double x, double[] parameters)
|
||||||
|
throws FunctionEvaluationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the gradient of the function with respect to its parameters.
|
||||||
|
* @param x the point for which the function value should be computed
|
||||||
|
* @param parameters function parameters
|
||||||
|
* @return the value
|
||||||
|
* @throws FunctionEvaluationException if the function evaluation fails
|
||||||
|
*/
|
||||||
|
public double[] gradient(double x, double[] parameters)
|
||||||
|
throws FunctionEvaluationException;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import org.apache.commons.math.FunctionEvaluationException;
|
||||||
|
import org.apache.commons.math.MathRuntimeException;
|
||||||
|
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
|
||||||
|
import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
|
||||||
|
import org.apache.commons.math.optimization.OptimizationException;
|
||||||
|
|
||||||
|
/** This class implements a curve fitting specialized for polynomials.
|
||||||
|
* <p>Polynomial fitting is a very simple case of curve fitting. The
|
||||||
|
* estimated coefficients are the polynomial coefficients. They are
|
||||||
|
* searched by a least square estimator.</p>
|
||||||
|
* @version $Revision$ $Date$
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class PolynomialFitter {
|
||||||
|
|
||||||
|
/** Fitter for the coefficients. */
|
||||||
|
private final CurveFitter fitter;
|
||||||
|
|
||||||
|
/** Polynomial degree. */
|
||||||
|
private final int degree;
|
||||||
|
|
||||||
|
/** Simple constructor.
|
||||||
|
* <p>The polynomial fitter built this way are complete polynomials,
|
||||||
|
* ie. a n-degree polynomial has n+1 coefficients.</p>
|
||||||
|
* @param degree maximal degree of the polynomial
|
||||||
|
* @param optimizer optimizer to use for the fitting
|
||||||
|
*/
|
||||||
|
public PolynomialFitter(int degree, final DifferentiableMultivariateVectorialOptimizer optimizer) {
|
||||||
|
this.fitter = new CurveFitter(optimizer);
|
||||||
|
this.degree = degree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add an observed weighted (x,y) point to the sample.
|
||||||
|
* @param weight weight of the observed point in the fit
|
||||||
|
* @param x abscissa of the point
|
||||||
|
* @param y observed value of the point at x, after fitting we should
|
||||||
|
* have P(x) as close as possible to this value
|
||||||
|
*/
|
||||||
|
public void addObservedPoint(double weight, double x, double y) {
|
||||||
|
fitter.addObservedPoint(weight, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the polynomial fitting the weighted (x, y) points.
|
||||||
|
* @return polynomial function best fitting the observed points
|
||||||
|
* @exception OptimizationException if the algorithm failed to converge
|
||||||
|
*/
|
||||||
|
public PolynomialFunction fit()
|
||||||
|
throws OptimizationException {
|
||||||
|
try {
|
||||||
|
return new PolynomialFunction(fitter.fit(new ParametricPolynomial(), new double[degree + 1]));
|
||||||
|
} catch (FunctionEvaluationException fee) {
|
||||||
|
// this should never happen
|
||||||
|
throw MathRuntimeException.createInternalError(fee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dedicated parametric polynomial class. */
|
||||||
|
private static class ParametricPolynomial implements ParametricRealFunction {
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public double[] gradient(double x, double[] parameters)
|
||||||
|
throws FunctionEvaluationException {
|
||||||
|
final double[] gradient = new double[parameters.length];
|
||||||
|
double xn = 1.0;
|
||||||
|
for (int i = 0; i < parameters.length; ++i) {
|
||||||
|
gradient[i] = xn;
|
||||||
|
xn *= x;
|
||||||
|
}
|
||||||
|
return gradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public double value(final double x, final double[] parameters) {
|
||||||
|
double y = 0;
|
||||||
|
for (int i = parameters.length - 1; i >= 0; --i) {
|
||||||
|
y = y * x + parameters[i];
|
||||||
|
}
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/** This class is a simple container for weighted observed point in
|
||||||
|
* {@link CurveFitter curve fitting}.
|
||||||
|
* <p>Instances of this class are guaranteed to be immutable.</p>
|
||||||
|
* @version $Revision$ $Date$
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public class WeightedObservedPoint implements Serializable {
|
||||||
|
|
||||||
|
/** Serializable version id. */
|
||||||
|
private static final long serialVersionUID = 5306874947404636157L;
|
||||||
|
|
||||||
|
/** Weight of the measurement in the fitting process. */
|
||||||
|
private final double weight;
|
||||||
|
|
||||||
|
/** Abscissa of the point. */
|
||||||
|
private final double x;
|
||||||
|
|
||||||
|
/** Observed value of the function at x. */
|
||||||
|
private final double y;
|
||||||
|
|
||||||
|
/** Simple constructor.
|
||||||
|
* @param weight weight of the measurement in the fitting process
|
||||||
|
* @param x abscissa of the measurement
|
||||||
|
* @param y ordinate of the measurement
|
||||||
|
*/
|
||||||
|
public WeightedObservedPoint(final double weight, final double x, final double y) {
|
||||||
|
this.weight = weight;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the weight of the measurement in the fitting process.
|
||||||
|
* @return weight of the measurement in the fitting process
|
||||||
|
*/
|
||||||
|
public double getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the abscissa of the point.
|
||||||
|
* @return abscissa of the point
|
||||||
|
*/
|
||||||
|
public double getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the observed value of the function at x.
|
||||||
|
* @return observed value of the function at x
|
||||||
|
*/
|
||||||
|
public double getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,222 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.estimation.*;
|
|
||||||
|
|
||||||
/** This class is the base class for all curve fitting classes in the package.
|
|
||||||
|
|
||||||
* <p>This class handles all common features of curve fitting like the
|
|
||||||
* sample points handling. It declares two methods ({@link
|
|
||||||
* #valueAt} and {@link #partial}) which should be implemented by
|
|
||||||
* sub-classes to define the precise shape of the curve they
|
|
||||||
* represent.</p>
|
|
||||||
|
|
||||||
* @version $Id$
|
|
||||||
* @author L. Maisonobe
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public abstract class AbstractCurveFitter
|
|
||||||
implements EstimationProblem, Serializable {
|
|
||||||
|
|
||||||
/** Simple constructor.
|
|
||||||
* @param n number of coefficients in the underlying function
|
|
||||||
* @param estimator estimator to use for the fitting
|
|
||||||
*/
|
|
||||||
protected AbstractCurveFitter(int n, Estimator estimator) {
|
|
||||||
|
|
||||||
coefficients = new EstimatedParameter[n];
|
|
||||||
measurements = new ArrayList();
|
|
||||||
this.estimator = estimator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Simple constructor.
|
|
||||||
* @param coefficients first estimate of the coefficients. A
|
|
||||||
* reference to this array is hold by the newly created object. Its
|
|
||||||
* elements will be adjusted during the fitting process and they will
|
|
||||||
* be set to the adjusted coefficients at the end.
|
|
||||||
* @param estimator estimator to use for the fitting
|
|
||||||
*/
|
|
||||||
protected AbstractCurveFitter(EstimatedParameter[] coefficients,
|
|
||||||
Estimator estimator) {
|
|
||||||
|
|
||||||
this.coefficients = coefficients;
|
|
||||||
measurements = new ArrayList();
|
|
||||||
this.estimator = estimator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Add a weighted (x,y) pair to the sample.
|
|
||||||
* @param weight weight of this pair in the fit
|
|
||||||
* @param x abscissa
|
|
||||||
* @param y ordinate, we have <code>y = f (x)</code>
|
|
||||||
*/
|
|
||||||
public void addWeightedPair(double weight, double x, double y) {
|
|
||||||
measurements.add(new FitMeasurement(weight, x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Perform the fitting.
|
|
||||||
|
|
||||||
* <p>This method compute the coefficients of the curve that best
|
|
||||||
* fit the sample of weighted pairs previously given through calls
|
|
||||||
* to the {@link #addWeightedPair addWeightedPair} method.</p>
|
|
||||||
|
|
||||||
* @return coefficients of the curve
|
|
||||||
* @exception EstimationException if the fitting is not possible
|
|
||||||
* (for example if the sample has to few independant points)
|
|
||||||
|
|
||||||
*/
|
|
||||||
public double[] fit()
|
|
||||||
throws EstimationException {
|
|
||||||
// perform the fit
|
|
||||||
estimator.estimate(this);
|
|
||||||
|
|
||||||
// extract the coefficients
|
|
||||||
double[] fittedCoefficients = new double[coefficients.length];
|
|
||||||
for (int i = 0; i < coefficients.length; ++i) {
|
|
||||||
fittedCoefficients[i] = coefficients[i].getEstimate();
|
|
||||||
}
|
|
||||||
|
|
||||||
return fittedCoefficients;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public WeightedMeasurement[] getMeasurements() {
|
|
||||||
return (WeightedMeasurement[]) measurements.toArray(new FitMeasurement[measurements.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the unbound parameters of the problem.
|
|
||||||
* For a curve fitting, none of the function coefficient is bound.
|
|
||||||
* @return unbound parameters
|
|
||||||
*/
|
|
||||||
public EstimatedParameter[] getUnboundParameters() {
|
|
||||||
return (EstimatedParameter[]) coefficients.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get all the parameters of the problem.
|
|
||||||
* @return parameters
|
|
||||||
*/
|
|
||||||
public EstimatedParameter[] getAllParameters() {
|
|
||||||
return (EstimatedParameter[]) coefficients.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Utility method to sort the measurements with respect to the abscissa.
|
|
||||||
|
|
||||||
* <p>This method is provided as a utility for derived classes. As
|
|
||||||
* an example, the {@link HarmonicFitter} class needs it in order to
|
|
||||||
* compute a first guess of the coefficients to initialize the
|
|
||||||
* estimation algorithm.</p>
|
|
||||||
|
|
||||||
*/
|
|
||||||
protected void sortMeasurements() {
|
|
||||||
|
|
||||||
// Since the samples are almost always already sorted, this
|
|
||||||
// method is implemented as an insertion sort that reorders the
|
|
||||||
// elements in place. Insertion sort is very efficient in this case.
|
|
||||||
FitMeasurement curr = (FitMeasurement) measurements.get(0);
|
|
||||||
for (int j = 1; j < measurements.size (); ++j) {
|
|
||||||
FitMeasurement prec = curr;
|
|
||||||
curr = (FitMeasurement) measurements.get(j);
|
|
||||||
if (curr.x < prec.x) {
|
|
||||||
// the current element should be inserted closer to the beginning
|
|
||||||
int i = j - 1;
|
|
||||||
FitMeasurement mI = (FitMeasurement) measurements.get(i);
|
|
||||||
while ((i >= 0) && (curr.x < mI.x)) {
|
|
||||||
measurements.set(i + 1, mI);
|
|
||||||
if (i-- != 0) {
|
|
||||||
mI = (FitMeasurement) measurements.get(i);
|
|
||||||
} else {
|
|
||||||
mI = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
measurements.set(i + 1, curr);
|
|
||||||
curr = (FitMeasurement) measurements.get(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the value of the function at x according to the current parameters value.
|
|
||||||
* @param x abscissa at which the theoretical value is requested
|
|
||||||
* @return theoretical value at x
|
|
||||||
*/
|
|
||||||
public abstract double valueAt(double x);
|
|
||||||
|
|
||||||
/** Get the derivative of the function at x with respect to parameter p.
|
|
||||||
* @param x abscissa at which the partial derivative is requested
|
|
||||||
* @param p parameter with respect to which the derivative is requested
|
|
||||||
* @return partial derivative
|
|
||||||
*/
|
|
||||||
public abstract double partial(double x, EstimatedParameter p);
|
|
||||||
|
|
||||||
/** This class represents the fit measurements.
|
|
||||||
* One measurement is a weighted pair (x, y), where <code>y = f
|
|
||||||
* (x)</code> is the value of the function at x abscissa. This class
|
|
||||||
* is an inner class because the methods related to the computation
|
|
||||||
* of f values and derivative are proveded by the fitter
|
|
||||||
* implementations.
|
|
||||||
*/
|
|
||||||
public class FitMeasurement
|
|
||||||
extends WeightedMeasurement {
|
|
||||||
|
|
||||||
/** Simple constructor.
|
|
||||||
* @param weight weight of the measurement in the fitting process
|
|
||||||
* @param x abscissa of the measurement
|
|
||||||
* @param y ordinate of the measurement
|
|
||||||
*/
|
|
||||||
public FitMeasurement(double weight, double x, double y) {
|
|
||||||
super(weight, y);
|
|
||||||
this.x = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the value of the fitted function at x.
|
|
||||||
* @return theoretical value at the measurement abscissa
|
|
||||||
*/
|
|
||||||
public double getTheoreticalValue() {
|
|
||||||
return valueAt(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Partial derivative with respect to one function coefficient.
|
|
||||||
* @param p parameter with respect to which the derivative is requested
|
|
||||||
* @return partial derivative
|
|
||||||
*/
|
|
||||||
public double getPartial(EstimatedParameter p) {
|
|
||||||
return partial(x, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Abscissa of the measurement. */
|
|
||||||
public final double x;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -2682582852369995960L;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Coefficients of the function */
|
|
||||||
protected EstimatedParameter[] coefficients;
|
|
||||||
|
|
||||||
/** Measurements vector */
|
|
||||||
protected List measurements;
|
|
||||||
|
|
||||||
/** Estimator for the fitting problem. */
|
|
||||||
private Estimator estimator;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.functions.FunctionException;
|
|
||||||
import org.spaceroots.mantissa.functions.ExhaustedSampleException;
|
|
||||||
import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator;
|
|
||||||
import org.spaceroots.mantissa.functions.vectorial.VectorialValuedPair;
|
|
||||||
|
|
||||||
/** This class provides sampled values of the function t -> [f(t)^2, f'(t)^2].
|
|
||||||
|
|
||||||
* This class is a helper class used to compute a first guess of the
|
|
||||||
* harmonic coefficients of a function <code>f (t) = a cos (omega t +
|
|
||||||
* phi)</code>.
|
|
||||||
|
|
||||||
* @see FFPIterator
|
|
||||||
* @see HarmonicCoefficientsGuesser
|
|
||||||
|
|
||||||
* @version $Id$
|
|
||||||
* @author L. Maisonobe
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
class F2FP2Iterator
|
|
||||||
implements SampledFunctionIterator, Serializable {
|
|
||||||
|
|
||||||
public F2FP2Iterator(AbstractCurveFitter.FitMeasurement[] measurements) {
|
|
||||||
ffpIterator = new FFPIterator(measurements);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDimension() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasNext() {
|
|
||||||
return ffpIterator.hasNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
public VectorialValuedPair nextSamplePoint()
|
|
||||||
throws ExhaustedSampleException, FunctionException {
|
|
||||||
|
|
||||||
// get the raw values from the underlying FFPIterator
|
|
||||||
VectorialValuedPair point = ffpIterator.nextSamplePoint();
|
|
||||||
double[] y = point.y;
|
|
||||||
|
|
||||||
// square the values
|
|
||||||
return new VectorialValuedPair(point.x,
|
|
||||||
new double[] {
|
|
||||||
y[0] * y[0], y[1] * y[1]
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private FFPIterator ffpIterator;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -8113110433795298072L;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.functions.FunctionException;
|
|
||||||
import org.spaceroots.mantissa.functions.ExhaustedSampleException;
|
|
||||||
import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator;
|
|
||||||
import org.spaceroots.mantissa.functions.vectorial.VectorialValuedPair;
|
|
||||||
|
|
||||||
/** This class provides sampled values of the function t -> [f(t), f'(t)].
|
|
||||||
|
|
||||||
* This class is a helper class used to compute a first guess of the
|
|
||||||
* harmonic coefficients of a function <code>f (t) = a cos (omega t +
|
|
||||||
* phi)</code>.
|
|
||||||
|
|
||||||
* @see F2FP2Iterator
|
|
||||||
* @see HarmonicCoefficientsGuesser
|
|
||||||
|
|
||||||
* @version $Id$
|
|
||||||
* @author L. Maisonobe
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
class FFPIterator
|
|
||||||
implements SampledFunctionIterator, Serializable {
|
|
||||||
|
|
||||||
public FFPIterator(AbstractCurveFitter.FitMeasurement[] measurements) {
|
|
||||||
this.measurements = measurements;
|
|
||||||
|
|
||||||
// initialize the points of the raw sample
|
|
||||||
current = measurements[0];
|
|
||||||
currentY = current.getMeasuredValue();
|
|
||||||
next = measurements[1];
|
|
||||||
nextY = next.getMeasuredValue();
|
|
||||||
nextIndex = 2;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDimension() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasNext() {
|
|
||||||
return nextIndex < measurements.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VectorialValuedPair nextSamplePoint()
|
|
||||||
throws ExhaustedSampleException, FunctionException {
|
|
||||||
if (nextIndex >= measurements.length) {
|
|
||||||
throw new ExhaustedSampleException(measurements.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// shift the points
|
|
||||||
previous = current;
|
|
||||||
previousY = currentY;
|
|
||||||
current = next;
|
|
||||||
currentY = nextY;
|
|
||||||
next = measurements[nextIndex++];
|
|
||||||
nextY = next.getMeasuredValue();
|
|
||||||
|
|
||||||
// return the two dimensions vector [f(x), f'(x)]
|
|
||||||
double[] table = new double[2];
|
|
||||||
table[0] = currentY;
|
|
||||||
table[1] = (nextY - previousY) / (next.x - previous.x);
|
|
||||||
return new VectorialValuedPair(current.x, table);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private AbstractCurveFitter.FitMeasurement[] measurements;
|
|
||||||
private int nextIndex;
|
|
||||||
|
|
||||||
private AbstractCurveFitter.FitMeasurement previous;
|
|
||||||
private double previousY;
|
|
||||||
|
|
||||||
private AbstractCurveFitter.FitMeasurement current;
|
|
||||||
private double nextY;
|
|
||||||
|
|
||||||
private AbstractCurveFitter.FitMeasurement next;
|
|
||||||
private double currentY;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -3187229691615380125L;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,269 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.functions.FunctionException;
|
|
||||||
import org.spaceroots.mantissa.functions.ExhaustedSampleException;
|
|
||||||
import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator;
|
|
||||||
import org.spaceroots.mantissa.functions.vectorial.VectorialValuedPair;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.quadrature.vectorial.EnhancedSimpsonIntegratorSampler;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.estimation.EstimationException;
|
|
||||||
|
|
||||||
/** This class guesses harmonic coefficients from a sample.
|
|
||||||
|
|
||||||
* <p>The algorithm used to guess the coefficients is as follows:</p>
|
|
||||||
|
|
||||||
* <p>We know f (t) at some sampling points t<sub>i</sub> and want to find a,
|
|
||||||
* ω and φ such that f (t) = a cos (ω t + φ).
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>From the analytical expression, we can compute two primitives :
|
|
||||||
* <pre>
|
|
||||||
* If2 (t) = ∫ f<sup>2</sup> = a<sup>2</sup> × [t + S (t)] / 2
|
|
||||||
* If'2 (t) = ∫ f'<sup>2</sup> = a<sup>2</sup> ω<sup>2</sup> × [t - S (t)] / 2
|
|
||||||
* where S (t) = sin (2 (ω t + φ)) / (2 ω)
|
|
||||||
* </pre>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>We can remove S between these expressions :
|
|
||||||
* <pre>
|
|
||||||
* If'2 (t) = a<sup>2</sup> ω<sup>2</sup> t - ω<sup>2</sup> If2 (t)
|
|
||||||
* </pre>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>The preceding expression shows that If'2 (t) is a linear
|
|
||||||
* combination of both t and If2 (t): If'2 (t) = A × t + B × If2 (t)
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>From the primitive, we can deduce the same form for definite
|
|
||||||
* integrals between t<sub>1</sub> and t<sub>i</sub> for each t<sub>i</sub> :
|
|
||||||
* <pre>
|
|
||||||
* If2 (t<sub>i</sub>) - If2 (t<sub>1</sub>) = A × (t<sub>i</sub> - t<sub>1</sub>) + B × (If2 (t<sub>i</sub>) - If2 (t<sub>1</sub>))
|
|
||||||
* </pre>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>We can find the coefficients A and B that best fit the sample
|
|
||||||
* to this linear expression by computing the definite integrals for
|
|
||||||
* each sample points.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>For a bilinear expression z (x<sub>i</sub>, y<sub>i</sub>) = A × x<sub>i</sub> + B × y<sub>i</sub>, the
|
|
||||||
* coefficients A and B that minimize a least square criterion
|
|
||||||
* ∑ (z<sub>i</sub> - z (x<sub>i</sub>, y<sub>i</sub>))<sup>2</sup> are given by these expressions:</p>
|
|
||||||
* <pre>
|
|
||||||
*
|
|
||||||
* ∑y<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
|
|
||||||
* A = ------------------------
|
|
||||||
* ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
|
|
||||||
*
|
|
||||||
* ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub>
|
|
||||||
* B = ------------------------
|
|
||||||
* ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
|
|
||||||
* </pre>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <p>In fact, we can assume both a and ω are positive and
|
|
||||||
* compute them directly, knowing that A = a<sup>2</sup> ω<sup>2</sup> and that
|
|
||||||
* B = - ω<sup>2</sup>. The complete algorithm is therefore:</p>
|
|
||||||
* <pre>
|
|
||||||
*
|
|
||||||
* for each t<sub>i</sub> from t<sub>1</sub> to t<sub>n-1</sub>, compute:
|
|
||||||
* f (t<sub>i</sub>)
|
|
||||||
* f' (t<sub>i</sub>) = (f (t<sub>i+1</sub>) - f(t<sub>i-1</sub>)) / (t<sub>i+1</sub> - t<sub>i-1</sub>)
|
|
||||||
* x<sub>i</sub> = t<sub>i</sub> - t<sub>1</sub>
|
|
||||||
* y<sub>i</sub> = ∫ f<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
|
|
||||||
* z<sub>i</sub> = ∫ f'<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
|
|
||||||
* update the sums ∑x<sub>i</sub>x<sub>i</sub>, ∑y<sub>i</sub>y<sub>i</sub>, ∑x<sub>i</sub>y<sub>i</sub>, ∑x<sub>i</sub>z<sub>i</sub> and ∑y<sub>i</sub>z<sub>i</sub>
|
|
||||||
* end for
|
|
||||||
*
|
|
||||||
* |--------------------------
|
|
||||||
* \ | ∑y<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
|
|
||||||
* a = \ | ------------------------
|
|
||||||
* \| ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* |--------------------------
|
|
||||||
* \ | ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
|
|
||||||
* ω = \ | ------------------------
|
|
||||||
* \| ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
* </p>
|
|
||||||
|
|
||||||
* <p>Once we know ω, we can compute:
|
|
||||||
* <pre>
|
|
||||||
* fc = ω f (t) cos (ω t) - f' (t) sin (ω t)
|
|
||||||
* fs = ω f (t) sin (ω t) + f' (t) cos (ω t)
|
|
||||||
* </pre>
|
|
||||||
* </p>
|
|
||||||
|
|
||||||
* <p>It appears that <code>fc = a ω cos (φ)</code> and
|
|
||||||
* <code>fs = -a ω sin (φ)</code>, so we can use these
|
|
||||||
* expressions to compute φ. The best estimate over the sample is
|
|
||||||
* given by averaging these expressions.
|
|
||||||
* </p>
|
|
||||||
|
|
||||||
* <p>Since integrals and means are involved in the preceding
|
|
||||||
* estimations, these operations run in O(n) time, where n is the
|
|
||||||
* number of measurements.</p>
|
|
||||||
|
|
||||||
* @version $Id$
|
|
||||||
* @author L. Maisonobe
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class HarmonicCoefficientsGuesser
|
|
||||||
implements Serializable{
|
|
||||||
|
|
||||||
public HarmonicCoefficientsGuesser(AbstractCurveFitter.FitMeasurement[] measurements) {
|
|
||||||
this.measurements =
|
|
||||||
(AbstractCurveFitter.FitMeasurement[]) measurements.clone();
|
|
||||||
a = Double.NaN;
|
|
||||||
omega = Double.NaN;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Estimate a first guess of the coefficients.
|
|
||||||
|
|
||||||
* @exception ExhaustedSampleException if the sample is exhausted.
|
|
||||||
|
|
||||||
* @exception FunctionException if the integrator throws one.
|
|
||||||
|
|
||||||
* @exception EstimationException if the sample is too short or if
|
|
||||||
* the first guess cannot be computed (when the elements under the
|
|
||||||
* square roots are negative).
|
|
||||||
* */
|
|
||||||
public void guess()
|
|
||||||
throws ExhaustedSampleException, FunctionException, EstimationException {
|
|
||||||
guessAOmega();
|
|
||||||
guessPhi();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Estimate a first guess of the a and ω coefficients.
|
|
||||||
|
|
||||||
* @exception ExhaustedSampleException if the sample is exhausted.
|
|
||||||
|
|
||||||
* @exception FunctionException if the integrator throws one.
|
|
||||||
|
|
||||||
* @exception EstimationException if the sample is too short or if
|
|
||||||
* the first guess cannot be computed (when the elements under the
|
|
||||||
* square roots are negative).
|
|
||||||
|
|
||||||
*/
|
|
||||||
private void guessAOmega()
|
|
||||||
throws ExhaustedSampleException, FunctionException, EstimationException {
|
|
||||||
|
|
||||||
// initialize the sums for the linear model between the two integrals
|
|
||||||
double sx2 = 0.0;
|
|
||||||
double sy2 = 0.0;
|
|
||||||
double sxy = 0.0;
|
|
||||||
double sxz = 0.0;
|
|
||||||
double syz = 0.0;
|
|
||||||
|
|
||||||
// build the integrals sampler
|
|
||||||
F2FP2Iterator iter = new F2FP2Iterator(measurements);
|
|
||||||
SampledFunctionIterator sampler =
|
|
||||||
new EnhancedSimpsonIntegratorSampler(iter);
|
|
||||||
VectorialValuedPair p0 = sampler.nextSamplePoint();
|
|
||||||
double p0X = p0.x;
|
|
||||||
double[] p0Y = p0.y;
|
|
||||||
|
|
||||||
// get the points for the linear model
|
|
||||||
while (sampler.hasNext()) {
|
|
||||||
|
|
||||||
VectorialValuedPair point = sampler.nextSamplePoint();
|
|
||||||
double pX = point.x;
|
|
||||||
double[] pY = point.y;
|
|
||||||
|
|
||||||
double x = pX - p0X;
|
|
||||||
double y = pY[0] - p0Y[0];
|
|
||||||
double z = pY[1] - p0Y[1];
|
|
||||||
|
|
||||||
sx2 += x * x;
|
|
||||||
sy2 += y * y;
|
|
||||||
sxy += x * y;
|
|
||||||
sxz += x * z;
|
|
||||||
syz += y * z;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute the amplitude and pulsation coefficients
|
|
||||||
double c1 = sy2 * sxz - sxy * syz;
|
|
||||||
double c2 = sxy * sxz - sx2 * syz;
|
|
||||||
double c3 = sx2 * sy2 - sxy * sxy;
|
|
||||||
if ((c1 / c2 < 0.0) || (c2 / c3 < 0.0)) {
|
|
||||||
throw new EstimationException("unable to guess a first estimate");
|
|
||||||
}
|
|
||||||
a = Math.sqrt(c1 / c2);
|
|
||||||
omega = Math.sqrt(c2 / c3);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Estimate a first guess of the φ coefficient.
|
|
||||||
|
|
||||||
* @exception ExhaustedSampleException if the sample is exhausted.
|
|
||||||
|
|
||||||
* @exception FunctionException if the sampler throws one.
|
|
||||||
|
|
||||||
*/
|
|
||||||
private void guessPhi()
|
|
||||||
throws ExhaustedSampleException, FunctionException {
|
|
||||||
|
|
||||||
SampledFunctionIterator iter = new FFPIterator(measurements);
|
|
||||||
|
|
||||||
// initialize the means
|
|
||||||
double fcMean = 0.0;
|
|
||||||
double fsMean = 0.0;
|
|
||||||
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
VectorialValuedPair point = iter.nextSamplePoint();
|
|
||||||
double omegaX = omega * point.x;
|
|
||||||
double cosine = Math.cos(omegaX);
|
|
||||||
double sine = Math.sin(omegaX);
|
|
||||||
fcMean += omega * point.y[0] * cosine - point.y[1] * sine;
|
|
||||||
fsMean += omega * point.y[0] * sine + point.y[1] * cosine;
|
|
||||||
}
|
|
||||||
|
|
||||||
phi = Math.atan2(-fsMean, fcMean);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getOmega() {
|
|
||||||
return omega;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getA() {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getPhi() {
|
|
||||||
return phi;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AbstractCurveFitter.FitMeasurement[] measurements;
|
|
||||||
private double a;
|
|
||||||
private double omega;
|
|
||||||
private double phi;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 2400399048702758814L;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.estimation.EstimatedParameter;
|
|
||||||
import org.spaceroots.mantissa.estimation.EstimationException;
|
|
||||||
import org.spaceroots.mantissa.estimation.Estimator;
|
|
||||||
import org.spaceroots.mantissa.estimation.GaussNewtonEstimator;
|
|
||||||
import org.spaceroots.mantissa.functions.ExhaustedSampleException;
|
|
||||||
import org.spaceroots.mantissa.functions.FunctionException;
|
|
||||||
|
|
||||||
/** This class implements a curve fitting specialized for sinusoids.
|
|
||||||
|
|
||||||
* <p>Harmonic fitting is a very simple case of curve fitting. The
|
|
||||||
* estimated coefficients are the amplitude a, the pulsation omega and
|
|
||||||
* the phase phi: <code>f (t) = a cos (omega t + phi)</code>. They are
|
|
||||||
* searched by a least square estimator initialized with a rough guess
|
|
||||||
* based on integrals.</p>
|
|
||||||
|
|
||||||
* <p>This class <emph>is by no means optimized</emph>, neither versus
|
|
||||||
* space nor versus time performance.</p>
|
|
||||||
|
|
||||||
* @version $Id$
|
|
||||||
* @author L. Maisonobe
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class HarmonicFitter
|
|
||||||
extends AbstractCurveFitter {
|
|
||||||
|
|
||||||
/** Simple constructor.
|
|
||||||
* @param estimator estimator to use for the fitting
|
|
||||||
*/
|
|
||||||
public HarmonicFitter(Estimator estimator) {
|
|
||||||
super(3, estimator);
|
|
||||||
coefficients[0] = new EstimatedParameter("a", 2.0 * Math.PI);
|
|
||||||
coefficients[1] = new EstimatedParameter("omega", 0.0);
|
|
||||||
coefficients[2] = new EstimatedParameter("phi", 0.0);
|
|
||||||
firstGuessNeeded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple constructor.
|
|
||||||
|
|
||||||
* <p>This constructor can be used when a first estimate of the
|
|
||||||
* coefficients is already known.</p>
|
|
||||||
|
|
||||||
* @param coefficients first estimate of the coefficients.
|
|
||||||
* A reference to this array is hold by the newly created
|
|
||||||
* object. Its elements will be adjusted during the fitting process
|
|
||||||
* and they will be set to the adjusted coefficients at the end.
|
|
||||||
* @param estimator estimator to use for the fitting
|
|
||||||
|
|
||||||
*/
|
|
||||||
public HarmonicFitter(EstimatedParameter[] coefficients,
|
|
||||||
Estimator estimator) {
|
|
||||||
super(coefficients, estimator);
|
|
||||||
firstGuessNeeded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double[] fit()
|
|
||||||
throws EstimationException {
|
|
||||||
if (firstGuessNeeded) {
|
|
||||||
if (measurements.size() < 4) {
|
|
||||||
throw new EstimationException("sample must contain at least {0} points",
|
|
||||||
new String[] {
|
|
||||||
Integer.toString(4)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sortMeasurements();
|
|
||||||
|
|
||||||
try {
|
|
||||||
HarmonicCoefficientsGuesser guesser =
|
|
||||||
new HarmonicCoefficientsGuesser((FitMeasurement[]) getMeasurements());
|
|
||||||
guesser.guess();
|
|
||||||
|
|
||||||
coefficients[0].setEstimate(guesser.getA());
|
|
||||||
coefficients[1].setEstimate(guesser.getOmega());
|
|
||||||
coefficients[2].setEstimate(guesser.getPhi());
|
|
||||||
} catch(ExhaustedSampleException e) {
|
|
||||||
throw new EstimationException(e);
|
|
||||||
} catch(FunctionException e) {
|
|
||||||
throw new EstimationException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
firstGuessNeeded = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.fit();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the current amplitude coefficient estimate.
|
|
||||||
* Get a, where <code>f (t) = a cos (omega t + phi)</code>
|
|
||||||
* @return current amplitude coefficient estimate
|
|
||||||
*/
|
|
||||||
public double getAmplitude() {
|
|
||||||
return coefficients[0].getEstimate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the current pulsation coefficient estimate.
|
|
||||||
* Get omega, where <code>f (t) = a cos (omega t + phi)</code>
|
|
||||||
* @return current pulsation coefficient estimate
|
|
||||||
*/
|
|
||||||
public double getPulsation() {
|
|
||||||
return coefficients[1].getEstimate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the current phase coefficient estimate.
|
|
||||||
* Get phi, where <code>f (t) = a cos (omega t + phi)</code>
|
|
||||||
* @return current phase coefficient estimate
|
|
||||||
*/
|
|
||||||
public double getPhase() {
|
|
||||||
return coefficients[2].getEstimate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the value of the function at x according to the current parameters value.
|
|
||||||
* @param x abscissa at which the theoretical value is requested
|
|
||||||
* @return theoretical value at x
|
|
||||||
*/
|
|
||||||
public double valueAt(double x) {
|
|
||||||
double a = coefficients[0].getEstimate();
|
|
||||||
double omega = coefficients[1].getEstimate();
|
|
||||||
double phi = coefficients[2].getEstimate();
|
|
||||||
return a * Math.cos(omega * x + phi);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the derivative of the function at x with respect to parameter p.
|
|
||||||
* @param x abscissa at which the partial derivative is requested
|
|
||||||
* @param p parameter with respect to which the derivative is requested
|
|
||||||
* @return partial derivative
|
|
||||||
*/
|
|
||||||
public double partial(double x, EstimatedParameter p) {
|
|
||||||
double a = coefficients[0].getEstimate();
|
|
||||||
double omega = coefficients[1].getEstimate();
|
|
||||||
double phi = coefficients[2].getEstimate();
|
|
||||||
if (p == coefficients[0]) {
|
|
||||||
return Math.cos(omega * x + phi);
|
|
||||||
} else if (p == coefficients[1]) {
|
|
||||||
return -a * x * Math.sin(omega * x + phi);
|
|
||||||
} else {
|
|
||||||
return -a * Math.sin(omega * x + phi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Indicator of the need to compute a first guess of the coefficients. */
|
|
||||||
private boolean firstGuessNeeded;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -8722683066277473450L;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.estimation.EstimatedParameter;
|
|
||||||
|
|
||||||
/** This class represents a polynomial coefficient.
|
|
||||||
|
|
||||||
* <p>Each coefficient is uniquely defined by its degree.</p>
|
|
||||||
|
|
||||||
* @see PolynomialFitter
|
|
||||||
|
|
||||||
* @version $Id$
|
|
||||||
* @author L. Maisonobe
|
|
||||||
|
|
||||||
*/
|
|
||||||
public class PolynomialCoefficient
|
|
||||||
extends EstimatedParameter {
|
|
||||||
|
|
||||||
public PolynomialCoefficient(int degree) {
|
|
||||||
super("a" + degree, 0.0);
|
|
||||||
this.degree = degree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final int degree;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 5775845068390259552L;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.estimation.EstimatedParameter;
|
|
||||||
import org.spaceroots.mantissa.estimation.Estimator;
|
|
||||||
import org.spaceroots.mantissa.estimation.GaussNewtonEstimator;
|
|
||||||
|
|
||||||
/** This class implements a curve fitting specialized for polynomials.
|
|
||||||
|
|
||||||
* <p>Polynomial fitting is a very simple case of curve fitting. The
|
|
||||||
* estimated coefficients are the polynom coefficients. They are
|
|
||||||
* searched by a least square estimator.</p>
|
|
||||||
|
|
||||||
* <p>This class <emph>is by no means optimized</emph>, neither in
|
|
||||||
* space nor in time performance.</p>
|
|
||||||
|
|
||||||
* @see PolynomialCoefficient
|
|
||||||
|
|
||||||
* @version $Id$
|
|
||||||
* @author L. Maisonobe
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class PolynomialFitter
|
|
||||||
extends AbstractCurveFitter {
|
|
||||||
|
|
||||||
/** Simple constructor.
|
|
||||||
|
|
||||||
* <p>The polynomial fitter built this way are complete polynoms,
|
|
||||||
* ie. a n-degree polynom has n+1 coefficients. In order to build
|
|
||||||
* fitter for sparse polynoms (for example <code>a x^20 - b
|
|
||||||
* x^30</code>, on should first build the coefficients array and
|
|
||||||
* provide it to {@link
|
|
||||||
* #PolynomialFitter(PolynomialCoefficient[], int, double, double,
|
|
||||||
* double)}.</p>
|
|
||||||
* @param degree maximal degree of the polynom
|
|
||||||
* @param estimator estimator to use for the fitting
|
|
||||||
*/
|
|
||||||
public PolynomialFitter(int degree, Estimator estimator) {
|
|
||||||
super(degree + 1, estimator);
|
|
||||||
for (int i = 0; i < coefficients.length; ++i) {
|
|
||||||
coefficients[i] = new PolynomialCoefficient(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Simple constructor.
|
|
||||||
|
|
||||||
* <p>This constructor can be used either when a first estimate of
|
|
||||||
* the coefficients is already known (which is of little interest
|
|
||||||
* because the fit cost is the same whether a first guess is known or
|
|
||||||
* not) or when one needs to handle sparse polynoms like <code>a
|
|
||||||
* x^20 - b x^30</code>.</p>
|
|
||||||
|
|
||||||
* @param coefficients first estimate of the coefficients.
|
|
||||||
* A reference to this array is hold by the newly created
|
|
||||||
* object. Its elements will be adjusted during the fitting process
|
|
||||||
* and they will be set to the adjusted coefficients at the end.
|
|
||||||
* @param estimator estimator to use for the fitting
|
|
||||||
*/
|
|
||||||
public PolynomialFitter(PolynomialCoefficient[] coefficients,
|
|
||||||
Estimator estimator) {
|
|
||||||
super(coefficients, estimator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the value of the function at x according to the current parameters value.
|
|
||||||
* @param x abscissa at which the theoretical value is requested
|
|
||||||
* @return theoretical value at x
|
|
||||||
*/
|
|
||||||
public double valueAt(double x) {
|
|
||||||
double y = coefficients[coefficients.length - 1].getEstimate();
|
|
||||||
for (int i = coefficients.length - 2; i >= 0; --i) {
|
|
||||||
y = y * x + coefficients[i].getEstimate();
|
|
||||||
}
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the derivative of the function at x with respect to parameter p.
|
|
||||||
* @param x abscissa at which the partial derivative is requested
|
|
||||||
* @param p parameter with respect to which the derivative is requested
|
|
||||||
* @return partial derivative
|
|
||||||
*/
|
|
||||||
public double partial(double x, EstimatedParameter p) {
|
|
||||||
if (p instanceof PolynomialCoefficient) {
|
|
||||||
return Math.pow(x, ((PolynomialCoefficient) p).degree);
|
|
||||||
}
|
|
||||||
throw new RuntimeException("internal error");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -744904084649890769L;
|
|
||||||
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,108 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
import junit.framework.*;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.estimation.EstimatedParameter;
|
|
||||||
import org.spaceroots.mantissa.estimation.LevenbergMarquardtEstimator;
|
|
||||||
import org.spaceroots.mantissa.estimation.WeightedMeasurement;
|
|
||||||
|
|
||||||
public class AbstractCurveFitterTest
|
|
||||||
extends TestCase {
|
|
||||||
|
|
||||||
public AbstractCurveFitterTest(String name) {
|
|
||||||
super(name);
|
|
||||||
fitter = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAlreadySorted() {
|
|
||||||
for (double x = 0.0; x < 100.0; x += 1.0) {
|
|
||||||
fitter.addWeightedPair(1.0, x, 0.0);
|
|
||||||
}
|
|
||||||
checkSorted();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReversed() {
|
|
||||||
for (double x = 0.0; x < 100.0; x += 1.0) {
|
|
||||||
fitter.addWeightedPair(1.0, 100.0 - x, 0.0);
|
|
||||||
}
|
|
||||||
checkSorted();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRandom() {
|
|
||||||
Random randomizer = new Random(86757343594l);
|
|
||||||
for (int i = 0; i < 100; ++i) {
|
|
||||||
fitter.addWeightedPair(1.0, 10.0 * randomizer.nextDouble(), 0.0);
|
|
||||||
}
|
|
||||||
checkSorted();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkSorted() {
|
|
||||||
fitter.doSort();
|
|
||||||
|
|
||||||
WeightedMeasurement[] measurements = fitter.getMeasurements();
|
|
||||||
for (int i = 1; i < measurements.length; ++i) {
|
|
||||||
AbstractCurveFitter.FitMeasurement m1
|
|
||||||
= (AbstractCurveFitter.FitMeasurement) measurements[i-1];
|
|
||||||
AbstractCurveFitter.FitMeasurement m2
|
|
||||||
= (AbstractCurveFitter.FitMeasurement) measurements[i];
|
|
||||||
assertTrue(m1.x <= m2.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Test suite() {
|
|
||||||
return new TestSuite(AbstractCurveFitterTest.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUp() {
|
|
||||||
fitter = new DummyFitter();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tearDown() {
|
|
||||||
fitter = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DummyFitter
|
|
||||||
extends AbstractCurveFitter {
|
|
||||||
|
|
||||||
public DummyFitter() {
|
|
||||||
super(10, new LevenbergMarquardtEstimator());
|
|
||||||
}
|
|
||||||
|
|
||||||
public double valueAt(double x) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double partial(double x, EstimatedParameter p) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doSort() {
|
|
||||||
sortMeasurements();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 4016396219767783678L;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private DummyFitter fitter;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import junit.framework.Test;
|
|
||||||
import junit.framework.TestSuite;
|
|
||||||
|
|
||||||
public class AllTests {
|
|
||||||
public static Test suite() {
|
|
||||||
|
|
||||||
TestSuite suite= new TestSuite("org.spaceroots.mantissa.fitting");
|
|
||||||
|
|
||||||
suite.addTest(AbstractCurveFitterTest.suite());
|
|
||||||
suite.addTest(PolynomialFitterTest.suite());
|
|
||||||
suite.addTest(HarmonicFitterTest.suite());
|
|
||||||
|
|
||||||
return suite;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
import junit.framework.*;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.estimation.EstimationException;
|
|
||||||
import org.spaceroots.mantissa.estimation.LevenbergMarquardtEstimator;
|
|
||||||
import org.spaceroots.mantissa.estimation.WeightedMeasurement;
|
|
||||||
|
|
||||||
public class HarmonicFitterTest
|
|
||||||
extends TestCase {
|
|
||||||
|
|
||||||
public HarmonicFitterTest(String name) {
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNoError()
|
|
||||||
throws EstimationException {
|
|
||||||
HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
|
|
||||||
|
|
||||||
HarmonicFitter fitter =
|
|
||||||
new HarmonicFitter(new LevenbergMarquardtEstimator());
|
|
||||||
for (double x = 0.0; x < 1.3; x += 0.01) {
|
|
||||||
fitter.addWeightedPair(1.0, x, f.valueAt(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] coeffs = fitter.fit();
|
|
||||||
|
|
||||||
HarmonicFunction fitted = new HarmonicFunction(coeffs[0],
|
|
||||||
coeffs[1],
|
|
||||||
coeffs[2]);
|
|
||||||
assertTrue(Math.abs(coeffs[0] - f.getA()) < 1.0e-13);
|
|
||||||
assertTrue(Math.abs(coeffs[1] - f.getOmega()) < 1.0e-13);
|
|
||||||
assertTrue(Math.abs(coeffs[2] - center(f.getPhi(), coeffs[2])) < 1.0e-13);
|
|
||||||
|
|
||||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
|
||||||
assertTrue(Math.abs(f.valueAt(x) - fitted.valueAt(x)) < 1.0e-13);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test1PercentError()
|
|
||||||
throws EstimationException {
|
|
||||||
Random randomizer = new Random(64925784252l);
|
|
||||||
HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
|
|
||||||
|
|
||||||
HarmonicFitter fitter =
|
|
||||||
new HarmonicFitter(new LevenbergMarquardtEstimator());
|
|
||||||
for (double x = 0.0; x < 10.0; x += 0.1) {
|
|
||||||
fitter.addWeightedPair(1.0, x,
|
|
||||||
f.valueAt(x) + 0.01 * randomizer.nextGaussian());
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] coeffs = fitter.fit();
|
|
||||||
|
|
||||||
new HarmonicFunction(coeffs[0], coeffs[1], coeffs[2]);
|
|
||||||
assertTrue(Math.abs(coeffs[0] - f.getA()) < 7.6e-4);
|
|
||||||
assertTrue(Math.abs(coeffs[1] - f.getOmega()) < 2.7e-3);
|
|
||||||
assertTrue(Math.abs(coeffs[2] - center(f.getPhi(), coeffs[2])) < 1.3e-2);
|
|
||||||
|
|
||||||
WeightedMeasurement[] measurements = fitter.getMeasurements();
|
|
||||||
for (int i = 0; i < measurements.length; ++i) {
|
|
||||||
WeightedMeasurement m = measurements[i];
|
|
||||||
assertTrue(Math.abs(measurements[i].getMeasuredValue()
|
|
||||||
- m.getTheoreticalValue()) < 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testUnsorted()
|
|
||||||
throws EstimationException {
|
|
||||||
Random randomizer = new Random(64925784252l);
|
|
||||||
HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
|
|
||||||
|
|
||||||
HarmonicFitter fitter =
|
|
||||||
new HarmonicFitter(new LevenbergMarquardtEstimator());
|
|
||||||
|
|
||||||
// build a regularly spaced array of measurements
|
|
||||||
int size = 100;
|
|
||||||
double[] xTab = new double[size];
|
|
||||||
double[] yTab = new double[size];
|
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
xTab[i] = 0.1 * i;
|
|
||||||
yTab[i] = f.valueAt (xTab[i]) + 0.01 * randomizer.nextGaussian();
|
|
||||||
}
|
|
||||||
|
|
||||||
// shake it
|
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
int i1 = randomizer.nextInt(size);
|
|
||||||
int i2 = randomizer.nextInt(size);
|
|
||||||
double xTmp = xTab[i1];
|
|
||||||
double yTmp = yTab[i1];
|
|
||||||
xTab[i1] = xTab[i2];
|
|
||||||
yTab[i1] = yTab[i2];
|
|
||||||
xTab[i2] = xTmp;
|
|
||||||
yTab[i2] = yTmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass it to the fitter
|
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
fitter.addWeightedPair(1.0, xTab[i], yTab[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] coeffs = fitter.fit();
|
|
||||||
|
|
||||||
new HarmonicFunction(coeffs[0], coeffs[1], coeffs[2]);
|
|
||||||
assertTrue(Math.abs(coeffs[0] - f.getA()) < 7.6e-4);
|
|
||||||
assertTrue(Math.abs(coeffs[1] - f.getOmega()) < 3.5e-3);
|
|
||||||
assertTrue(Math.abs(coeffs[2] - center(f.getPhi(), coeffs[2])) < 1.5e-2);
|
|
||||||
|
|
||||||
WeightedMeasurement[] measurements = fitter.getMeasurements();
|
|
||||||
for (int i = 0; i < measurements.length; ++i) {
|
|
||||||
WeightedMeasurement m = measurements[i];
|
|
||||||
assertTrue(Math.abs(m.getMeasuredValue() - m.getTheoreticalValue())
|
|
||||||
< 0.04);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Test suite() {
|
|
||||||
return new TestSuite(HarmonicFitterTest.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Center an angle with respect to another one. */
|
|
||||||
private static double center(double a, double ref) {
|
|
||||||
double twoPi = Math.PI + Math.PI;
|
|
||||||
return a - twoPi * Math.floor((a + Math.PI - ref) / twoPi);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class HarmonicFunction {
|
|
||||||
public HarmonicFunction(double a, double omega, double phi) {
|
|
||||||
this.a = a;
|
|
||||||
this.omega = omega;
|
|
||||||
this.phi = phi;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double valueAt(double x) {
|
|
||||||
return a * Math.cos(omega * x + phi);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getA() {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getOmega() {
|
|
||||||
return omega;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getPhi() {
|
|
||||||
return phi;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double a;
|
|
||||||
private double omega;
|
|
||||||
private double phi;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
// or more contributor license agreements. See the NOTICE file
|
|
||||||
// distributed with this work for additional information
|
|
||||||
// regarding copyright ownership. The ASF licenses this file
|
|
||||||
// to you under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance
|
|
||||||
// with the License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing,
|
|
||||||
// software distributed under the License is distributed on an
|
|
||||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
// KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package org.spaceroots.mantissa.fitting;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
import junit.framework.*;
|
|
||||||
|
|
||||||
import org.spaceroots.mantissa.estimation.EstimationException;
|
|
||||||
import org.spaceroots.mantissa.estimation.Estimator;
|
|
||||||
import org.spaceroots.mantissa.estimation.GaussNewtonEstimator;
|
|
||||||
import org.spaceroots.mantissa.estimation.LevenbergMarquardtEstimator;
|
|
||||||
|
|
||||||
public class PolynomialFitterTest
|
|
||||||
extends TestCase {
|
|
||||||
|
|
||||||
public PolynomialFitterTest(String name) {
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNoError()
|
|
||||||
throws EstimationException {
|
|
||||||
Random randomizer = new Random(64925784252l);
|
|
||||||
for (int degree = 0; degree < 10; ++degree) {
|
|
||||||
Polynom p = new Polynom(degree);
|
|
||||||
for (int i = 0; i <= degree; ++i) {
|
|
||||||
p.initCoeff (i, randomizer.nextGaussian());
|
|
||||||
}
|
|
||||||
|
|
||||||
PolynomialFitter fitter =
|
|
||||||
new PolynomialFitter(degree, new LevenbergMarquardtEstimator());
|
|
||||||
for (int i = 0; i <= degree; ++i) {
|
|
||||||
fitter.addWeightedPair(1.0, i, p.valueAt(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
Polynom fitted = new Polynom(fitter.fit());
|
|
||||||
|
|
||||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
|
||||||
double error = Math.abs(p.valueAt(x) - fitted.valueAt(x))
|
|
||||||
/ (1.0 + Math.abs(p.valueAt(x)));
|
|
||||||
assertTrue(Math.abs(error) < 1.0e-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSmallError()
|
|
||||||
throws EstimationException {
|
|
||||||
Random randomizer = new Random(53882150042l);
|
|
||||||
for (int degree = 0; degree < 10; ++degree) {
|
|
||||||
Polynom p = new Polynom(degree);
|
|
||||||
for (int i = 0; i <= degree; ++i) {
|
|
||||||
p.initCoeff(i, randomizer.nextGaussian());
|
|
||||||
}
|
|
||||||
|
|
||||||
PolynomialFitter fitter =
|
|
||||||
new PolynomialFitter(degree, new LevenbergMarquardtEstimator());
|
|
||||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
|
||||||
fitter.addWeightedPair(1.0, x,
|
|
||||||
p.valueAt(x) + 0.1 * randomizer.nextGaussian());
|
|
||||||
}
|
|
||||||
|
|
||||||
Polynom fitted = new Polynom(fitter.fit());
|
|
||||||
|
|
||||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
|
||||||
double error = Math.abs(p.valueAt(x) - fitted.valueAt(x))
|
|
||||||
/ (1.0 + Math.abs(p.valueAt(x)));
|
|
||||||
assertTrue(Math.abs(error) < 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRedundantSolvable() {
|
|
||||||
// Levenberg-Marquardt should handle redundant information gracefully
|
|
||||||
checkUnsolvableProblem(new LevenbergMarquardtEstimator(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRedundantUnsolvable() {
|
|
||||||
// Gauss-Newton should not be able to solve redundant information
|
|
||||||
checkUnsolvableProblem(new GaussNewtonEstimator(10, 1.0e-7, 1.0e-7,
|
|
||||||
1.0e-10),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkUnsolvableProblem(Estimator estimator,
|
|
||||||
boolean solvable) {
|
|
||||||
Random randomizer = new Random(1248788532l);
|
|
||||||
for (int degree = 0; degree < 10; ++degree) {
|
|
||||||
Polynom p = new Polynom(degree);
|
|
||||||
for (int i = 0; i <= degree; ++i) {
|
|
||||||
p.initCoeff(i, randomizer.nextGaussian());
|
|
||||||
}
|
|
||||||
|
|
||||||
PolynomialFitter fitter = new PolynomialFitter(degree, estimator);
|
|
||||||
|
|
||||||
// reusing the same point over and over again does not bring
|
|
||||||
// information, the problem cannot be solved in this case for
|
|
||||||
// degrees greater than 1 (but one point is sufficient for
|
|
||||||
// degree 0)
|
|
||||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
|
||||||
fitter.addWeightedPair(1.0, 0.0, p.valueAt(0.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
fitter.fit();
|
|
||||||
assertTrue(solvable || (degree == 0));
|
|
||||||
} catch(EstimationException e) {
|
|
||||||
assertTrue((! solvable) && (degree > 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Test suite() {
|
|
||||||
return new TestSuite(PolynomialFitterTest.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Polynom {
|
|
||||||
|
|
||||||
public Polynom(int degree) {
|
|
||||||
coeffs = new double[degree + 1];
|
|
||||||
for (int i = 0; i < coeffs.length; ++i) {
|
|
||||||
coeffs[i] = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Polynom(double[]coeffs) {
|
|
||||||
this.coeffs = coeffs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initCoeff(int i, double c) {
|
|
||||||
coeffs[i] = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double valueAt(double x) {
|
|
||||||
double y = coeffs[coeffs.length - 1];
|
|
||||||
for (int i = coeffs.length - 2; i >= 0; --i) {
|
|
||||||
y = y * x + coeffs[i];
|
|
||||||
}
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double[] coeffs;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -39,6 +39,9 @@ The <action> type attribute can be add,update,fix,remove.
|
||||||
</properties>
|
</properties>
|
||||||
<body>
|
<body>
|
||||||
<release version="2.0" date="TBD" description="TBD">
|
<release version="2.0" date="TBD" description="TBD">
|
||||||
|
<action dev="luc" type="add" >
|
||||||
|
Added curve fitting with a general case and two specific cases (polynomial and harmonic).
|
||||||
|
</action>
|
||||||
<action dev="psteitz" type="update" issue="MATH-276" due-to="Mark Anderson">
|
<action dev="psteitz" type="update" issue="MATH-276" due-to="Mark Anderson">
|
||||||
Optimized Complex isNaN(), isInfinite() by moving computation to constructor.
|
Optimized Complex isNaN(), isInfinite() by moving computation to constructor.
|
||||||
</action>
|
</action>
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
<li><a href="analysis.html#a12.3_Linear_Programming">12.3 Linear Programming</a></li>
|
<li><a href="analysis.html#a12.3_Linear_Programming">12.3 Linear Programming</a></li>
|
||||||
<li><a href="optimization.html#a12.4_Direct_Methods">12.4 Direct Methods</a></li>
|
<li><a href="optimization.html#a12.4_Direct_Methods">12.4 Direct Methods</a></li>
|
||||||
<li><a href="analysis.html#a12.5_General_Case">12.5 General Case</a></li>
|
<li><a href="analysis.html#a12.5_General_Case">12.5 General Case</a></li>
|
||||||
|
<li><a href="analysis.html#a12.6_Curve_Fitting">12.6 Curve Fitting</a></li>
|
||||||
</ul></li>
|
</ul></li>
|
||||||
<li><a href="ode.html">13. Ordinary Differential Equations Integration</a>
|
<li><a href="ode.html">13. Ordinary Differential Equations Integration</a>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
using direct search methods (i.e. not using derivatives),</li>
|
using direct search methods (i.e. not using derivatives),</li>
|
||||||
<li>the general package handles multivariate scalar or vector functions
|
<li>the general package handles multivariate scalar or vector functions
|
||||||
using derivatives.</li>
|
using derivatives.</li>
|
||||||
|
<li>the fitting package handles curve fitting by univariate real functions</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -212,6 +213,55 @@
|
||||||
solver).
|
solver).
|
||||||
</p>
|
</p>
|
||||||
</subsection>
|
</subsection>
|
||||||
|
<subsection name="12.6 Curve Fitting" href="fitting">
|
||||||
|
<p>
|
||||||
|
The fitting package deals with curve fitting for univariate real functions.
|
||||||
|
When a univariate real function y = f(x) does depend on some unknown parameters
|
||||||
|
p<sub>0</sub>, p<sub>1</sub> ... p<sub>n-1</sub>, curve fitting can be used to
|
||||||
|
find these parameters. It does this by <em>fitting</em> the curve so it remains
|
||||||
|
very close to a set of observed points (x<sub>0</sub>, y<sub>0</sub>),
|
||||||
|
(x<sub>1</sub>, y<sub>1</sub>) ... (x<sub>k-1</sub>, y<sub>k-1</sub>). This
|
||||||
|
fitting is done by finding the parameters values that minimizes the objective
|
||||||
|
function sum(y<sub>i</sub>-f(x<sub>i</sub>))<sup>2</sup>. This is really a least
|
||||||
|
squares problem.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
For all provided curve fitters, the operating principle is the same. Users must first
|
||||||
|
create an instance of the fitter, then add the observed points and once the complete
|
||||||
|
sample of observed points has been added they must call the <code>fit</code> method
|
||||||
|
which will compute the parameters that best fit the sample. A weight is associated
|
||||||
|
with each observed point, this allows to take into account uncertainty on some points
|
||||||
|
when they come from loosy measurements for example. If no such information exist and
|
||||||
|
all points should be treated the same, it is safe to put 1.0 as the weight for all points.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The <a
|
||||||
|
href="../apidocs/org/apache/commons/math/optimization/fitting/CurveFitter.html">
|
||||||
|
CurveFitter</a> class provides curve fitting for general curves. Users must
|
||||||
|
provide their own implementation of the curve template as a class implementing
|
||||||
|
the <a
|
||||||
|
href="../apidocs/org/apache/commons/math/optimization/fitting/ParametricRealFunction.html">
|
||||||
|
ParametricRealFunction</a> interface and they must provide the initial guess of the
|
||||||
|
parameters. The more specialized <a
|
||||||
|
href="../apidocs/org/apache/commons/math/optimization/fitting/PolynomialFitter.html">
|
||||||
|
PolynomialFitter</a> and <a
|
||||||
|
href="../apidocs/org/apache/commons/math/optimization/fitting/HarmonicFitter.html">
|
||||||
|
HarmonicFitter</a> classes require neither an implementation of the parametric real function
|
||||||
|
not an initial guess as they are able to compute them by themselves.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
An example of fitting a polynomial is given here:
|
||||||
|
</p>
|
||||||
|
<source>PolynomialFitter fitter = new PolynomialFitter(degree, new LevenbergMarquardtOptimizer());
|
||||||
|
fitter.addObservedPoint(-1.00, 2.021170021833143);
|
||||||
|
fitter.addObservedPoint(-0.99 2.221135431136975);
|
||||||
|
fitter.addObservedPoint(-0.98 2.09985277659314);
|
||||||
|
fitter.addObservedPoint(-0.97 2.0211192647627025);
|
||||||
|
// lots of lines ommitted
|
||||||
|
fitter.addObservedPoint( 0.99, -2.4345814727089854);
|
||||||
|
PolynomialFunction fitted = fitter.fit();
|
||||||
|
</source>
|
||||||
|
</subsection>
|
||||||
</section>
|
</section>
|
||||||
</body>
|
</body>
|
||||||
</document>
|
</document>
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.apache.commons.math.optimization.OptimizationException;
|
||||||
|
import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
|
||||||
|
import org.apache.commons.math.util.MathUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class HarmonicFitterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoError() throws OptimizationException {
|
||||||
|
HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
|
||||||
|
|
||||||
|
HarmonicFitter fitter =
|
||||||
|
new HarmonicFitter(new LevenbergMarquardtOptimizer());
|
||||||
|
for (double x = 0.0; x < 1.3; x += 0.01) {
|
||||||
|
fitter.addObservedPoint(1.0, x, f.value(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
HarmonicFunction fitted = fitter.fit();
|
||||||
|
assertEquals(f.getAmplitude(), fitted.getAmplitude(), 1.0e-13);
|
||||||
|
assertEquals(f.getPulsation(), fitted.getPulsation(), 1.0e-13);
|
||||||
|
assertEquals(f.getPhase(), MathUtils.normalizeAngle(fitted.getPhase(), f.getPhase()), 1.0e-13);
|
||||||
|
|
||||||
|
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||||
|
assertTrue(Math.abs(f.value(x) - fitted.value(x)) < 1.0e-13);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1PercentError() throws OptimizationException {
|
||||||
|
Random randomizer = new Random(64925784252l);
|
||||||
|
HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
|
||||||
|
|
||||||
|
HarmonicFitter fitter =
|
||||||
|
new HarmonicFitter(new LevenbergMarquardtOptimizer());
|
||||||
|
for (double x = 0.0; x < 10.0; x += 0.1) {
|
||||||
|
fitter.addObservedPoint(1.0, x,
|
||||||
|
f.value(x) + 0.01 * randomizer.nextGaussian());
|
||||||
|
}
|
||||||
|
|
||||||
|
HarmonicFunction fitted = fitter.fit();
|
||||||
|
assertEquals(f.getAmplitude(), fitted.getAmplitude(), 7.6e-4);
|
||||||
|
assertEquals(f.getPulsation(), fitted.getPulsation(), 2.7e-3);
|
||||||
|
assertEquals(f.getPhase(), MathUtils.normalizeAngle(fitted.getPhase(), f.getPhase()), 1.3e-2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitialGuess() throws OptimizationException {
|
||||||
|
Random randomizer = new Random(45314242l);
|
||||||
|
HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
|
||||||
|
|
||||||
|
HarmonicFitter fitter =
|
||||||
|
new HarmonicFitter(new LevenbergMarquardtOptimizer(), new double[] { 0.15, 3.6, 4.5 });
|
||||||
|
for (double x = 0.0; x < 10.0; x += 0.1) {
|
||||||
|
fitter.addObservedPoint(1.0, x,
|
||||||
|
f.value(x) + 0.01 * randomizer.nextGaussian());
|
||||||
|
}
|
||||||
|
|
||||||
|
HarmonicFunction fitted = fitter.fit();
|
||||||
|
assertEquals(f.getAmplitude(), fitted.getAmplitude(), 1.2e-3);
|
||||||
|
assertEquals(f.getPulsation(), fitted.getPulsation(), 3.3e-3);
|
||||||
|
assertEquals(f.getPhase(), MathUtils.normalizeAngle(fitted.getPhase(), f.getPhase()), 1.7e-2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnsorted() throws OptimizationException {
|
||||||
|
Random randomizer = new Random(64925784252l);
|
||||||
|
HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
|
||||||
|
|
||||||
|
HarmonicFitter fitter =
|
||||||
|
new HarmonicFitter(new LevenbergMarquardtOptimizer());
|
||||||
|
|
||||||
|
// build a regularly spaced array of measurements
|
||||||
|
int size = 100;
|
||||||
|
double[] xTab = new double[size];
|
||||||
|
double[] yTab = new double[size];
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
xTab[i] = 0.1 * i;
|
||||||
|
yTab[i] = f.value(xTab[i]) + 0.01 * randomizer.nextGaussian();
|
||||||
|
}
|
||||||
|
|
||||||
|
// shake it
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
int i1 = randomizer.nextInt(size);
|
||||||
|
int i2 = randomizer.nextInt(size);
|
||||||
|
double xTmp = xTab[i1];
|
||||||
|
double yTmp = yTab[i1];
|
||||||
|
xTab[i1] = xTab[i2];
|
||||||
|
yTab[i1] = yTab[i2];
|
||||||
|
xTab[i2] = xTmp;
|
||||||
|
yTab[i2] = yTmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass it to the fitter
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
fitter.addObservedPoint(1.0, xTab[i], yTab[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
HarmonicFunction fitted = fitter.fit();
|
||||||
|
assertEquals(f.getAmplitude(), fitted.getAmplitude(), 7.6e-4);
|
||||||
|
assertEquals(f.getPulsation(), fitted.getPulsation(), 3.5e-3);
|
||||||
|
assertEquals(f.getPhase(), MathUtils.normalizeAngle(fitted.getPhase(), f.getPhase()), 1.5e-2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.commons.math.optimization.fitting;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
|
||||||
|
import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
|
||||||
|
import org.apache.commons.math.optimization.OptimizationException;
|
||||||
|
import org.apache.commons.math.optimization.general.GaussNewtonOptimizer;
|
||||||
|
import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class PolynomialFitterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoError() throws OptimizationException {
|
||||||
|
Random randomizer = new Random(64925784252l);
|
||||||
|
for (int degree = 1; degree < 10; ++degree) {
|
||||||
|
PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
|
||||||
|
|
||||||
|
PolynomialFitter fitter =
|
||||||
|
new PolynomialFitter(degree, new LevenbergMarquardtOptimizer());
|
||||||
|
for (int i = 0; i <= degree; ++i) {
|
||||||
|
fitter.addObservedPoint(1.0, i, p.value(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
PolynomialFunction fitted = fitter.fit();
|
||||||
|
|
||||||
|
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||||
|
double error = Math.abs(p.value(x) - fitted.value(x)) /
|
||||||
|
(1.0 + Math.abs(p.value(x)));
|
||||||
|
assertEquals(0.0, error, 1.0e-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallError() throws OptimizationException {
|
||||||
|
Random randomizer = new Random(53882150042l);
|
||||||
|
double maxError = 0;
|
||||||
|
for (int degree = 0; degree < 10; ++degree) {
|
||||||
|
PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
|
||||||
|
|
||||||
|
PolynomialFitter fitter =
|
||||||
|
new PolynomialFitter(degree, new LevenbergMarquardtOptimizer());
|
||||||
|
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||||
|
fitter.addObservedPoint(1.0, x,
|
||||||
|
p.value(x) + 0.1 * randomizer.nextGaussian());
|
||||||
|
}
|
||||||
|
|
||||||
|
PolynomialFunction fitted = fitter.fit();
|
||||||
|
|
||||||
|
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||||
|
double error = Math.abs(p.value(x) - fitted.value(x)) /
|
||||||
|
(1.0 + Math.abs(p.value(x)));
|
||||||
|
maxError = Math.max(maxError, error);
|
||||||
|
assertTrue(Math.abs(error) < 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(maxError > 0.01);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRedundantSolvable() {
|
||||||
|
// Levenberg-Marquardt should handle redundant information gracefully
|
||||||
|
checkUnsolvableProblem(new LevenbergMarquardtOptimizer(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRedundantUnsolvable() {
|
||||||
|
// Gauss-Newton should not be able to solve redundant information
|
||||||
|
DifferentiableMultivariateVectorialOptimizer optimizer =
|
||||||
|
new GaussNewtonOptimizer(true);
|
||||||
|
checkUnsolvableProblem(optimizer, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkUnsolvableProblem(DifferentiableMultivariateVectorialOptimizer optimizer,
|
||||||
|
boolean solvable) {
|
||||||
|
Random randomizer = new Random(1248788532l);
|
||||||
|
for (int degree = 0; degree < 10; ++degree) {
|
||||||
|
PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
|
||||||
|
|
||||||
|
PolynomialFitter fitter = new PolynomialFitter(degree, optimizer);
|
||||||
|
|
||||||
|
// reusing the same point over and over again does not bring
|
||||||
|
// information, the problem cannot be solved in this case for
|
||||||
|
// degrees greater than 1 (but one point is sufficient for
|
||||||
|
// degree 0)
|
||||||
|
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||||
|
fitter.addObservedPoint(1.0, 0.0, p.value(0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fitter.fit();
|
||||||
|
assertTrue(solvable || (degree == 0));
|
||||||
|
} catch(OptimizationException e) {
|
||||||
|
assertTrue((! solvable) && (degree > 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private PolynomialFunction buildRandomPolynomial(int degree, Random randomizer) {
|
||||||
|
final double[] coefficients = new double[degree + 1];
|
||||||
|
for (int i = 0; i <= degree; ++i) {
|
||||||
|
coefficients[i] = randomizer.nextGaussian();
|
||||||
|
}
|
||||||
|
return new PolynomialFunction(coefficients);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue