//
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
//
// Copyright (c) 2009-2015 Math.NET
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Linq;
namespace IStation.Numerics.Differentiation
{
///
/// Type of finite different step size.
///
public enum StepType
{
///
/// The absolute step size value will be used in numerical derivatives, regardless of order or function parameters.
///
Absolute,
///
/// A base step size value, h, will be scaled according to the function input parameter. A common example is hx = h*(1+abs(x)), however
/// this may vary depending on implementation. This definition only guarantees that the only scaling will be relative to the
/// function input parameter and not the order of the finite difference derivative.
///
RelativeX,
///
/// A base step size value, eps (typically machine precision), is scaled according to the finite difference coefficient order
/// and function input parameter. The initial scaling according to finite different coefficient order can be thought of as producing a
/// base step size, h, that is equivalent to scaling. This step size is then scaled according to the function
/// input parameter. Although implementation may vary, an example of second order accurate scaling may be (eps)^(1/3)*(1+abs(x)).
///
Relative
};
///
/// Class to evaluate the numerical derivative of a function using finite difference approximations.
/// Variable point and center methods can be initialized .
/// This class can also be used to return function handles (delegates) for a fixed derivative order and variable.
/// It is possible to evaluate the derivative and partial derivative of univariate and multivariate functions respectively.
///
public class NumericalDerivative
{
readonly int _points;
int _center;
double _stepSize = Math.Pow(2, -10);
double _epsilon = Precision.PositiveMachineEpsilon;
double _baseStepSize = Math.Pow(2, -26);
readonly FiniteDifferenceCoefficients _coefficients;
///
/// Initializes a NumericalDerivative class with the default 3 point center difference method.
///
public NumericalDerivative() : this(3, 1)
{
}
///
/// Initialized a NumericalDerivative class.
///
/// Number of points for finite difference derivatives.
/// Location of the center with respect to other points. Value ranges from zero to points-1.
public NumericalDerivative(int points, int center)
{
if (points < 2)
{
throw new ArgumentOutOfRangeException(nameof(points), "Points must be two or greater.");
}
_center = center;
_points = points;
Center = center;
_coefficients = new FiniteDifferenceCoefficients(points);
}
///
/// Sets and gets the finite difference step size. This value is for each function evaluation if relative step size types are used.
/// If the base step size used in scaling is desired, see .
///
///
/// Setting then getting the StepSize may return a different value. This is not unusual since a user-defined step size is converted to a
/// base-2 representable number to improve finite difference accuracy.
///
public double StepSize
{
get => _stepSize;
set
{
//Base 2 yields more accurate results...
var p = Math.Log(Math.Abs(value))/Math.Log(2);
_stepSize = Math.Pow(2, Math.Round(p));
}
}
///
/// Sets and gets the base finite difference step size. This assigned value to this parameter is only used if is set to RelativeX.
/// However, if the StepType is Relative, it will contain the base step size computed from based on the finite difference order.
///
public double BaseStepSize
{
get => _baseStepSize;
set
{
//Base 2 yields more accurate results...
var p = Math.Log(Math.Abs(value)) / Math.Log(2);
_baseStepSize = Math.Pow(2, Math.Round(p));
}
}
///
/// Sets and gets the base finite difference step size. This parameter is only used if is set to Relative.
/// By default this is set to machine epsilon, from which is computed.
///
public double Epsilon
{
get => _epsilon;
set
{
//Base 2 yields more accurate results...
var p = Math.Log(Math.Abs(value)) / Math.Log(2);
_epsilon = Math.Pow(2, Math.Round(p));
}
}
///
/// Sets and gets the location of the center point for the finite difference derivative.
///
public int Center
{
get => _center;
set
{
if (value >= _points || value < 0)
throw new ArgumentOutOfRangeException(nameof(value), "Center must lie between 0 and points -1");
_center = value;
}
}
///
/// Number of times a function is evaluated for numerical derivatives.
///
public int Evaluations { get; private set; }
///
/// Type of step size for computing finite differences. If set to absolute, dx = h.
/// If set to relative, dx = (1+abs(x))*h^(2/(order+1)). This provides accurate results when
/// h is approximately equal to the square-root of machine accuracy, epsilon.
///
public StepType StepType { get; set; } = StepType.Relative;
///
/// Evaluates the derivative of equidistant points using the finite difference method.
///
/// Vector of points StepSize apart.
/// Derivative order.
/// Finite difference step size.
/// Derivative of points of the specified order.
public double EvaluateDerivative(double[] points, int order, double stepSize)
{
if (points == null)
throw new ArgumentNullException(nameof(points));
if (order >= _points || order < 0)
throw new ArgumentOutOfRangeException(nameof(order), "Order must be between zero and points-1.");
var c = _coefficients.GetCoefficients(Center, order);
var result = c.Select((t, i) => t*points[i]).Sum();
result /= Math.Pow(stepSize, order);
return result;
}
///
/// Evaluates the derivative of a scalar univariate function.
///
///
/// Supplying the optional argument currentValue will reduce the number of function evaluations
/// required to calculate the finite difference derivative.
///
/// Function handle.
/// Point at which to compute the derivative.
/// Derivative order.
/// Current function value at center.
/// Function derivative at x of the specified order.
public double EvaluateDerivative(Func f, double x, int order, double? currentValue = null)
{
var c = _coefficients.GetCoefficients(Center, order);
var h = CalculateStepSize(_points, x, order);
var points = new double[_points];
for (int i = 0; i < _points; i++)
{
if (i == Center && currentValue.HasValue)
points[i] = currentValue.Value;
else if(c[i] != 0) // Only evaluate function if it will actually be used.
{
points[i] = f(x + (i - Center) * h);
Evaluations++;
}
}
return EvaluateDerivative(points, order, h);
}
///
/// Creates a function handle for the derivative of a scalar univariate function.
///
/// Input function handle.
/// Derivative order.
/// Function handle that evaluates the derivative of input function at a fixed order.
public Func CreateDerivativeFunctionHandle(Func f, int order)
{
return x => EvaluateDerivative(f, x, order);
}
///
/// Evaluates the partial derivative of a multivariate function.
///
/// Multivariate function handle.
/// Vector at which to evaluate the derivative.
/// Index of independent variable for partial derivative.
/// Derivative order.
/// Current function value at center.
/// Function partial derivative at x of the specified order.
public double EvaluatePartialDerivative(Func f, double[] x, int parameterIndex, int order, double? currentValue = null)
{
var xi = x[parameterIndex];
var c = _coefficients.GetCoefficients(Center, order);
var h = CalculateStepSize(_points, x[parameterIndex], order);
var points = new double[_points];
for (int i = 0; i < _points; i++)
{
if (i == Center && currentValue.HasValue)
points[i] = currentValue.Value;
else if(c[i] != 0) // Only evaluate function if it will actually be used.
{
x[parameterIndex] = xi + (i - Center) * h;
points[i] = f(x);
Evaluations++;
}
}
//restore original value
x[parameterIndex] = xi;
return EvaluateDerivative(points, order, h);
}
///
/// Evaluates the partial derivatives of a multivariate function array.
///
///
/// This function assumes the input vector x is of the correct length for f.
///
/// Multivariate vector function array handle.
/// Vector at which to evaluate the derivatives.
/// Index of independent variable for partial derivative.
/// Derivative order.
/// Current function value at center.
/// Vector of functions partial derivatives at x of the specified order.
public double[] EvaluatePartialDerivative(Func[] f, double[] x, int parameterIndex, int order, double?[] currentValue = null)
{
var df = new double[f.Length];
for (int i = 0; i < f.Length; i++)
{
if(currentValue != null && currentValue[i].HasValue)
df[i] = EvaluatePartialDerivative(f[i], x, parameterIndex, order, currentValue[i]!.Value);
else
df[i] = EvaluatePartialDerivative(f[i], x, parameterIndex, order);
}
return df;
}
///
/// Creates a function handle for the partial derivative of a multivariate function.
///
/// Input function handle.
/// Index of the independent variable for partial derivative.
/// Derivative order.
/// Function handle that evaluates partial derivative of input function at a fixed order.
public Func CreatePartialDerivativeFunctionHandle(Func f, int parameterIndex,
int order)
{
return x => EvaluatePartialDerivative(f, x, parameterIndex, order);
}
///
/// Creates a function handle for the partial derivative of a vector multivariate function.
///
/// Input function handle.
/// Index of the independent variable for partial derivative.
/// Derivative order.
/// Function handle that evaluates partial derivative of input function at fixed order.
public Func CreatePartialDerivativeFunctionHandle(Func[] f,
int parameterIndex,
int order)
{
return x => EvaluatePartialDerivative(f, x, parameterIndex, order);
}
///
/// Evaluates the mixed partial derivative of variable order for multivariate functions.
///
///
/// This function recursively uses to evaluate mixed partial derivative.
/// Therefore, it is more efficient to call for higher order derivatives of
/// a single independent variable.
///
/// Multivariate function handle.
/// Points at which to evaluate the derivative.
/// Vector of indices for the independent variables at descending derivative orders.
/// Highest order of differentiation.
/// Current function value at center.
/// Function mixed partial derivative at x of the specified order.
public double EvaluateMixedPartialDerivative(Func f, double[] x, int[] parameterIndex,
int order, double? currentValue = null)
{
if (parameterIndex.Length != order)
throw new ArgumentOutOfRangeException(nameof(parameterIndex),
"The number of parameters must match derivative order.");
if (order == 1)
return EvaluatePartialDerivative(f, x, parameterIndex[0], order, currentValue);
int reducedOrder = order - 1;
var reducedParameterIndex = new int[reducedOrder];
Array.Copy(parameterIndex, 0, reducedParameterIndex, 0, reducedOrder);
var points = new double[_points];
var currentParameterIndex = parameterIndex[order - 1];
var h = CalculateStepSize(_points, x[currentParameterIndex], order);
var xi = x[currentParameterIndex];
for (int i = 0; i < _points; i++)
{
x[currentParameterIndex] = xi + (i - Center)*h;
points[i] = EvaluateMixedPartialDerivative(f, x, reducedParameterIndex, reducedOrder);
}
// restore original value
x[currentParameterIndex] = xi;
// This will always be to the first order
return EvaluateDerivative(points, 1, h);
}
///
/// Evaluates the mixed partial derivative of variable order for multivariate function arrays.
///
///
/// This function recursively uses to evaluate mixed partial derivative.
/// Therefore, it is more efficient to call for higher order derivatives of
/// a single independent variable.
///
/// Multivariate function array handle.
/// Vector at which to evaluate the derivative.
/// Vector of indices for the independent variables at descending derivative orders.
/// Highest order of differentiation.
/// Current function value at center.
/// Function mixed partial derivatives at x of the specified order.
public double[] EvaluateMixedPartialDerivative(Func[] f, double[] x, int[] parameterIndex,
int order, double?[]? currentValue = null)
{
var df = new double[f.Length];
for (int i = 0; i < f.Length; i++)
{
if(currentValue != null && currentValue[i].HasValue)
df[i] = EvaluateMixedPartialDerivative(f[i], x, parameterIndex, order, currentValue[i]!.Value);
else
df[i] = EvaluateMixedPartialDerivative(f[i], x, parameterIndex, order);
}
return df;
}
///
/// Creates a function handle for the mixed partial derivative of a multivariate function.
///
/// Input function handle.
/// Vector of indices for the independent variables at descending derivative orders.
/// Highest derivative order.
/// Function handle that evaluates the fixed mixed partial derivative of input function at fixed order.
public Func CreateMixedPartialDerivativeFunctionHandle(Func f,
int[] parameterIndex, int order)
{
return x => EvaluateMixedPartialDerivative(f, x, parameterIndex, order);
}
///
/// Creates a function handle for the mixed partial derivative of a multivariate vector function.
///
/// Input vector function handle.
/// Vector of indices for the independent variables at descending derivative orders.
/// Highest derivative order.
/// Function handle that evaluates the fixed mixed partial derivative of input function at fixed order.
public Func CreateMixedPartialDerivativeFunctionHandle(Func[] f,
int[] parameterIndex, int order)
{
return x => EvaluateMixedPartialDerivative(f, x, parameterIndex, order);
}
///
/// Resets the evaluation counter.
///
public void ResetEvaluations()
{
Evaluations = 0;
}
private double[] CalculateStepSize(int points, double[] x, double order)
{
var h = new double[x.Length];
for (int i = 1; i < h.Length; i++)
h[i] = CalculateStepSize(points, x[i], order);
return h;
}
private double CalculateStepSize(int points, double x, double order)
{
// Step size relative to function input parameter
if (StepType == StepType.RelativeX)
{
StepSize = BaseStepSize*(1 + Math.Abs(x));
}
// Step size relative to function input parameter and order
else if (StepType == StepType.Relative)
{
var accuracy = points - order;
BaseStepSize = Math.Pow(Epsilon,(1/(accuracy + order)));
StepSize = BaseStepSize*(1 + Math.Abs(x));
}
// Do nothing for absolute step size.
return StepSize;
}
}
}