com.opengamma.strata.math.impl.differentiation.VectorFieldFirstOrderDifferentiator Maven / Gradle / Ivy
Show all versions of strata-math Show documentation
/*
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.math.impl.differentiation;
import java.util.function.Function;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;
import com.opengamma.strata.math.MathException;
/**
* Differentiates a vector field (i.e. there is a vector value for every point
* in some vector space) with respect to the vector space using finite difference.
*
* For a function $\mathbf{y} = f(\mathbf{x})$ where $\mathbf{x}$ is a
* n-dimensional vector and $\mathbf{y}$ is a m-dimensional vector, this class
* produces the Jacobian function $\mathbf{J}(\mathbf{x})$, i.e. a function
* that returns the Jacobian for each point $\mathbf{x}$, where
* $\mathbf{J}$ is the $m \times n$ matrix $\frac{dy_i}{dx_j}$
*/
public class VectorFieldFirstOrderDifferentiator
implements Differentiator {
private static final double DEFAULT_EPS = 1e-5;
private final double eps;
private final double twoEps;
private final FiniteDifferenceType differenceType;
/**
* Creates an instance using the default value of eps (10-5) and central differencing type.
*/
public VectorFieldFirstOrderDifferentiator() {
this(FiniteDifferenceType.CENTRAL, DEFAULT_EPS);
}
/**
* Creates an instance using the default value of eps (10-5).
*
* @param differenceType the differencing type to be used in calculating the gradient function
*/
public VectorFieldFirstOrderDifferentiator(FiniteDifferenceType differenceType) {
this(differenceType, DEFAULT_EPS);
}
/**
* Creates an instance using the central differencing type.
*
* If the size of the domain is very small or very large, consider re-scaling first.
* If this value is too small, the result will most likely be dominated by noise.
* Use around 10-5 times the domain size.
*
* @param eps the step size used to approximate the derivative
*/
public VectorFieldFirstOrderDifferentiator(double eps) {
this(FiniteDifferenceType.CENTRAL, eps);
}
/**
* Creates an instance.
*
* If the size of the domain is very small or very large, consider re-scaling first.
* If this value is too small, the result will most likely be dominated by noise.
* Use around 10-5 times the domain size.
*
* @param differenceType the differencing type to be used in calculating the gradient function
* @param eps the step size used to approximate the derivative
*/
public VectorFieldFirstOrderDifferentiator(FiniteDifferenceType differenceType, double eps) {
ArgChecker.notNull(differenceType, "differenceType");
this.differenceType = differenceType;
this.eps = eps;
this.twoEps = 2 * eps;
}
//-------------------------------------------------------------------------
@Override
public Function differentiate(Function function) {
ArgChecker.notNull(function, "function");
switch (differenceType) {
case FORWARD:
return new Function() {
@SuppressWarnings("synthetic-access")
@Override
public DoubleMatrix apply(DoubleArray x) {
ArgChecker.notNull(x, "x");
DoubleArray y = function.apply(x);
int n = x.size();
int m = y.size();
double[][] res = new double[m][n];
for (int j = 0; j < n; j++) {
double xj = x.get(j);
DoubleArray up = function.apply(x.with(j, xj + eps));
for (int i = 0; i < m; i++) {
res[i][j] = (up.get(i) - y.get(i)) / eps;
}
}
return DoubleMatrix.copyOf(res);
}
};
case CENTRAL:
return new Function() {
@SuppressWarnings("synthetic-access")
@Override
public DoubleMatrix apply(DoubleArray x) {
ArgChecker.notNull(x, "x");
DoubleArray y = function.apply(x); // need this unused evaluation to get size of y
int n = x.size();
int m = y.size();
double[][] res = new double[m][n];
for (int j = 0; j < n; j++) {
double xj = x.get(j);
DoubleArray up = function.apply(x.with(j, xj + eps));
DoubleArray down = function.apply(x.with(j, xj - eps));
for (int i = 0; i < m; i++) {
res[i][j] = (up.get(i) - down.get(i)) / twoEps;
}
}
return DoubleMatrix.copyOf(res);
}
};
case BACKWARD:
return new Function() {
@SuppressWarnings("synthetic-access")
@Override
public DoubleMatrix apply(DoubleArray x) {
ArgChecker.notNull(x, "x");
DoubleArray y = function.apply(x);
int n = x.size();
int m = y.size();
double[][] res = new double[m][n];
for (int j = 0; j < n; j++) {
double xj = x.get(j);
DoubleArray down = function.apply(x.with(j, xj - eps));
for (int i = 0; i < m; i++) {
res[i][j] = (y.get(i) - down.get(i)) / eps;
}
}
return DoubleMatrix.copyOf(res);
}
};
default:
throw new IllegalArgumentException("Can only handle forward, backward and central differencing");
}
}
//-------------------------------------------------------------------------
@Override
public Function differentiate(
Function function,
Function domain) {
ArgChecker.notNull(function, "function");
ArgChecker.notNull(domain, "domain");
double[] wFwd = new double[] {-3., 4., -1.};
double[] wCent = new double[] {-1., 0., 1.};
double[] wBack = new double[] {1., -4., 3.};
return new Function() {
@SuppressWarnings("synthetic-access")
@Override
public DoubleMatrix apply(DoubleArray x) {
ArgChecker.notNull(x, "x");
ArgChecker.isTrue(domain.apply(x), "point {} is not in the function domain", x.toString());
DoubleArray mid = function.apply(x); // need this unused evaluation to get size of y
int n = x.size();
int m = mid.size();
double[][] res = new double[m][n];
DoubleArray[] y = new DoubleArray[3];
double[] w;
for (int j = 0; j < n; j++) {
double xj = x.get(j);
DoubleArray xPlusOneEps = x.with(j, xj + eps);
DoubleArray xMinusOneEps = x.with(j, xj - eps);
if (!domain.apply(xPlusOneEps)) {
DoubleArray xMinusTwoEps = x.with(j, xj - twoEps);
if (!domain.apply(xMinusTwoEps)) {
throw new MathException("cannot get derivative at point " + x.toString() + " in direction " + j);
}
y[2] = mid;
y[0] = function.apply(xMinusTwoEps);
y[1] = function.apply(xMinusOneEps);
w = wBack;
} else {
if (!domain.apply(xMinusOneEps)) {
y[0] = mid;
y[1] = function.apply(xPlusOneEps);
y[2] = function.apply(x.with(j, xj + twoEps));
w = wFwd;
} else {
y[2] = function.apply(xPlusOneEps);
y[0] = function.apply(xMinusOneEps);
y[1] = mid;
w = wCent;
}
}
for (int i = 0; i < m; i++) {
double sum = 0;
for (int k = 0; k < 3; k++) {
if (w[k] != 0.0) {
sum += w[k] * y[k].get(i);
}
}
res[i][j] = sum / twoEps;
}
}
return DoubleMatrix.copyOf(res);
}
};
}
}