blob: 4b2109af84b34abba4ee3c38551438a18a99cc02 [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#ifndef EIGEN_NUMERICALDIFF_RIDDERS_H
#define EIGEN_NUMERICALDIFF_RIDDERS_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
namespace internal {
template <typename F, typename = void>
struct ridders_solver_traits {};
template <typename F>
struct ridders_solver_traits<F, void_t<typename F::QRSolver>> {
using QRSolver = typename F::QRSolver;
};
} // namespace internal
/** \class Ridders
* \ingroup NumericalDiff_Module
* \brief Adaptive numerical Jacobians using Ridders' extrapolation method.
*
* Stores a functor by value and computes its Jacobian from central differences at successively
* smaller steps. Richardson extrapolation in the squared step size reduces truncation error;
* the Jacobian column with the smallest estimated error is retained. The stopping test uses
* the maximum absolute componentwise difference over the entire column.
*
* The functor supplies \c Scalar, \c InputType, \c ValueType, \c JacobianType,
* \c InputsAtCompileTime, \c ValuesAtCompileTime, \c values(), and \c operator(),
* as for NumericalDiff. The scalar type must be real and non-integer.
* The functor's \c inputs() method is required only when calling inputs().
*
* \warning The functor must be smooth and defined throughout the initial step interval.
*
* \code
* Eigen::Ridders<MyFunctor> derivative(functor);
* derivative.df(x, jacobian); // Returns the number of function evaluations.
* derivative.df(x, jacobian, error); // Also returns an absolute error estimate per input coordinate.
* \endcode
*
* \tparam Functor_ The vector-valued functor to differentiate.
*/
template <typename Functor_>
class Ridders : public internal::ridders_solver_traits<Functor_> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
using Functor = Functor_;
using Scalar = typename Functor::Scalar;
using InputType = typename Functor::InputType;
using ValueType = typename Functor::ValueType;
using JacobianType = typename Functor::JacobianType;
static constexpr int InputsAtCompileTime = Functor::InputsAtCompileTime;
static constexpr int ValuesAtCompileTime = Functor::ValuesAtCompileTime;
/** \brief Parameters controlling the initial interval and the maximum refinement cost. */
struct Parameters {
/** \brief Initial step scale, giving \f$h_j = \mathrm{initialStep}\,\max(|x_j|, 1)\f$.
* Must be positive and finite. */
Scalar initialStep = Scalar(0.01);
/** \brief Step reduction factor applied at each refinement.
* The factor must be greater than one and its square must be finite.
* Values close to one amplify rounding errors in extrapolation and may repeat sampling points.
* Refinement stops when both sampling points repeat; this does not detect all ill-conditioning. */
Scalar stepReduction = Scalar(2);
/** \brief Maximum number of central differences per input coordinate.
* Must be at least two and at most half the maximum representable Index. */
int maxSteps = 10;
};
Ridders() = default;
/** \brief Constructs from a copy of a functor and differentiation parameters.
* \param functor The functor to differentiate.
* \param parameters The differentiation parameters. */
explicit Ridders(const Functor& functor, const Parameters& parameters = Parameters()) : m_functor(functor) {
setParameters(parameters);
}
/** \brief Sets the differentiation parameters.
* \param parameters The differentiation parameters. */
void setParameters(const Parameters& parameters) {
eigen_assert(parameters.initialStep > Scalar(0) && (numext::isfinite)(parameters.initialStep));
eigen_assert(parameters.stepReduction > Scalar(1) &&
(numext::isfinite)(parameters.stepReduction * parameters.stepReduction));
eigen_assert(parameters.maxSteps >= 2);
eigen_assert(parameters.maxSteps <= NumTraits<Index>::highest() / 2);
m_parameters = parameters;
}
/** \returns the differentiation parameters. */
const Parameters& parameters() const { return m_parameters; }
/** \returns the stored functor. */
Functor& functor() { return m_functor; }
/** \returns the stored functor. */
const Functor& functor() const { return m_functor; }
/** \brief Evaluates the stored functor.
* \param[in] x The evaluation point.
* \param[out] values The function values.
* \returns the value returned by the functor. */
int operator()(const InputType& x, ValueType& values) const { return m_functor(x, values); }
/** \returns the stored functor's number of inputs. */
int inputs() const { return m_functor.inputs(); }
/** \returns the stored functor's number of values. */
int values() const { return m_functor.values(); }
/**
* \brief Computes the Jacobian using adaptive central differences.
*
* The caller sizes \a jac to values() by x.size(). At most \c 2*maxSteps evaluations
* are made per coordinate. Difference quotients divide by the separation of the representable
* evaluation points. Refinement stops when successive tableau diagonals differ by at least
* twice the best estimated error, when evaluation points coincide or repeat the previous pair,
* or when a non-finite value is encountered. The best finite estimate is retained;
* if none exists, the column contains NaNs.
*
* \param[in] x The evaluation point.
* \param[out] jac The Jacobian at \a x.
* \returns the number of functor evaluations.
* \sa Parameters
*/
int df(const InputType& x, JacobianType& jac) const { return dfImpl(x, jac, nullptr); }
/**
* \brief Computes the Jacobian and an absolute error estimate per input coordinate.
*
* The estimate is the maximum componentwise difference from the two parent tableau entries;
* it is a heuristic, not an error bound. An infinite estimate means no extrapolation was available.
*
* \param[in] x The evaluation point.
* \param[out] jac The Jacobian at \a x, sized by the caller to values() by x.size().
* \param[out] error The error estimates, resized to x.size().
* \returns the number of functor evaluations.
* \sa Parameters
*/
int df(const InputType& x, JacobianType& jac, InputType& error) const {
error.resize(x.size());
return dfImpl(x, jac, &error);
}
private:
Functor m_functor;
Parameters m_parameters;
int dfImpl(const InputType& input, JacobianType& jac, InputType* error) const {
EIGEN_STATIC_ASSERT(!NumTraits<Scalar>::IsComplex && !NumTraits<Scalar>::IsInteger,
NUMERICAL_DIFF_REQUIRES_A_REAL_FLOATING_POINT_SCALAR);
// C. J. F. Ridders, "Accurate computation of F'(x) and F'(x)F''(x)",
// Advances in Engineering Software 4(2), 75-76 (1982). Extrapolate central differences in h^2.
using Tableau = Matrix<Scalar, Functor::ValuesAtCompileTime, Dynamic>;
const Index values = m_functor.values();
eigen_assert(jac.rows() == values && jac.cols() == input.size());
if (values == 0 || input.size() == 0) {
if (error) error->setZero();
return 0;
}
Tableau workspace(values, 2 * Index(m_parameters.maxSteps));
auto first = workspace.leftCols(m_parameters.maxSteps);
auto second = workspace.rightCols(m_parameters.maxSteps);
auto current = &first;
auto previous = &second;
// Functors may require ValueType& outputs, which cannot bind to column views.
ValueType plus(values), minus(values);
InputType x = input;
const Scalar reductionSquared = m_parameters.stepReduction * m_parameters.stepReduction;
int evaluations = 0;
for (Index j = 0; j < input.size(); ++j) {
Scalar h = m_parameters.initialStep * numext::maxi(numext::abs(input[j]), Scalar(1));
Scalar previousPlus = input[j], previousMinus = input[j];
Scalar bestError = NumTraits<Scalar>::infinity();
jac.col(j).setConstant(NumTraits<Scalar>::quiet_NaN());
for (int step = 0; step < m_parameters.maxSteps; ++step) {
Scalar xPlus = input[j] + h;
Scalar xMinus = input[j] - h;
internal::pbarrier(xPlus);
internal::pbarrier(xMinus);
if (!(numext::isfinite)(xPlus) || !(numext::isfinite)(xMinus)) break;
if (step > 0 && xPlus == previousPlus && xMinus == previousMinus) break;
const Scalar separation = xPlus - xMinus;
if (!(separation > Scalar(0)) || !(numext::isfinite)(separation)) break;
previousPlus = xPlus;
previousMinus = xMinus;
x[j] = xPlus;
m_functor(x, plus);
x[j] = xMinus;
m_functor(x, minus);
x[j] = input[j];
evaluations += 2;
current->col(0) = (plus - minus) / separation;
if (!current->col(0).allFinite()) {
if (separation <= Scalar(1)) break;
// Divide before subtracting to avoid overflow, preserving already finite quotients.
auto quotient = current->col(0).array();
quotient = quotient.isFiniteTyped().select(quotient, plus.array() / separation - minus.array() / separation);
if (!current->col(0).allFinite()) break;
}
Index bestOrder = step == 0 ? 0 : -1;
Scalar factor = reductionSquared;
bool finite = true;
for (int order = 1; order <= step; ++order) {
// A(k,i) = (t^(2k) * A(k-1,i) - A(k-1,i-1)) / (t^(2k) - 1).
// Avoid forming `t^(2k) * A(k-1,i)` directly, since it can overflow.
current->col(order) =
current->col(order - 1) + (current->col(order - 1) - previous->col(order - 1)) / (factor - Scalar(1));
if (!current->col(order).allFinite()) {
finite = false;
break;
}
const Scalar estimate =
numext::maxi((current->col(order) - current->col(order - 1)).template lpNorm<Infinity>(),
(current->col(order) - previous->col(order - 1)).template lpNorm<Infinity>());
if ((numext::isfinite)(estimate) && estimate <= bestError) {
bestError = estimate;
bestOrder = order;
}
factor *= reductionSquared;
}
if (bestOrder >= 0) jac.col(j) = current->col(bestOrder);
if (!finite) break;
if (step > 0 &&
(current->col(step) - previous->col(step - 1)).template lpNorm<Infinity>() >= Scalar(2) * bestError)
break;
std::swap(current, previous);
h /= m_parameters.stepReduction;
}
if (error) (*error)[j] = bestError;
}
return evaluations;
}
};
#if EIGEN_COMP_CXXVER < 17
template <typename Functor>
constexpr int Ridders<Functor>::InputsAtCompileTime;
template <typename Functor>
constexpr int Ridders<Functor>::ValuesAtCompileTime;
#endif
} // namespace Eigen
#endif // EIGEN_NUMERICALDIFF_RIDDERS_H