blob: 99def805fff43bfbf8bde1a4a025155ff00f4e0b [file] [log] [blame]
/*
* 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.math4.legacy.analysis.differentiation;
import org.apache.commons.math4.legacy.TestUtils;
import org.apache.commons.math4.legacy.analysis.QuinticFunction;
import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
import org.apache.commons.math4.legacy.analysis.UnivariateMatrixFunction;
import org.apache.commons.math4.legacy.analysis.UnivariateVectorFunction;
import org.apache.commons.math4.legacy.analysis.function.Gaussian;
import org.apache.commons.math4.legacy.analysis.function.Sin;
import org.apache.commons.math4.legacy.exception.MathInternalError;
import org.apache.commons.math4.legacy.exception.NotPositiveException;
import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
import org.apache.commons.math4.legacy.core.jdkmath.AccurateMath;
import org.junit.Assert;
import org.junit.Test;
/**
* Test for class {@link FiniteDifferencesDifferentiator}.
*/
public class FiniteDifferencesDifferentiatorTest {
@Test(expected=NumberIsTooSmallException.class)
public void testWrongNumberOfPoints() {
new FiniteDifferencesDifferentiator(1, 1.0);
}
@Test(expected=NotPositiveException.class)
public void testWrongStepSize() {
new FiniteDifferencesDifferentiator(3, 0.0);
}
@Test
public void testSerialization() {
FiniteDifferencesDifferentiator differentiator =
new FiniteDifferencesDifferentiator(3, 1.0e-3);
FiniteDifferencesDifferentiator recovered =
(FiniteDifferencesDifferentiator) TestUtils.serializeAndRecover(differentiator);
Assert.assertEquals(differentiator.getNbPoints(), recovered.getNbPoints());
Assert.assertEquals(differentiator.getStepSize(), recovered.getStepSize(), 1.0e-15);
}
@Test
public void testConstant() {
FiniteDifferencesDifferentiator differentiator =
new FiniteDifferencesDifferentiator(5, 0.01);
UnivariateDifferentiableFunction f =
differentiator.differentiate(new UnivariateFunction() {
@Override
public double value(double x) {
return 42.0;
}
});
for (double x = -10; x < 10; x += 0.1) {
DerivativeStructure y = f.value(new DerivativeStructure(1, 2, 0, x));
Assert.assertEquals(42.0, y.getValue(), 1.0e-15);
Assert.assertEquals( 0.0, y.getPartialDerivative(1), 1.0e-15);
Assert.assertEquals( 0.0, y.getPartialDerivative(2), 1.0e-15);
}
}
@Test
public void testLinear() {
FiniteDifferencesDifferentiator differentiator =
new FiniteDifferencesDifferentiator(5, 0.01);
UnivariateDifferentiableFunction f =
differentiator.differentiate(new UnivariateFunction() {
@Override
public double value(double x) {
return 2 - 3 * x;
}
});
for (double x = -10; x < 10; x += 0.1) {
DerivativeStructure y = f.value(new DerivativeStructure(1, 2, 0, x));
Assert.assertEquals("" + (2 - 3 * x - y.getValue()), 2 - 3 * x, y.getValue(), 2.0e-15);
Assert.assertEquals(-3.0, y.getPartialDerivative(1), 4.0e-13);
Assert.assertEquals( 0.0, y.getPartialDerivative(2), 9.0e-11);
}
}
@Test
public void testGaussian() {
FiniteDifferencesDifferentiator differentiator =
new FiniteDifferencesDifferentiator(9, 0.02);
UnivariateDifferentiableFunction gaussian = new Gaussian(1.0, 2.0);
UnivariateDifferentiableFunction f =
differentiator.differentiate(gaussian);
double[] expectedError = new double[] {
6.939e-18, 1.284e-15, 2.477e-13, 1.168e-11, 2.840e-9, 7.971e-8
};
double[] maxError = new double[expectedError.length];
for (double x = -10; x < 10; x += 0.1) {
DerivativeStructure dsX = new DerivativeStructure(1, maxError.length - 1, 0, x);
DerivativeStructure yRef = gaussian.value(dsX);
DerivativeStructure y = f.value(dsX);
Assert.assertEquals(f.value(dsX.getValue()), f.value(dsX).getValue(), 1.0e-15);
for (int order = 0; order <= yRef.getOrder(); ++order) {
maxError[order] = AccurateMath.max(maxError[order],
AccurateMath.abs(yRef.getPartialDerivative(order) -
y.getPartialDerivative(order)));
}
}
for (int i = 0; i < maxError.length; ++i) {
Assert.assertEquals(expectedError[i], maxError[i], 0.01 * expectedError[i]);
}
}
@Test
public void testStepSizeUnstability() {
UnivariateDifferentiableFunction quintic = new QuinticFunction();
UnivariateDifferentiableFunction goodStep =
new FiniteDifferencesDifferentiator(7, 0.25).differentiate(quintic);
UnivariateDifferentiableFunction badStep =
new FiniteDifferencesDifferentiator(7, 1.0e-6).differentiate(quintic);
double[] maxErrorGood = new double[7];
double[] maxErrorBad = new double[7];
for (double x = -10; x < 10; x += 0.1) {
DerivativeStructure dsX = new DerivativeStructure(1, 6, 0, x);
DerivativeStructure yRef = quintic.value(dsX);
DerivativeStructure yGood = goodStep.value(dsX);
DerivativeStructure yBad = badStep.value(dsX);
for (int order = 0; order <= 6; ++order) {
maxErrorGood[order] = AccurateMath.max(maxErrorGood[order],
AccurateMath.abs(yRef.getPartialDerivative(order) -
yGood.getPartialDerivative(order)));
maxErrorBad[order] = AccurateMath.max(maxErrorBad[order],
AccurateMath.abs(yRef.getPartialDerivative(order) -
yBad.getPartialDerivative(order)));
}
}
// the 0.25 step size is good for finite differences in the quintic on this abscissa range for 7 points
// the errors are fair
final double[] expectedGood = new double[] {
7.276e-12, 7.276e-11, 9.968e-10, 3.092e-9, 5.432e-8, 8.196e-8, 1.818e-6
};
// the 1.0e-6 step size is far too small for finite differences in the quintic on this abscissa range for 7 points
// the errors are huge!
final double[] expectedBad = new double[] {
2.910e-11, 2.087e-5, 147.7, 3.820e7, 6.354e14, 6.548e19, 1.543e27
};
for (int i = 0; i < maxErrorGood.length; ++i) {
Assert.assertEquals(expectedGood[i], maxErrorGood[i], 0.01 * expectedGood[i]);
Assert.assertEquals(expectedBad[i], maxErrorBad[i], 0.01 * expectedBad[i]);
}
}
@Test(expected=NumberIsTooLargeException.class)
public void testWrongOrder() {
UnivariateDifferentiableFunction f =
new FiniteDifferencesDifferentiator(3, 0.01).differentiate(new UnivariateFunction() {
@Override
public double value(double x) {
// this exception should not be thrown because wrong order
// should be detected before function call
throw new MathInternalError();
}
});
f.value(new DerivativeStructure(1, 3, 0, 1.0));
}
@Test(expected=NumberIsTooLargeException.class)
public void testWrongOrderVector() {
UnivariateDifferentiableVectorFunction f =
new FiniteDifferencesDifferentiator(3, 0.01).differentiate(new UnivariateVectorFunction() {
@Override
public double[] value(double x) {
// this exception should not be thrown because wrong order
// should be detected before function call
throw new MathInternalError();
}
});
f.value(new DerivativeStructure(1, 3, 0, 1.0));
}
@Test(expected=NumberIsTooLargeException.class)
public void testWrongOrderMatrix() {
UnivariateDifferentiableMatrixFunction f =
new FiniteDifferencesDifferentiator(3, 0.01).differentiate(new UnivariateMatrixFunction() {
@Override
public double[][] value(double x) {
// this exception should not be thrown because wrong order
// should be detected before function call
throw new MathInternalError();
}
});
f.value(new DerivativeStructure(1, 3, 0, 1.0));
}
@Test(expected=NumberIsTooLargeException.class)
public void testTooLargeStep() {
new FiniteDifferencesDifferentiator(3, 2.5, 0.0, 1.0);
}
@Test
public void testBounds() {
final double slope = 2.5;
UnivariateFunction f = new UnivariateFunction() {
@Override
public double value(double x) {
if (x < 0) {
throw new NumberIsTooSmallException(x, 0, true);
} else if (x > 1) {
throw new NumberIsTooLargeException(x, 1, true);
} else {
return slope * x;
}
}
};
UnivariateDifferentiableFunction missingBounds =
new FiniteDifferencesDifferentiator(3, 0.1).differentiate(f);
UnivariateDifferentiableFunction properlyBounded =
new FiniteDifferencesDifferentiator(3, 0.1, 0.0, 1.0).differentiate(f);
DerivativeStructure tLow = new DerivativeStructure(1, 1, 0, 0.05);
DerivativeStructure tHigh = new DerivativeStructure(1, 1, 0, 0.95);
try {
// here, we did not set the bounds, so the differences are evaluated out of domain
// using f(-0.05), f(0.05), f(0.15)
missingBounds.value(tLow);
Assert.fail("an exception should have been thrown");
} catch (NumberIsTooSmallException nse) {
Assert.assertEquals(-0.05, nse.getArgument().doubleValue(), 1.0e-10);
} catch (Exception e) {
Assert.fail("wrong exception caught: " + e.getClass().getName());
}
try {
// here, we did not set the bounds, so the differences are evaluated out of domain
// using f(0.85), f(0.95), f(1.05)
missingBounds.value(tHigh);
Assert.fail("an exception should have been thrown");
} catch (NumberIsTooLargeException nle) {
Assert.assertEquals(1.05, nle.getArgument().doubleValue(), 1.0e-10);
} catch (Exception e) {
Assert.fail("wrong exception caught: " + e.getClass().getName());
}
// here, we did set the bounds, so evaluations are done within domain
// using f(0.0), f(0.1), f(0.2)
Assert.assertEquals(slope, properlyBounded.value(tLow).getPartialDerivative(1), 1.0e-10);
// here, we did set the bounds, so evaluations are done within domain
// using f(0.8), f(0.9), f(1.0)
Assert.assertEquals(slope, properlyBounded.value(tHigh).getPartialDerivative(1), 1.0e-10);
}
@Test
public void testBoundedSqrt() {
UnivariateFunctionDifferentiator differentiator =
new FiniteDifferencesDifferentiator(9, 1.0 / 32, 0.0, Double.POSITIVE_INFINITY);
UnivariateDifferentiableFunction sqrt = differentiator.differentiate(new UnivariateFunction() {
@Override
public double value(double x) {
return AccurateMath.sqrt(x);
}
});
// we are able to compute derivative near 0, but the accuracy is much poorer there
DerivativeStructure t001 = new DerivativeStructure(1, 1, 0, 0.01);
Assert.assertEquals(0.5 / AccurateMath.sqrt(t001.getValue()), sqrt.value(t001).getPartialDerivative(1), 1.6);
DerivativeStructure t01 = new DerivativeStructure(1, 1, 0, 0.1);
Assert.assertEquals(0.5 / AccurateMath.sqrt(t01.getValue()), sqrt.value(t01).getPartialDerivative(1), 7.0e-3);
DerivativeStructure t03 = new DerivativeStructure(1, 1, 0, 0.3);
Assert.assertEquals(0.5 / AccurateMath.sqrt(t03.getValue()), sqrt.value(t03).getPartialDerivative(1), 2.1e-7);
}
@Test
public void testVectorFunction() {
FiniteDifferencesDifferentiator differentiator =
new FiniteDifferencesDifferentiator(7, 0.01);
UnivariateDifferentiableVectorFunction f =
differentiator.differentiate(new UnivariateVectorFunction() {
@Override
public double[] value(double x) {
return new double[] { AccurateMath.cos(x), AccurateMath.sin(x) };
}
});
for (double x = -10; x < 10; x += 0.1) {
DerivativeStructure dsX = new DerivativeStructure(1, 2, 0, x);
DerivativeStructure[] y = f.value(dsX);
double cos = AccurateMath.cos(x);
double sin = AccurateMath.sin(x);
double[] f1 = f.value(dsX.getValue());
DerivativeStructure[] f2 = f.value(dsX);
Assert.assertEquals(f1.length, f2.length);
for (int i = 0; i < f1.length; ++i) {
Assert.assertEquals(f1[i], f2[i].getValue(), 1.0e-15);
}
Assert.assertEquals( cos, y[0].getValue(), 7.0e-16);
Assert.assertEquals( sin, y[1].getValue(), 7.0e-16);
Assert.assertEquals(-sin, y[0].getPartialDerivative(1), 6.0e-14);
Assert.assertEquals( cos, y[1].getPartialDerivative(1), 6.0e-14);
Assert.assertEquals(-cos, y[0].getPartialDerivative(2), 2.0e-11);
Assert.assertEquals(-sin, y[1].getPartialDerivative(2), 2.0e-11);
}
}
@Test
public void testMatrixFunction() {
FiniteDifferencesDifferentiator differentiator =
new FiniteDifferencesDifferentiator(7, 0.01);
UnivariateDifferentiableMatrixFunction f =
differentiator.differentiate(new UnivariateMatrixFunction() {
@Override
public double[][] value(double x) {
return new double[][] {
{ AccurateMath.cos(x), AccurateMath.sin(x) },
{ AccurateMath.cosh(x), AccurateMath.sinh(x) }
};
}
});
for (double x = -1; x < 1; x += 0.02) {
DerivativeStructure dsX = new DerivativeStructure(1, 2, 0, x);
DerivativeStructure[][] y = f.value(dsX);
double cos = AccurateMath.cos(x);
double sin = AccurateMath.sin(x);
double cosh = AccurateMath.cosh(x);
double sinh = AccurateMath.sinh(x);
double[][] f1 = f.value(dsX.getValue());
DerivativeStructure[][] f2 = f.value(dsX);
Assert.assertEquals(f1.length, f2.length);
for (int i = 0; i < f1.length; ++i) {
Assert.assertEquals(f1[i].length, f2[i].length);
for (int j = 0; j < f1[i].length; ++j) {
Assert.assertEquals(f1[i][j], f2[i][j].getValue(), 1.0e-15);
}
}
Assert.assertEquals(cos, y[0][0].getValue(), 7.0e-18);
Assert.assertEquals(sin, y[0][1].getValue(), 6.0e-17);
Assert.assertEquals(cosh, y[1][0].getValue(), 3.0e-16);
Assert.assertEquals(sinh, y[1][1].getValue(), 3.0e-16);
Assert.assertEquals(-sin, y[0][0].getPartialDerivative(1), 2.0e-14);
Assert.assertEquals( cos, y[0][1].getPartialDerivative(1), 2.0e-14);
Assert.assertEquals( sinh, y[1][0].getPartialDerivative(1), 3.0e-14);
Assert.assertEquals( cosh, y[1][1].getPartialDerivative(1), 3.0e-14);
Assert.assertEquals(-cos, y[0][0].getPartialDerivative(2), 3.0e-12);
Assert.assertEquals(-sin, y[0][1].getPartialDerivative(2), 3.0e-12);
Assert.assertEquals( cosh, y[1][0].getPartialDerivative(2), 6.0e-12);
Assert.assertEquals( sinh, y[1][1].getPartialDerivative(2), 6.0e-12);
}
}
@Test
public void testSeveralFreeParameters() {
FiniteDifferencesDifferentiator differentiator =
new FiniteDifferencesDifferentiator(5, 0.001);
UnivariateDifferentiableFunction sine = new Sin();
UnivariateDifferentiableFunction f =
differentiator.differentiate(sine);
double[] expectedError = new double[] {
6.696e-16, 1.371e-12, 2.007e-8, 1.754e-5
};
double[] maxError = new double[expectedError.length];
for (double x = -2; x < 2; x += 0.1) {
for (double y = -2; y < 2; y += 0.1) {
DerivativeStructure dsX = new DerivativeStructure(2, maxError.length - 1, 0, x);
DerivativeStructure dsY = new DerivativeStructure(2, maxError.length - 1, 1, y);
DerivativeStructure dsT = dsX.multiply(3).subtract(dsY.multiply(2));
DerivativeStructure sRef = sine.value(dsT);
DerivativeStructure s = f.value(dsT);
for (int xOrder = 0; xOrder <= sRef.getOrder(); ++xOrder) {
for (int yOrder = 0; yOrder <= sRef.getOrder(); ++yOrder) {
if (xOrder + yOrder <= sRef.getOrder()) {
maxError[xOrder +yOrder] = AccurateMath.max(maxError[xOrder + yOrder],
AccurateMath.abs(sRef.getPartialDerivative(xOrder, yOrder) -
s.getPartialDerivative(xOrder, yOrder)));
}
}
}
}
}
for (int i = 0; i < maxError.length; ++i) {
Assert.assertEquals(expectedError[i], maxError[i], 0.01 * expectedError[i]);
}
}
}