MATH-1648: Derivatives for "BicubicInterpolatingFunction".

This commit is contained in:
amoscatelli 2022-07-22 15:21:49 +02:00 committed by Gilles Sadowski
parent 4ef2d3b8c2
commit 8e6dee5958
3 changed files with 493 additions and 3 deletions

View File

@ -17,6 +17,8 @@
package org.apache.commons.math4.legacy.analysis.interpolation; package org.apache.commons.math4.legacy.analysis.interpolation;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.DoubleBinaryOperator;
import java.util.function.Function;
import org.apache.commons.numbers.core.Sum; import org.apache.commons.numbers.core.Sum;
import org.apache.commons.math4.legacy.analysis.BivariateFunction; import org.apache.commons.math4.legacy.analysis.BivariateFunction;
@ -92,6 +94,38 @@ public class BicubicInterpolatingFunction
throws DimensionMismatchException, throws DimensionMismatchException,
NoDataException, NoDataException,
NonMonotonicSequenceException { NonMonotonicSequenceException {
this(x, y, f, dFdX, dFdY, d2FdXdY, false);
}
/**
* @param x Sample values of the x-coordinate, in increasing order.
* @param y Sample values of the y-coordinate, in increasing order.
* @param f Values of the function on every grid point.
* @param dFdX Values of the partial derivative of function with respect
* to x on every grid point.
* @param dFdY Values of the partial derivative of function with respect
* to y on every grid point.
* @param d2FdXdY Values of the cross partial derivative of function on
* every grid point.
* @param initializeDerivatives Whether to initialize the internal data
* needed for calling any of the methods that compute the partial derivatives
* this function.
* @throws DimensionMismatchException if the various arrays do not contain
* the expected number of elements.
* @throws NonMonotonicSequenceException if {@code x} or {@code y} are
* not strictly increasing.
* @throws NoDataException if any of the arrays has zero length.
*/
public BicubicInterpolatingFunction(double[] x,
double[] y,
double[][] f,
double[][] dFdX,
double[][] dFdY,
double[][] d2FdXdY,
boolean initializeDerivatives)
throws DimensionMismatchException,
NoDataException,
NonMonotonicSequenceException {
final int xLen = x.length; final int xLen = x.length;
final int yLen = y.length; final int yLen = y.length;
@ -147,7 +181,10 @@ public class BicubicInterpolatingFunction
d2FdXdY[i][j] * xRyR, d2FdXdY[ip1][j] * xRyR, d2FdXdY[i][jp1] * xRyR, d2FdXdY[ip1][jp1] * xRyR d2FdXdY[i][j] * xRyR, d2FdXdY[ip1][j] * xRyR, d2FdXdY[i][jp1] * xRyR, d2FdXdY[ip1][jp1] * xRyR
}; };
splines[i][j] = new BicubicFunction(computeSplineCoefficients(beta)); splines[i][j] = new BicubicFunction(computeSplineCoefficients(beta),
xR,
yR,
initializeDerivatives);
} }
} }
} }
@ -181,6 +218,75 @@ public class BicubicInterpolatingFunction
y > yval[yval.length - 1]); y > yval[yval.length - 1]);
} }
/**
* @return the first partial derivative respect to x.
* @throws NullPointerException if the internal data were not initialized
* (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
* double[][],double[][],double[][],boolean) constructor}).
*/
public DoubleBinaryOperator partialDerivativeX() {
return partialDerivative(BicubicFunction::partialDerivativeX);
}
/**
* @return the first partial derivative respect to y.
* @throws NullPointerException if the internal data were not initialized
* (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
* double[][],double[][],double[][],boolean) constructor}).
*/
public DoubleBinaryOperator partialDerivativeY() {
return partialDerivative(BicubicFunction::partialDerivativeY);
}
/**
* @return the second partial derivative respect to x.
* @throws NullPointerException if the internal data were not initialized
* (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
* double[][],double[][],double[][],boolean) constructor}).
*/
public DoubleBinaryOperator partialDerivativeXX() {
return partialDerivative(BicubicFunction::partialDerivativeXX);
}
/**
* @return the second partial derivative respect to y.
* @throws NullPointerException if the internal data were not initialized
* (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
* double[][],double[][],double[][],boolean) constructor}).
*/
public DoubleBinaryOperator partialDerivativeYY() {
return partialDerivative(BicubicFunction::partialDerivativeYY);
}
/**
* @return the second partial cross derivative.
* @throws NullPointerException if the internal data were not initialized
* (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
* double[][],double[][],double[][],boolean) constructor}).
*/
public DoubleBinaryOperator partialDerivativeXY() {
return partialDerivative(BicubicFunction::partialDerivativeXY);
}
/**
* @param which derivative function to apply.
* @return the selected partial derivative.
* @throws NullPointerException if the internal data were not initialized
* (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
* double[][],double[][],double[][],boolean) constructor}).
*/
private DoubleBinaryOperator partialDerivative(Function<BicubicFunction, BivariateFunction> which) {
return (x, y) -> {
final int i = searchIndex(x, xval);
final int j = searchIndex(y, yval);
final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
return which.apply(splines[i][j]).value(xN, yN);
};
}
/** /**
* @param c Coordinate. * @param c Coordinate.
* @param val Coordinate samples. * @param val Coordinate samples.
@ -256,6 +362,7 @@ public class BicubicInterpolatingFunction
return a; return a;
} }
} }
/** /**
@ -266,13 +373,31 @@ class BicubicFunction implements BivariateFunction {
private static final short N = 4; private static final short N = 4;
/** Coefficients. */ /** Coefficients. */
private final double[][] a; private final double[][] a;
/** First partial derivative along x. */
private final BivariateFunction partialDerivativeX;
/** First partial derivative along y. */
private final BivariateFunction partialDerivativeY;
/** Second partial derivative along x. */
private final BivariateFunction partialDerivativeXX;
/** Second partial derivative along y. */
private final BivariateFunction partialDerivativeYY;
/** Second crossed partial derivative. */
private final BivariateFunction partialDerivativeXY;
/** /**
* Simple constructor. * Simple constructor.
* *
* @param coeff Spline coefficients. * @param coeff Spline coefficients.
* @param xR x spacing.
* @param yR y spacing.
* @param initializeDerivatives Whether to initialize the internal data
* needed for calling any of the methods that compute the partial derivatives
* this function.
*/ */
BicubicFunction(double[] coeff) { BicubicFunction(double[] coeff,
double xR,
double yR,
boolean initializeDerivatives) {
a = new double[N][N]; a = new double[N][N];
for (int j = 0; j < N; j++) { for (int j = 0; j < N; j++) {
final double[] aJ = a[j]; final double[] aJ = a[j];
@ -280,6 +405,80 @@ class BicubicFunction implements BivariateFunction {
aJ[i] = coeff[i * N + j]; aJ[i] = coeff[i * N + j];
} }
} }
if (initializeDerivatives) {
// Compute all partial derivatives functions.
final double[][] aX = new double[N][N];
final double[][] aY = new double[N][N];
final double[][] aXX = new double[N][N];
final double[][] aYY = new double[N][N];
final double[][] aXY = new double[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
final double c = a[i][j];
aX[i][j] = i * c;
aY[i][j] = j * c;
aXX[i][j] = (i - 1) * aX[i][j];
aYY[i][j] = (j - 1) * aY[i][j];
aXY[i][j] = j * aX[i][j];
}
}
partialDerivativeX = (double x, double y) -> {
final double x2 = x * x;
final double[] pX = {0, 1, x, x2};
final double y2 = y * y;
final double y3 = y2 * y;
final double[] pY = {1, y, y2, y3};
return apply(pX, pY, aX) / xR;
};
partialDerivativeY = (double x, double y) -> {
final double x2 = x * x;
final double x3 = x2 * x;
final double[] pX = {1, x, x2, x3};
final double y2 = y * y;
final double[] pY = {0, 1, y, y2};
return apply(pX, pY, aY) / yR;
};
partialDerivativeXX = (double x, double y) -> {
final double[] pX = {0, 0, 1, x};
final double y2 = y * y;
final double y3 = y2 * y;
final double[] pY = {1, y, y2, y3};
return apply(pX, pY, aXX) / (xR * xR);
};
partialDerivativeYY = (double x, double y) -> {
final double x2 = x * x;
final double x3 = x2 * x;
final double[] pX = {1, x, x2, x3};
final double[] pY = {0, 0, 1, y};
return apply(pX, pY, aYY) / (yR * yR);
};
partialDerivativeXY = (double x, double y) -> {
final double x2 = x * x;
final double[] pX = {0, 1, x, x2};
final double y2 = y * y;
final double[] pY = {0, 1, y, y2};
return apply(pX, pY, aXY) / (xR * yR);
};
} else {
partialDerivativeX = null;
partialDerivativeY = null;
partialDerivativeXX = null;
partialDerivativeYY = null;
partialDerivativeXY = null;
}
} }
/** /**
@ -322,4 +521,40 @@ class BicubicFunction implements BivariateFunction {
return result; return result;
} }
/**
* @return the partial derivative wrt {@code x}.
*/
BivariateFunction partialDerivativeX() {
return partialDerivativeX;
}
/**
* @return the partial derivative wrt {@code y}.
*/
BivariateFunction partialDerivativeY() {
return partialDerivativeY;
}
/**
* @return the second partial derivative wrt {@code x}.
*/
BivariateFunction partialDerivativeXX() {
return partialDerivativeXX;
}
/**
* @return the second partial derivative wrt {@code y}.
*/
BivariateFunction partialDerivativeYY() {
return partialDerivativeYY;
}
/**
* @return the second partial cross-derivative.
*/
BivariateFunction partialDerivativeXY() {
return partialDerivativeXY;
}
} }

