Molassembler  1.1.0
Molecule graph and conformer library
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
NewtonRaphson.h
Go to the documentation of this file.
1 
8 #ifndef INCLUDE_TEMPLE_NEWTON_RAPHSON_H
9 #define INCLUDE_TEMPLE_NEWTON_RAPHSON_H
10 
11 #include <Eigen/Core>
12 #include <Eigen/Eigenvalues>
13 #include <unsupported/Eigen/NumericalDiff>
14 #include "Molassembler/Temple/STL17.h"
15 #include <iostream>
16 
17 namespace Scine {
18 namespace Molassembler {
19 namespace Temple {
20 
30 template<typename FloatType = double>
31 struct NewtonRaphson {
32  using MatrixType = Eigen::Matrix<FloatType, Eigen::Dynamic, Eigen::Dynamic>;
33  using VectorType = Eigen::Matrix<FloatType, Eigen::Dynamic, 1>;
34 
38  unsigned iterations;
40  FloatType value;
42  VectorType gradient;
43  };
44 
45  template<
46  typename UpdateFunction,
47  typename Checker
48  > OptimizationReturnType minimize(
49  Eigen::Ref<VectorType> parameters,
50  UpdateFunction&& function,
51  Checker&& check
52  ) {
53  const unsigned P = parameters.size();
54  FloatType value;
55  VectorType gradients = Eigen::VectorXd::Zero(P);
56  MatrixType hessian (P, P);
57  hessian.setZero();
58 
59  VectorType numericalGradient(P);
60  {
61  constexpr FloatType h = 1e-4;
62  FloatType a;
63  FloatType b;
64  VectorType diffParameters = parameters;
65  for(unsigned i = 0; i < P; ++i) {
66  diffParameters(i) += h;
67  function(diffParameters, b, gradients, hessian);
68  diffParameters(i) -= 2 * h;
69  function(diffParameters, a, gradients, hessian);
70  diffParameters(i) = parameters(i);
71  numericalGradient(i) = (b - a) / (2 * h);
72  }
73  }
74 
75  function(parameters, value, gradients, hessian);
76 
77  if(numericalGradient.norm() > 1e-4) {
78  if(!numericalGradient.isApprox(gradients, 1e-8)) {
79  std::cout << "MISMATCH: Numerical gradient is " << numericalGradient.transpose() << "\nfunction gradient is : " << gradients.transpose() << "\n";
80  } else {
81  std::cout << "match: norm is " << gradients.norm() << " and diff norm is " << (numericalGradient - gradients).norm() << "\n";
82  }
83  }
84 
85  MatrixType numericalHessian(P, P);
86  {
87  constexpr FloatType h = 1e-4;
88  FloatType a;
89  FloatType b;
90  FloatType c;
91  FloatType d;
92  VectorType diffParameters = parameters;
93  for(unsigned i = 0; i < P; ++i) {
94  // Diagonal second derivative from forward and backward derivative
95  function(diffParameters, b, gradients, hessian);
96  diffParameters(i) += h;
97  function(diffParameters, c, gradients, hessian);
98  diffParameters(i) -= 2 * h;
99  function(diffParameters, a, gradients, hessian);
100  diffParameters(i) = parameters(i);
101  numericalHessian(i, i) = (a - 2 * b + c) / (h * h);
102 
103  // Cross terms from two central derivatives
104  for(unsigned j = i + 1; j < P; ++j) {
105  // (i) + h, (j) + h -> a
106  diffParameters(i) += h;
107  diffParameters(j) += h;
108  function(diffParameters, a, gradients, hessian);
109  // (i) + h, (j) - h - > b
110  diffParameters(j) -= 2 * h;
111  function(diffParameters, b, gradients, hessian);
112  // (i) - h, (j) - h -> d
113  diffParameters(i) -= 2 * h;
114  function(diffParameters, d, gradients, hessian);
115  // (i) - h, (j) + h -> c
116  diffParameters(j) += 2 * h;
117  function(diffParameters, c, gradients, hessian);
118 
119  numericalHessian(i, j) = (a - b - c + d) / (4 * h * h);
120  numericalHessian(j, i) = numericalHessian(i, j);
121  }
122  }
123  }
124 
125  function(parameters, value, gradients, hessian);
126 
127  if(!numericalHessian.isApprox(hessian), 1e-2) {
128  std::cout << "MISMATCH: Hessian is\n" << hessian << "\nNumerical hessian is\n" << numericalHessian << "\n";
129  } else {
130  std::cout << "Hessian matches\n";
131  }
132 
133  unsigned iteration = 0;
134  while(
135  check.shouldContinue(
136  iteration,
137  Stl17::as_const(value),
138  Stl17::as_const(gradients)
139  )
140  ) {
141  // Solve HΔx = g, then apply x_(n+1) = x_n - Δx
142  Eigen::JacobiSVD<MatrixType> svd(
143  hessian,
144  Eigen::ComputeFullV | Eigen::ComputeFullU
145  );
146  svd.setThreshold(svdThreshold);
147  VectorType steps = svd.solve(gradients);
148 
149  // Take a step
150  FloatType rms = std::sqrt(steps.squaredNorm() / P);
151  if (rms > trustRadius) {
152  steps *= trustRadius / rms;
153  }
154  parameters -= steps;
155 
156  function(parameters, value, gradients, hessian);
157  ++iteration;
158  }
159 
160  return {
161  iteration,
162  value,
163  std::move(gradients)
164  };
165  }
166 
168  FloatType svdThreshold = 1.0e-12;
170  FloatType trustRadius = 0.5;
171 };
172 
173 } // namespace Temple
174 } // namespace Molassembler
175 } // namespace Scine
176 
177 #endif
FloatType value
Final function value.
Definition: NewtonRaphson.h:40
unsigned iterations
Number of iterations.
Definition: NewtonRaphson.h:38
Type returned from an optimization.
Definition: NewtonRaphson.h:36
VectorType gradient
Final gradient.
Definition: NewtonRaphson.h:42
FloatType trustRadius
The maximum RMS of a taken step.
Definition: NewtonRaphson.h:170
FloatType svdThreshold
The SVD threshold for the decomposition of the Hessian.
Definition: NewtonRaphson.h:168
A very basic newton-raphson minimizer.
Definition: NewtonRaphson.h:31