Gemmi C++ API
Loading...
Searching...
No Matches
levmar.hpp
Go to the documentation of this file.
1// Copyright 2020 Global Phasing Ltd.
2//
3// Least-squares fitting - Levenberg-Marquardt method.
4//
5// Based on the code from fityk (but here it's under MPL 2.0).
6
7#ifndef GEMMI_LEVMAR_HPP_
8#define GEMMI_LEVMAR_HPP_
9
10#include <cassert>
11#include <cmath> // for fabs
12#include <algorithm> // for min
13#include <vector>
14#include "fail.hpp" // for fail
15#include "math.hpp" // for sq
16
17//#define GEMMI_DEBUG_LEVMAR
18
19namespace gemmi {
20
29inline void jordan_solve(double* a, double* b, int n) {
30 for (int i = 0; i < n; i++) {
31 // looking for a pivot element
32 int maxnr = -1;
33 double amax = 0;
34 for (int j = i; j < n; j++) {
35 double aji = std::fabs(a[n * j + i]);
36 if (aji > amax) {
37 maxnr = j;
38 amax = aji;
39 }
40 }
41 // handle singular matrix
42 if (maxnr == -1) {
43 // i-th column has only zeros.
44 // If it's the same about i-th row, and b[i]==0, let x[i]==0.
45 for (int j = i; j < n; j++)
46 if (a[n * i + j] != 0. || b[i] != 0.)
47 fail("Trying to reverse singular matrix. Column ", std::to_string(i), " is zeroed.");
48 continue; // x[i]=b[i], b[i]==0
49 }
50 // interchanging rows
51 if (maxnr != i) {
52 for (int j = i; j < n; j++)
53 std::swap(a[n * maxnr + j], a[n * i + j]);
54 std::swap(b[i], b[maxnr]);
55 }
56 // divide by a_ii -- to get a_ii=1
57 double c = 1.0 / a[i * n + i];
58 for (int j = i; j < n; j++)
59 a[i * n + j] *= c;
60 b[i] *= c;
61 // subtract -- to zero all remaining elements of this row
62 for (int k = 0; k < n; k++)
63 if (k != i) {
64 double d = a[k * n + i];
65 for (int j = i; j < n; j++)
66 a[k * n + j] -= a[i * n + j] * d;
67 b[k] -= b[i] * d;
68 }
69 }
70}
71
72inline void jordan_solve(std::vector<double>& a, std::vector<double>& b) {
73 assert(a.size() == b.size() * b.size());
74 jordan_solve(a.data(), b.data(), (int)b.size());
75}
76
77inline void print_parameters(const std::string& name, std::vector<double> &a) {
78 fprintf(stderr, " %s:", name.c_str());
79 for (double& x : a)
80 fprintf(stderr, " %g", x);
81 fprintf(stderr, "\n");
82}
83
84template<typename Target>
85double compute_wssr(const Target& target) {
86 long double wssr = 0; // long double here notably increases the accuracy
87 for (const auto& p : target.points)
88 wssr += sq(p.get_weight() * (p.get_y() - target.compute_value(p)));
89 return (double) wssr;
90}
91
92template<typename Target>
93double compute_gradients(const Target& target, unsigned n, double* grad) {
94 double wssr = 0;
95 for (unsigned i = 0; i < n; ++i)
96 grad[i] = 0;
97 std::vector<double> dy_da(n);
98 for (const auto& p : target.points) {
99 double y = target.compute_value_and_derivatives(p, dy_da);
100 double dy = p.get_weight() * (p.get_y() - y);
101 wssr += sq(dy);
102 for (unsigned i = 0; i < n; ++i)
103 grad[i] += -2 * dy * dy_da[i];
104 }
105#if 0
106 // calculate numerical derivatives to check analytical formulas
107 fprintf(stderr, ">> y=%g\n", wssr);
108 std::vector<double> x = target.get_parameters();
109 assert(x.size() == n);
110 for (unsigned i = 0; i < n; ++i) {
111 double x_orig = x[i];
112 double h = std::max(std::fabs(x[i]), 1e-6) * 1e-3;
113 x[i] = x_orig - h;
114 const_cast<Target&>(target).set_parameters(x);
115 double y_left = compute_wssr(target);
116 x[i] = x_orig + h;
117 const_cast<Target&>(target).set_parameters(x);
118 double y_right = compute_wssr(target);
119 double numeric = (y_right - y_left) / (2 * h);
120 x[i] = x_orig;
121 double m = std::max(std::fabs(grad[i]), std::fabs(numeric));
122 if (m > 1e-3 && std::fabs(grad[i] - numeric) > 0.02 * m)
123 fprintf(stderr, "!! grad[%u]: %g vs %g (value: %g)\n", i, grad[i], numeric, x[i]);
124 }
125 const_cast<Target&>(target).set_parameters(x);
126#endif
127 return wssr;
128}
129
130// alpha and beta are matrices outputted for the Levenberg-Marquardt algorithm.
131// Ignoring weights, alpha is a squared Jacobian J^T J (which approximates the
132// Hessian, as discussed in Numerical Recipes, chapter 15.5), not "damped" yet.
133// The return value is the same as from compute_wssr().
134template<typename Target>
136 std::vector<double>& alpha,
137 std::vector<double>& beta) {
138 assert(!beta.empty());
139 assert(alpha.size() == beta.size() * beta.size());
140 long double wssr = 0; // long double here notably increases the accuracy
141 size_t na = beta.size();
142 std::fill(alpha.begin(), alpha.end(), 0.0);
143 std::fill(beta.begin(), beta.end(), 0.0);
144 std::vector<double> dy_da(na);
145 for (const auto& p : target.points) {
146 double y = target.compute_value_and_derivatives(p, dy_da);
147 double weight = p.get_weight();
148 double dy_sig = weight * (p.get_y() - y);
149 for (size_t j = 0; j != na; ++j) {
150 if (dy_da[j] != 0) {
151 dy_da[j] *= weight;
152 for (size_t k = j+1; k-- != 0;)
153 alpha[na * j + k] += dy_da[j] * dy_da[k];
154 beta[j] += dy_sig * dy_da[j];
155 }
156 }
157 wssr += sq(dy_sig);
158 }
159
160 // Only half of the alpha matrix was filled above. Fill the rest.
161 for (size_t j = 1; j < na; j++)
162 for (size_t k = 0; k < j; k++)
163 alpha[na * k + j] = alpha[na * j + k];
164 return (double) wssr;
165}
166
167struct LevMar {
168 // termination criteria
169 int eval_limit = 100;
170 double lambda_limit = 1e+15;
171 double stop_rel_change = 1e-5;
172
173 // adjustable parameters (normally the default values work fine)
174 double lambda_up_factor = 10;
175 double lambda_down_factor = 0.1;
176 double lambda_start = 0.001;
177
178 // values set in fit() that can be inspected later
180 int eval_count = 0; // number of function evaluations
181
182 // arrays used during refinement
183 std::vector<double> alpha; // matrix
184 std::vector<double> beta; // vector
185 std::vector<double> temp_alpha, temp_beta; // working arrays
186
187
188 template<typename Target>
189 double fit(Target& target) {
190 std::vector<double> initial_a = target.get_parameters();
191#ifdef GEMMI_DEBUG_LEVMAR
193#endif
194 std::vector<double> best_a = initial_a;
195 size_t na = initial_a.size();
196
197 double lambda = lambda_start;
198 alpha.resize(na * na);
199 beta.resize(na);
200
202 double wssr = initial_wssr;
203
204 int small_change_counter = 0;
205 eval_count = 1; // number of function evaluations so far
206 for (int iter = 0; ; iter++) {
207 if (eval_limit > 0 && eval_count >= eval_limit)
208 break;
209
210 // prepare next parameters -> temp_beta
212 // Using '*=' not '+=' below applies the dampling factor as:
213 // J^T J + lambda * diag(J^T J); not ... + lambda * I.
214 for (size_t j = 0; j < na; j++)
215 temp_alpha[na * j + j] *= (1.0 + lambda);
216 temp_beta = beta;
217
218 // Matrix solution (Ax=b) temp_alpha * da == temp_beta
220
221 for (size_t i = 0; i < na; i++)
222 // put new a[] into temp_beta[]
223 temp_beta[i] += best_a[i];
224
225 target.set_parameters(temp_beta);
226 double new_wssr = compute_wssr(target);
227 ++eval_count;
228#ifdef GEMMI_DEBUG_LEVMAR
229 fprintf(stderr, " #%d WSSR=%.8g %+g%% (%+.4g%%) lambda=%g\n",
230 iter, new_wssr, 100. * (new_wssr / initial_wssr - 1.),
231 100. * (new_wssr / wssr - 1.), lambda);
232 if (new_wssr < wssr)
234#else
235 (void) iter;
236#endif
237 if (new_wssr < wssr) {
238 double rel_change = (wssr - new_wssr) / wssr;
239 wssr = new_wssr;
241
242 if (wssr == 0)
243 break;
244 // termination criterion: negligible change of wssr
246 if (++small_change_counter >= 2)
247 break;
248 } else {
250 }
252 ++eval_count;
254 } else { // worse fitting
255 if (lambda > lambda_limit) // termination criterion: large lambda
256 break;
258 }
259 }
260
261 target.set_parameters(wssr < initial_wssr ? best_a : initial_a);
262 return wssr;
263 }
264};
265
266} // namespace gemmi
267#endif
fail(), unreachable() and __declspec/__attribute__ macros
Math utilities. 3D linear algebra.
void print_parameters(const std::string &name, std::vector< double > &a)
Definition levmar.hpp:77
double compute_gradients(const Target &target, unsigned n, double *grad)
Definition levmar.hpp:93
double compute_lm_matrices(const Target &target, std::vector< double > &alpha, std::vector< double > &beta)
Definition levmar.hpp:135
constexpr float sq(float x)
Definition math.hpp:34
double compute_wssr(const Target &target)
Definition levmar.hpp:85
void fail(const std::string &msg)
Definition fail.hpp:59
void jordan_solve(double *a, double *b, int n)
This function solves a set of linear algebraic equations using Gauss-Jordan elimination with partial ...
Definition levmar.hpp:29
std::vector< double > alpha
Definition levmar.hpp:183
double fit(Target &target)
Definition levmar.hpp:189
double lambda_down_factor
Definition levmar.hpp:175
double initial_wssr
Definition levmar.hpp:179
double lambda_up_factor
Definition levmar.hpp:174
double stop_rel_change
Definition levmar.hpp:171
std::vector< double > beta
Definition levmar.hpp:184
double lambda_start
Definition levmar.hpp:176
std::vector< double > temp_alpha
Definition levmar.hpp:185
std::vector< double > temp_beta
Definition levmar.hpp:185
double lambda_limit
Definition levmar.hpp:170