8 #ifndef INCLUDE_TEMPLE_OPTIMIZATION_TRUST_REGION_NEWTON_H
9 #define INCLUDE_TEMPLE_OPTIMIZATION_TRUST_REGION_NEWTON_H
13 #include "boost/math/tools/roots.hpp"
14 #include <Eigen/Eigenvalues>
19 namespace Molassembler {
23 template<
typename Derived>
24 auto resolve(
const Eigen::MatrixBase<Derived>& matrix) ->
typename Eigen::MatrixBase<Derived>::Scalar {
25 assert(matrix.rows() == 1 && matrix.cols() == 1);
29 template<
typename FloatType>
30 struct TROFloatingPointTolerances {};
33 struct TROFloatingPointTolerances<double> {
34 static constexpr
unsigned rootBitAccuracy = 20;
38 struct TROFloatingPointTolerances<float> {
39 static constexpr
unsigned rootBitAccuracy = 10;
49 template<
typename FloatType =
double>
51 using MatrixType = Eigen::Matrix<FloatType, Eigen::Dynamic, Eigen::Dynamic>;
52 using VectorType = Eigen::Matrix<FloatType, Eigen::Dynamic, 1>;
65 typename UpdateFunction,
68 Eigen::Ref<VectorType> parameters,
69 UpdateFunction&&
function,
73 constexpr FloatType trustRadiusLimit = 4.0;
74 static_assert(trustRadiusLimit > 0,
"Trust radius is not positive!");
77 constexpr FloatType agreementContractionUpperBound = 0.25;
78 constexpr FloatType agreementExpansionLowerBound = 0.75;
81 constexpr FloatType agreementEta = 0.125;
83 0.0 <= agreementEta && agreementEta < 0.25,
84 "Eta not within bounds"
89 FloatType trustRadius = 1.0;
90 FloatType modelAgreement = 0;
91 unsigned iterations = 0;
93 step.initialize(parameters,
function, trustRadius);
99 std::as_const(step.values.current),
100 std::as_const(step.gradients.current)
104 modelAgreement = step.modelAgreement();
107 if(modelAgreement < agreementContractionUpperBound) {
109 trustRadius = agreementContractionUpperBound * trustRadius;
111 modelAgreement > agreementExpansionLowerBound
112 && std::fabs(step.direction.norm() - trustRadius) < 1e-5
115 trustRadius = std::min(2 * trustRadius, trustRadiusLimit);
119 if(modelAgreement > agreementEta) {
121 step.accept(
function, trustRadius);
124 step.prepare(
function, trustRadius);
131 std::move(step.gradients.current)
149 VectorType direction;
151 template<
typename UpdateFunction>
153 Eigen::Ref<VectorType> initialParameters,
154 UpdateFunction&&
function,
155 const FloatType trustRadius
157 parameters.current = std::move(initialParameters);
158 const unsigned P = parameters.current.size();
159 parameters.proposed.resize(P);
160 gradients.current.resize(P);
161 gradients.proposed.resize(P);
162 hessians.current.resize(P, P);
163 hessians.proposed.resize(P, P);
165 function(parameters.current, values.current, gradients.current, hessians.current);
166 prepare(
function, trustRadius);
183 const FloatType trustRadius,
187 using Vector2Type = Eigen::Matrix<FloatType, 2, 1>;
188 using Matrix2Type = Eigen::Matrix<FloatType, 2, 2>;
191 const MatrixType Binverse = B.inverse();
193 const Vector2Type gTilde {g.squaredNorm(), g.dot(Binverse * g)};
195 BTilde << g.dot(B * g), gTilde(0),
196 gTilde(0), gTilde(1);
198 const Vector2Type uStar = - BTilde.inverse() * gTilde;
201 BBar << gTilde(0), gTilde(1),
202 gTilde(1), (Binverse * g).squaredNorm();
205 const FloatType J = FloatType {0.5} * uStar.transpose() * BBar * uStar;
206 const FloatType trustRadiusSquare = trustRadius * trustRadius;
208 if(J <= trustRadiusSquare) {
209 return uStar(0) * g + uStar(1) * Binverse * g;
213 boost::uintmax_t iterations = 1000;
214 auto root_result = boost::math::tools::toms748_solve(
215 [&](
const FloatType lambda) -> FloatType {
216 const Vector2Type intermediate = (BTilde + lambda * BBar).inverse() * gTilde;
217 return FloatType {0.5} * intermediate.transpose() * BBar * intermediate - trustRadiusSquare;
219 std::nextafter(FloatType {0.0}, FloatType {1.0}),
221 boost::math::tools::eps_tolerance<FloatType> {
222 Detail::TROFloatingPointTolerances<FloatType>::rootBitAccuracy
227 if(iterations >= 1000) {
228 throw std::logic_error(
"Could not find lambda to satisfy equation");
231 const FloatType lambda = (root_result.first + root_result.second) / 2;
234 const Vector2Type uMin = -(BTilde + lambda * BBar).inverse() * gTilde;
235 return uMin(0) * g + uMin(1) * Binverse * g;
238 VectorType determineDirection(
const FloatType trustRadius)
const {
249 VectorType eigenvalues = hessians.current.template selfadjointView<Eigen::Lower>().eigenvalues();
254 if((eigenvalues.array() < FloatType {0.0}).any()) {
256 const FloatType mostNegativeEigenvalue = eigenvalues(0);
261 const FloatType alpha = FloatType {-1.5} * mostNegativeEigenvalue;
262 const unsigned P = parameters.current.size();
263 const Eigen::MatrixXd modifiedHessian = hessians.current + alpha * Eigen::MatrixXd::Identity(P, P);
278 const FloatType gradientNorm = gradients.current.norm();
279 const FloatType
tau = [&]() -> FloatType {
280 const FloatType cauchyIntermediate = gradients.current.transpose() * hessians.current * gradients.current;
281 if(cauchyIntermediate <= 0) {
286 std::pow(gradientNorm, 3) / (trustRadius * cauchyIntermediate),
291 return - (tau * trustRadius / gradientNorm) * gradients.current;
294 template<
typename UpdateFunction>
295 void prepare(UpdateFunction&&
function,
const FloatType trustRadius) {
296 direction = determineDirection(trustRadius);
297 parameters.proposed.noalias() = parameters.current + direction;
298 function(parameters.proposed, values.proposed, gradients.proposed, hessians.proposed);
301 FloatType modelAgreement()
const {
302 const FloatType predictedValue = (
304 + Detail::resolve(gradients.current.transpose() * direction)
305 + Detail::resolve(FloatType {0.5} * direction.transpose() * hessians.current * direction)
309 (values.current - values.proposed)
310 / (values.current - predictedValue)
314 template<
typename UpdateFunction>
315 void accept(UpdateFunction&&
function,
const FloatType trustRadius) {
316 parameters.propagate();
318 gradients.propagate();
319 hessians.propagate();
321 prepare(
function, trustRadius);
Sylvester's criterion for positive definiteness and positive semi-definiteness.
VectorType subspaceMinimize(const FloatType trustRadius, const VectorType &g, const MatrixType &B) const
Find the minimizer of the trust region subspace.
Definition: TrustRegion.h:182
unsigned iterations
Number of iterations.
Definition: TrustRegion.h:57
Trust region Newton-Raphson minimizer with 2D subspace minimization.
Definition: TrustRegion.h:50
Type returned from an optimization.
Definition: TrustRegion.h:55
bool positiveDefinite(const Eigen::MatrixBase< Derived > &matrix)
Determine whether a matrix is positive definite.
Definition: SylvestersCriterion.h:26
FloatType value
Final function value.
Definition: TrustRegion.h:59
Data types common to optimizers.
VectorType gradient
Final gradient.
Definition: TrustRegion.h:61
double tau(const std::vector< double > &angles)
Calculates the tau value for four and five angle symmetries.
Definition: TauCriteria.h:63
Stores all parameter, value, gradient and hessian data of two points.
Definition: TrustRegion.h:143