View File

@ -41,6 +41,32 @@ import org.apache.commons.math4.legacy.core.MathArrays;
*/ */
public class BicubicInterpolator public class BicubicInterpolator
implements BivariateGridInterpolator { implements BivariateGridInterpolator {
/**
* Whether to initialize internal data used to compute the analytical
* derivatives of the splines.
*/
private final boolean initializeDerivatives;
/**
* Default constructor.
* The argument {@link #BicubicInterpolator(boolean) initializeDerivatives}
* is set to {@code false}.
*/
public BicubicInterpolator() {
this(false);
}
/**
* Creates an interpolator.
*
* @param initializeDerivatives Whether to initialize the internal data
* needed for calling any of the methods that compute the partial derivatives
* of the {@link BicubicInterpolatingFunction function} returned from
* the call to {@link #interpolate(double[],double[],double[][]) interpolate}.
*/
public BicubicInterpolator(boolean initializeDerivatives) {
this.initializeDerivatives = initializeDerivatives;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -96,7 +122,8 @@ public class BicubicInterpolator
// Create the interpolating function. // Create the interpolating function.
return new BicubicInterpolatingFunction(xval, yval, fval, return new BicubicInterpolatingFunction(xval, yval, fval,
dFdX, dFdY, d2FdXdY) { dFdX, dFdY, d2FdXdY,
initializeDerivatives) {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public boolean isValidPoint(double x, double y) { public boolean isValidPoint(double x, double y) {

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.math4.legacy.analysis.interpolation; package org.apache.commons.math4.legacy.analysis.interpolation;
import java.util.function.DoubleBinaryOperator;
import org.apache.commons.math4.legacy.analysis.BivariateFunction; import org.apache.commons.math4.legacy.analysis.BivariateFunction;
import org.apache.commons.statistics.distribution.ContinuousDistribution; import org.apache.commons.statistics.distribution.ContinuousDistribution;
import org.apache.commons.statistics.distribution.UniformContinuousDistribution; import org.apache.commons.statistics.distribution.UniformContinuousDistribution;
@ -287,6 +288,233 @@ public final class BicubicInterpolatingFunctionTest {
false); false);
} }
/**
* Test for partial derivatives of {@link BicubicFunction}.
* <p>
* f(x, y) = &Sigma;<sub>i</sub>&Sigma;<sub>j</sub> (i+1) (j+2) x<sup>i</sup> y<sup>j</sup>
*/
@Test
public void testSplinePartialDerivatives() {
final int N = 4;
final double[] coeff = new double[16];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
coeff[i + N * j] = (i + 1) * (j + 2);
}
}
final BicubicFunction f = new BicubicFunction(coeff, 1, 1, true);
BivariateFunction derivative;
final double x = 0.435;
final double y = 0.776;
final double tol = 1e-13;
derivative = new BivariateFunction() {
public double value(double x, double y) {
final double x2 = x * x;
final double y2 = y * y;
final double y3 = y2 * y;
final double yFactor = 2 + 3 * y + 4 * y2 + 5 * y3;
return yFactor * (2 + 6 * x + 12 * x2);
}
};
Assert.assertEquals("dFdX", derivative.value(x, y),
f.partialDerivativeX().value(x, y), tol);
derivative = new BivariateFunction() {
public double value(double x, double y) {
final double x2 = x * x;
final double x3 = x2 * x;
final double y2 = y * y;
final double xFactor = 1 + 2 * x + 3 * x2 + 4 * x3;
return xFactor * (3 + 8 * y + 15 * y2);
}
};
Assert.assertEquals("dFdY", derivative.value(x, y),
f.partialDerivativeY().value(x, y), tol);
derivative = new BivariateFunction() {
public double value(double x, double y) {
final double y2 = y * y;
final double y3 = y2 * y;
final double yFactor = 2 + 3 * y + 4 * y2 + 5 * y3;
return yFactor * (6 + 24 * x);
}
};
Assert.assertEquals("d2FdX2", derivative.value(x, y),
f.partialDerivativeXX().value(x, y), tol);
derivative = new BivariateFunction() {
public double value(double x, double y) {
final double x2 = x * x;
final double x3 = x2 * x;
final double xFactor = 1 + 2 * x + 3 * x2 + 4 * x3;
return xFactor * (8 + 30 * y);
}
};
Assert.assertEquals("d2FdY2", derivative.value(x, y),
f.partialDerivativeYY().value(x, y), tol);
derivative = new BivariateFunction() {
public double value(double x, double y) {
final double x2 = x * x;
final double y2 = y * y;
final double yFactor = 3 + 8 * y + 15 * y2;
return yFactor * (2 + 6 * x + 12 * x2);
}
};
Assert.assertEquals("d2FdXdY", derivative.value(x, y),
f.partialDerivativeXY().value(x, y), tol);
}
/**
* Test that the partial derivatives computed from a
* {@link BicubicInterpolatingFunction} match the input data.
* <p>
* f(x, y) = 5
* - 3 x + 2 y
* - x y + 2 x<sup>2</sup> - 3 y<sup>2</sup>
* + 4 x<sup>2</sup> y - x y<sup>2</sup> - 3 x<sup>3</sup> + y<sup>3</sup>
*/
@Test
public void testMatchingPartialDerivatives() {
final int sz = 21;
double[] xval = new double[sz];
double[] yval = new double[sz];
// Coordinate values
final double delta = 1d / (sz - 1);
for (int i = 0; i < sz; i++) {
xval[i] = i * delta;
yval[i] = i * delta / 3;
}
// Function values
BivariateFunction f = new BivariateFunction() {
public double value(double x, double y) {
final double x2 = x * x;
final double x3 = x2 * x;
final double y2 = y * y;
final double y3 = y2 * y;
return 5
- 3 * x + 2 * y
- x * y + 2 * x2 - 3 * y2
+ 4 * x2 * y - x * y2 - 3 * x3 + y3;
}
};
double[][] fval = new double[sz][sz];
for (int i = 0; i < sz; i++) {
for (int j = 0; j < sz; j++) {
fval[i][j] = f.value(xval[i], yval[j]);
}
}
// Partial derivatives with respect to x
double[][] dFdX = new double[sz][sz];
BivariateFunction dfdX = new BivariateFunction() {
public double value(double x, double y) {
final double x2 = x * x;
final double y2 = y * y;
return - 3 - y + 4 * x + 8 * x * y - y2 - 9 * x2;
}
};
for (int i = 0; i < sz; i++) {
for (int j = 0; j < sz; j++) {
dFdX[i][j] = dfdX.value(xval[i], yval[j]);
}
}
// Partial derivatives with respect to y
double[][] dFdY = new double[sz][sz];
BivariateFunction dfdY = new BivariateFunction() {
public double value(double x, double y) {
final double x2 = x * x;
final double y2 = y * y;
return 2 - x - 6 * y + 4 * x2 - 2 * x * y + 3 * y2;
}
};
for (int i = 0; i < sz; i++) {
for (int j = 0; j < sz; j++) {
dFdY[i][j] = dfdY.value(xval[i], yval[j]);
}
}
// Second partial derivatives with respect to x
double[][] d2Fd2X = new double[sz][sz];
BivariateFunction d2fd2X = new BivariateFunction() {
public double value(double x, double y) {
return 4 + 8 * y - 18 * x;
}
};
for (int i = 0; i < sz; i++) {
for (int j = 0; j < sz; j++) {
d2Fd2X[i][j] = d2fd2X.value(xval[i], yval[j]);
}
}
// Second partial derivatives with respect to y
double[][] d2Fd2Y = new double[sz][sz];
BivariateFunction d2fd2Y = new BivariateFunction() {
public double value(double x, double y) {
return - 6 - 2 * x + 6 * y;
}
};
for (int i = 0; i < sz; i++) {
for (int j = 0; j < sz; j++) {
d2Fd2Y[i][j] = d2fd2Y.value(xval[i], yval[j]);
}
}
// Partial cross-derivatives
double[][] d2FdXdY = new double[sz][sz];
BivariateFunction d2fdXdY = new BivariateFunction() {
public double value(double x, double y) {
return -1 + 8 * x - 2 * y;
}
};
for (int i = 0; i < sz; i++) {
for (int j = 0; j < sz; j++) {
d2FdXdY[i][j] = d2fdXdY.value(xval[i], yval[j]);
}
}
BicubicInterpolatingFunction bcf
= new BicubicInterpolatingFunction(xval, yval, fval, dFdX, dFdY, d2FdXdY, true);
DoubleBinaryOperator partialDerivativeX = bcf.partialDerivativeX();
DoubleBinaryOperator partialDerivativeY = bcf.partialDerivativeY();
DoubleBinaryOperator partialDerivativeXX = bcf.partialDerivativeXX();
DoubleBinaryOperator partialDerivativeYY = bcf.partialDerivativeYY();
DoubleBinaryOperator partialDerivativeXY = bcf.partialDerivativeXY();
double x;
double y;
double expected;
double result;
final double tol = 1e-10;
for (int i = 0; i < sz; i++) {
x = xval[i];
for (int j = 0; j < sz; j++) {
y = yval[j];
expected = dfdX.value(x, y);
result = partialDerivativeX.applyAsDouble(x, y);
Assert.assertEquals(x + " " + y + " dFdX", expected, result, tol);
expected = dfdY.value(x, y);
result = partialDerivativeY.applyAsDouble(x, y);
Assert.assertEquals(x + " " + y + " dFdY", expected, result, tol);
expected = d2fd2X.value(x, y);
result = partialDerivativeXX.applyAsDouble(x, y);
Assert.assertEquals(x + " " + y + " d2Fd2X", expected, result, tol);
expected = d2fd2Y.value(x, y);
result = partialDerivativeYY.applyAsDouble(x, y);
Assert.assertEquals(x + " " + y + " d2Fd2Y", expected, result, tol);
expected = d2fdXdY.value(x, y);
result = partialDerivativeXY.applyAsDouble(x, y);
Assert.assertEquals(x + " " + y + " d2FdXdY", expected, result, tol);
}
}
}
/** /**
* @param minimumX Lower bound of interpolation range along the x-coordinate. * @param minimumX Lower bound of interpolation range along the x-coordinate.
* @param maximumX Higher bound of interpolation range along the x-coordinate. * @param maximumX Higher bound of interpolation range along the x-coordinate.