blob: f8c597807e4940f7724f92261c254c37f8115509 [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#include "main.h"
#include <contrib/Eigen/NumericalDiff>
#include <contrib/Eigen/LevenbergMarquardt>
template <typename Scalar_, int Inputs = Dynamic, int Values = Dynamic, int Options = ColMajor>
struct RiddersFunctor {
using Scalar = Scalar_;
using InputType = Matrix<Scalar, Inputs, 1>;
using ValueType = Matrix<Scalar, Values, 1>;
using JacobianType = Matrix<Scalar, Values, Inputs, Options>;
static constexpr int InputsAtCompileTime = Inputs;
static constexpr int ValuesAtCompileTime = Values;
mutable int evaluations = 0;
int values() const { return 4; }
int operator()(const InputType& x, ValueType& y) const {
++evaluations;
y[0] = numext::exp(x[0]);
y[1] = numext::sin(Scalar(50) * x[0]);
y[2] = x[0] * x[1];
y[3] = numext::exp(x[1]);
return 0;
}
};
template <typename Scalar, int Inputs, int Values, int Options>
void test_ridders_jacobian() {
using F = RiddersFunctor<Scalar, Inputs, Values, Options>;
Ridders<F> diff;
typename F::InputType x(2), error(2);
typename F::JacobianType jac(4, 2), expected(4, 2), withoutError(4, 2);
x << Scalar(1), Scalar(-2);
const typename F::InputType original = x;
expected.row(0) << Scalar(std::exp(1.0L)), Scalar(0);
expected.row(1) << Scalar(50 * std::cos(50.0L)), Scalar(0);
expected.row(2) << x[1], x[0];
expected.row(3) << Scalar(0), Scalar(std::exp(-2.0L));
const int count = diff.df(x, jac, error);
VERIFY_IS_EQUAL(count, diff.functor().evaluations);
VERIFY(count >= 8 && count <= 40 && count % 2 == 0);
VERIFY(jac.allFinite());
VERIFY(error.allFinite());
VERIFY((error.array() >= Scalar(0)).all());
// The initial stencil spans half a radian in the oscillatory component; extrapolation
// must remove its truncation error to reach a roundoff-scale bound.
VERIFY((jac - expected).template lpNorm<Infinity>() <=
Scalar(256) * NumTraits<Scalar>::epsilon() * expected.template lpNorm<Infinity>());
VERIFY_IS_EQUAL(x, original);
VERIFY_IS_EQUAL(diff.df(x, withoutError), count);
VERIFY_IS_EQUAL(jac, withoutError);
}
struct RiddersScalarFunctor {
using Scalar = double;
using InputType = Matrix<double, 1, 1>;
using ValueType = InputType;
using JacobianType = InputType;
static constexpr int InputsAtCompileTime = 1;
static constexpr int ValuesAtCompileTime = 1;
enum Function { Cubic, LargeCubic, Quintic, SquareRoot, NoisyExp, Identity, Invalid, InvalidRefinement };
Function function = Cubic;
mutable int evaluations = 0;
int values() const { return 1; }
int operator()(const InputType& x, ValueType& y) const {
++evaluations;
switch (function) {
case Cubic:
y[0] = x[0] * x[0] * x[0];
break;
case LargeCubic:
y[0] = 2e307 * x[0] * x[0] * x[0];
break;
case Quintic:
y[0] = x[0] * x[0] * x[0] * x[0] * x[0];
break;
case SquareRoot:
y[0] = std::sqrt(x[0]);
break;
case NoisyExp:
y[0] = double(float(std::exp(x[0])));
break;
case Identity:
y[0] = x[0];
break;
case Invalid:
y[0] = NumTraits<double>::quiet_NaN();
break;
case InvalidRefinement:
y[0] = evaluations <= 2 ? x[0] : NumTraits<double>::quiet_NaN();
break;
}
return 0;
}
};
void test_ridders_parameters() {
using DiffType = Ridders<RiddersScalarFunctor>;
DiffType diff;
DiffType::Parameters parameters;
parameters.initialStep = 0.25;
parameters.stepReduction = 4;
parameters.maxSteps = 2;
diff.setParameters(parameters);
RiddersScalarFunctor::InputType x, error;
RiddersScalarFunctor::JacobianType jac;
x[0] = 2;
VERIFY_IS_EQUAL(diff.df(x, jac, error), 4);
VERIFY_IS_EQUAL(jac[0], 12.0);
// The cubic's central differences are 12 + h^2, with h = 1/2 and 1/8.
VERIFY_IS_EQUAL(error[0], 0.25);
parameters.maxSteps = 10;
diff.setParameters(parameters);
VERIFY(diff.df(x, jac, error) < 20);
VERIFY_IS_EQUAL(jac[0], 12.0);
VERIFY_IS_EQUAL(error[0], 0.0);
parameters.initialStep = 0;
VERIFY_RAISES_ASSERT(diff.setParameters(parameters));
parameters.initialStep = 0.01;
parameters.stepReduction = 1;
VERIFY_RAISES_ASSERT(diff.setParameters(parameters));
parameters.stepReduction = 2;
parameters.maxSteps = 1;
VERIFY_RAISES_ASSERT(diff.setParameters(parameters));
parameters.maxSteps = 10;
parameters.initialStep = NumTraits<double>::infinity();
VERIFY_RAISES_ASSERT(diff.setParameters(parameters));
parameters.initialStep = 0.01;
parameters.stepReduction = NumTraits<double>::infinity();
VERIFY_RAISES_ASSERT(diff.setParameters(parameters));
parameters = DiffType::Parameters();
if (NumTraits<Index>::highest() / 2 < NumTraits<int>::highest()) {
parameters.maxSteps = static_cast<int>(NumTraits<Index>::highest() / 2) + 1;
VERIFY_RAISES_ASSERT(diff.setParameters(parameters));
--parameters.maxSteps;
} else {
parameters.maxSteps = NumTraits<int>::highest();
}
diff.setParameters(parameters);
}
void test_ridders_repeated_stencil() {
using DiffType = Ridders<RiddersScalarFunctor>;
for (int multiplier : {1, 16}) {
DiffType diff;
DiffType::Parameters parameters;
parameters.stepReduction = 1 + multiplier * NumTraits<double>::epsilon();
diff.setParameters(parameters);
RiddersScalarFunctor::InputType x, error;
RiddersScalarFunctor::JacobianType jac;
x[0] = 1;
VERIFY_IS_EQUAL(diff.df(x, jac, error), 2);
VERIFY_IS_EQUAL(diff.functor().evaluations, 2);
VERIFY((numext::isfinite)(jac[0]));
VERIFY(std::abs(jac[0] - 3.0001) <= 32 * NumTraits<double>::epsilon());
VERIFY((numext::isinf)(error[0]) && error[0] > 0);
}
}
void test_ridders_dimensions() {
Ridders<RiddersFunctor<double>> diff;
VectorXd x = VectorXd::Ones(2), error;
MatrixXd jac(3, 2);
VERIFY_RAISES_ASSERT(diff.df(x, jac));
jac.resize(4, 1);
VERIFY_RAISES_ASSERT(diff.df(x, jac, error));
jac.resize(4, 0);
VERIFY_RAISES_ASSERT(diff.df(x, jac));
VERIFY_IS_EQUAL(diff.functor().evaluations, 0);
}
template <typename Scalar_>
struct RiddersAffineFunctor {
using Scalar = Scalar_;
using InputType = Matrix<Scalar, 1, 1>;
using ValueType = Matrix<Scalar, Dynamic, 1>;
using JacobianType = Matrix<Scalar, Dynamic, 1>;
static constexpr int InputsAtCompileTime = 1;
static constexpr int ValuesAtCompileTime = Dynamic;
ValueType slopes, offsets;
Scalar center = Scalar(100);
Scalar inputScale = Scalar(1);
mutable int evaluations = 0;
int finiteEvaluations = 20;
Index values() const { return slopes.size(); }
int operator()(const InputType& x, ValueType& y) const {
++evaluations;
y = slopes * ((x[0] - center) * inputScale) + offsets;
if (evaluations > finiteEvaluations) y.setConstant(NumTraits<Scalar>::quiet_NaN());
return 0;
}
};
template <typename Scalar>
void test_ridders_central_overflow() {
using Functor = RiddersAffineFunctor<Scalar>;
using DiffType = Ridders<Functor>;
const Scalar large = NumTraits<Scalar>::highest() * Scalar(0.75);
// 32-bit ARM NEON flushes subnormal lanes; there, keep tiny and small * h for the steps h = 1, 1/2 normal.
constexpr bool kPacketsFlush = EIGEN_ARCH_ARM && internal::packet_traits<Scalar>::Vectorizable;
const Scalar small = Scalar(kPacketsFlush ? 2 : 1) * (std::numeric_limits<Scalar>::min)();
const Scalar tiny = kPacketsFlush ? small : (std::numeric_limits<Scalar>::denorm_min)();
for (Index count : {1, 7, 17}) {
Functor functor;
functor.slopes.resize(count);
functor.offsets.setZero(count);
for (Index i = 0; i < count; ++i) {
functor.slopes[i] = i % 3 == 0 ? large : (i % 3 == 1 ? Scalar(-8) : small);
if (i % 6 == 3) functor.slopes[i] = -large;
}
DiffType diff(functor);
typename Functor::InputType x, error;
typename Functor::JacobianType jac(count, 1);
x[0] = functor.center;
VERIFY_IS_EQUAL(diff.df(x, jac, error), 4);
VERIFY(jac.allFinite());
for (Index i = 0; i < count; ++i) VERIFY_IS_EQUAL(jac[i], functor.slopes[i]);
VERIFY_IS_EQUAL(error[0], Scalar(0));
// A failed refinement must retain finite entries, including subnormals unless packets flush them, from the
// initial stencil.
diff.functor().evaluations = 0;
diff.functor().finiteEvaluations = 2;
for (Index i = 2; i < count; i += 3) diff.functor().slopes[i] = tiny;
VERIFY_IS_EQUAL(diff.df(x, jac, error), 4);
VERIFY_IS_EQUAL(jac, diff.functor().slopes);
VERIFY((numext::isinf)(error[0]) && error[0] > Scalar(0));
}
Functor functor;
functor.slopes.setZero(1);
functor.offsets.setConstant(1, large);
DiffType diff(functor);
typename Functor::InputType x, error;
typename Functor::JacobianType jac(1, 1);
x[0] = Scalar(100);
VERIFY_IS_EQUAL(diff.df(x, jac, error), 4);
VERIFY_IS_EQUAL(jac[0], Scalar(0));
x[0] = Scalar(0);
diff.functor().center = Scalar(0);
typename DiffType::Parameters parameters = diff.parameters();
parameters.initialStep = (std::numeric_limits<Scalar>::min)();
diff.setParameters(parameters);
VERIFY_IS_EQUAL(diff.df(x, jac, error), 4);
VERIFY_IS_EQUAL(jac[0], Scalar(0));
// Division first cannot repair a genuinely overflowing derivative or nonfinite samples.
diff.functor().slopes[0] = NumTraits<Scalar>::highest();
diff.functor().offsets[0] = Scalar(0);
diff.functor().inputScale = Scalar(4);
parameters.initialStep = Scalar(0.25);
diff.setParameters(parameters);
VERIFY_IS_EQUAL(diff.df(x, jac, error), 2);
VERIFY((numext::isnan)(jac[0]));
VERIFY((numext::isinf)(error[0]) && error[0] > Scalar(0));
diff.functor().slopes[0] = NumTraits<Scalar>::infinity();
VERIFY_IS_EQUAL(diff.df(x, jac, error), 2);
VERIFY((numext::isnan)(jac[0]));
VERIFY((numext::isinf)(error[0]) && error[0] > Scalar(0));
parameters.initialStep = Scalar(2);
diff.setParameters(parameters);
VERIFY_IS_EQUAL(diff.df(x, jac, error), 2);
VERIFY((numext::isnan)(jac[0]));
}
void test_ridders_domain_and_noise() {
using DiffType = Ridders<RiddersScalarFunctor>;
DiffType diff;
RiddersScalarFunctor::InputType x, error;
RiddersScalarFunctor::JacobianType jac;
diff.functor().function = RiddersScalarFunctor::SquareRoot;
x[0] = 0.005;
DiffType::Parameters parameters;
parameters.initialStep = 0.01 * x[0];
diff.setParameters(parameters);
VERIFY(diff.df(x, jac, error) <= 20);
const double expected = double(0.5L / std::sqrt(static_cast<long double>(x[0])));
VERIFY(std::abs(jac[0] - expected) <= 256 * NumTraits<double>::epsilon() * expected);
VERIFY((numext::isfinite)(error[0]));
diff.functor().function = RiddersScalarFunctor::NoisyExp;
x[0] = 1;
parameters.initialStep = 0.01;
diff.setParameters(parameters);
VERIFY(diff.df(x, jac, error) < 20);
VERIFY(std::abs(jac[0] - std::exp(1.0)) <= 256 * double(NumTraits<float>::epsilon()) * std::exp(1.0));
VERIFY((numext::isfinite)(error[0]));
}
void test_ridders_richardson_overflow() {
Ridders<RiddersScalarFunctor> diff;
diff.functor().function = RiddersScalarFunctor::LargeCubic;
RiddersScalarFunctor::InputType x, error;
RiddersScalarFunctor::JacobianType jac;
x[0] = 1;
const int evaluations = diff.df(x, jac, error);
VERIFY_IS_EQUAL(evaluations, diff.functor().evaluations);
VERIFY(evaluations > 4 && evaluations <= 20);
VERIFY((numext::isfinite)(jac[0]));
VERIFY((numext::isfinite)(error[0]));
VERIFY(error[0] >= 0);
// The cubic's truncation error cancels on extrapolation; divide before comparing near the range limit.
VERIFY(std::abs(jac[0] / 6e307 - 1) <= 256 * NumTraits<double>::epsilon());
}
void test_ridders_stencil() {
using DiffType = Ridders<RiddersScalarFunctor>;
DiffType diff;
diff.functor().function = RiddersScalarFunctor::Quintic;
DiffType::Parameters parameters;
parameters.initialStep = 0.25;
parameters.maxSteps = 3;
diff.setParameters(parameters);
RiddersScalarFunctor::InputType x, error, value;
RiddersScalarFunctor::JacobianType jac;
x[0] = 1;
VERIFY_IS_EQUAL(diff.df(x, jac, error), 6);
VERIFY_IS_EQUAL(jac[0], 5.0);
// At depth three the extrapolation is the degree-five-exact stencil on these six nodes.
const Stencil<1, double, 6> stencil({-0.25, 0.25, -0.125, 0.125, -0.0625, 0.0625});
double expected = 0;
for (Index i = 0; i < 6; ++i) {
RiddersScalarFunctor::InputType point;
point[0] = x[0] + stencil.point(i);
diff(point, value);
expected += stencil.weight(i) * value[0];
}
VERIFY(std::abs(jac[0] - expected) <= 32 * NumTraits<double>::epsilon() * std::abs(jac[0]));
}
// Tests construction from a functor without a default constructor and optional inputs() forwarding.
struct OwnedRiddersFunctor : RiddersScalarFunctor {
explicit OwnedRiddersFunctor(Function selected) { function = selected; }
int inputs() const { return 1; }
};
void test_ridders_composition() {
using Derivative = Ridders<OwnedRiddersFunctor>;
static_assert(!std::is_base_of<OwnedRiddersFunctor, Derivative>::value, "Ridders owns its functor by composition");
OwnedRiddersFunctor functor(RiddersScalarFunctor::Cubic);
Derivative::Parameters parameters;
parameters.initialStep = 0.25;
parameters.maxSteps = 2;
Derivative derivative(functor, parameters);
VERIFY_IS_EQUAL(derivative.inputs(), 1);
VERIFY_IS_EQUAL(derivative.values(), 1);
VERIFY_IS_EQUAL(derivative.parameters().maxSteps, 2);
const int* inputs = &Derivative::InputsAtCompileTime;
const int* values = &Derivative::ValuesAtCompileTime;
VERIFY_IS_EQUAL(*inputs, 1);
VERIFY_IS_EQUAL(*values, 1);
OwnedRiddersFunctor::InputType x, y, error;
OwnedRiddersFunctor::JacobianType jac;
x[0] = 2;
VERIFY_IS_EQUAL(derivative(x, y), 0);
VERIFY_IS_EQUAL(y[0], 8.0);
VERIFY_IS_EQUAL(derivative.df(x, jac, error), 4);
VERIFY_IS_EQUAL(jac[0], 12.0);
VERIFY_IS_EQUAL(functor.evaluations, 0);
const Derivative& constDerivative = derivative;
VERIFY_IS_EQUAL(constDerivative.functor().evaluations, 5);
}
struct LinearRiddersFunctor : RiddersFunctor<double, 7, 7> {
int values() const { return 7; }
int operator()(const InputType& x, ValueType& y) const {
y = x * 8;
return 0;
}
};
void test_ridders_step_alignment() {
Ridders<LinearRiddersFunctor> derivative;
LinearRiddersFunctor::InputType x;
x << 0.0, 0.082, 1.13, -2350.7, 7.7e8 + 0.3, -1.0 - std::ldexp(1.0, -27), -1.0;
LinearRiddersFunctor::JacobianType jac;
derivative.df(x, jac);
VERIFY_IS_EQUAL(jac, LinearRiddersFunctor::JacobianType::Identity() * 8);
}
void test_ridders_fallback() {
using DiffType = Ridders<RiddersScalarFunctor>;
DiffType diff;
RiddersScalarFunctor::InputType x, error;
RiddersScalarFunctor::JacobianType jac;
x[0] = 1;
diff.functor().function = RiddersScalarFunctor::Invalid;
VERIFY_IS_EQUAL(diff.df(x, jac, error), 2);
VERIFY((numext::isnan)(jac[0]));
VERIFY((numext::isinf)(error[0]) && error[0] > 0);
diff.functor().function = RiddersScalarFunctor::InvalidRefinement;
diff.functor().evaluations = 0;
VERIFY_IS_EQUAL(diff.df(x, jac, error), 4);
VERIFY_IS_EQUAL(jac[0], 1.0);
VERIFY((numext::isinf)(error[0]));
diff.functor().function = RiddersScalarFunctor::Identity;
DiffType::Parameters parameters;
parameters.initialStep = NumTraits<double>::epsilon() / 16;
diff.setParameters(parameters);
VERIFY_IS_EQUAL(diff.df(x, jac, error), 0);
VERIFY((numext::isnan)(jac[0]));
VERIFY((numext::isinf)(error[0]));
parameters.initialStep = NumTraits<double>::epsilon();
diff.setParameters(parameters);
VERIFY_IS_EQUAL(diff.df(x, jac, error), 4);
VERIFY_IS_EQUAL(jac[0], 1.0);
VERIFY_IS_EQUAL(error[0], 0.0);
}
struct RiddersLevenbergMarquardtFunctor : DenseFunctor<double> {
RiddersLevenbergMarquardtFunctor() : DenseFunctor<double>(1, 1) {}
int operator()(const InputType& x, ValueType& y) const {
y[0] = x[0] * x[0] - 2;
return 0;
}
};
void test_ridders_levenberg_marquardt() {
using Functor = RiddersLevenbergMarquardtFunctor;
using Derivative = Ridders<Functor>;
static_assert(std::is_same<typename Derivative::QRSolver, typename Functor::QRSolver>::value,
"Ridders must preserve the functor's QRSolver type");
Derivative derivative;
LevenbergMarquardt<Derivative> solver(derivative);
VectorXd x = VectorXd::Ones(1);
const LevenbergMarquardtSpace::Status status = solver.minimize(x);
VERIFY(status >= LevenbergMarquardtSpace::RelativeReductionTooSmall &&
status <= LevenbergMarquardtSpace::CosinusTooSmall);
VERIFY(std::abs(x[0] - std::sqrt(2.0)) <= 256 * NumTraits<double>::epsilon());
}
struct EmptyRiddersFunctor : RiddersFunctor<double> {
int values() const { return 0; }
};
void test_ridders_empty() {
Ridders<EmptyRiddersFunctor> diff;
VectorXd x = VectorXd::Ones(2), error;
MatrixXd jac(0, 2);
VERIFY_IS_EQUAL(diff.df(x, jac, error), 0);
VERIFY_IS_EQUAL(error, VectorXd::Zero(2));
VERIFY_IS_EQUAL(diff.functor().evaluations, 0);
Ridders<RiddersFunctor<double>> noInputs;
x.resize(0);
jac.resize(4, 0);
VERIFY_IS_EQUAL(noInputs.df(x, jac, error), 0);
VERIFY_IS_EQUAL(error.size(), 0);
VERIFY_IS_EQUAL(noInputs.functor().evaluations, 0);
}
EIGEN_DECLARE_TEST(ridders) {
CALL_SUBTEST((test_ridders_jacobian<float, 2, 4, ColMajor>()));
CALL_SUBTEST((test_ridders_jacobian<double, 2, 4, RowMajor>()));
CALL_SUBTEST((test_ridders_jacobian<double, Dynamic, Dynamic, ColMajor>()));
CALL_SUBTEST((test_ridders_jacobian<long double, Dynamic, Dynamic, RowMajor>()));
CALL_SUBTEST(test_ridders_parameters());
CALL_SUBTEST(test_ridders_repeated_stencil());
CALL_SUBTEST(test_ridders_dimensions());
CALL_SUBTEST(test_ridders_central_overflow<float>());
CALL_SUBTEST(test_ridders_central_overflow<double>());
CALL_SUBTEST(test_ridders_domain_and_noise());
CALL_SUBTEST(test_ridders_richardson_overflow());
CALL_SUBTEST(test_ridders_stencil());
CALL_SUBTEST(test_ridders_composition());
CALL_SUBTEST(test_ridders_step_alignment());
CALL_SUBTEST(test_ridders_fallback());
CALL_SUBTEST(test_ridders_levenberg_marquardt());
CALL_SUBTEST(test_ridders_empty());
}