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
16//#define GEMMI_DEBUG_LEVMAR
17
18namespace gemmi {
19
28inline void jordan_solve(std::vector<double>& a, std::vector<double>& b) {
29 assert(a.size() == b.size() * b.size());
30 int n = (int) b.size();
31 for (int i = 0; i < n; i++) {
32 // looking for a pivot element
33 int maxnr = -1;
34 double amax = 0;
35 for (int j = i; j < n; j++) {
36 double aji = std::fabs(a[n * j + i]);
37 if (aji > amax) {
38 maxnr = j;
39 amax = aji;
40 }
41 }
42 // handle singular matrix
43 if (maxnr == -1) {
44 // i-th column has only zeros.
45 // If it's the same about i-th row, and b[i]==0, let x[i]==0.
46 for (int j = i; j < n; j++)
47 if (a[n * i + j] != 0. || b[i] != 0.)
48 fail("Trying to reverse singular matrix. Column ", std::to_string(i), " is zeroed.");
49 continue; // x[i]=b[i], b[i]==0
50 }
51 // interchanging rows
52 if (maxnr != i) {
53 for (int j = i; j < n; j++)
54 std::swap(a[n * maxnr + j], a[n * i + j]);
55 std::swap(b[i], b[maxnr]);
56 }
57 // divide by a_ii -- to get a_ii=1
58 double c = 1.0 / a[i * n + i];
59 for (int j = i; j < n; j++)
60 a[i * n + j] *= c;
61 b[i] *= c;
62 // subtract -- to zero all remaining elements of this row
63 for (int k = 0; k < n; k++)
64 if (k != i) {
65 double d = a[k * n + i];
66 for (int j = i; j < n; j++)
67 a[k * n + j] -= a[i * n + j] * d;
68 b[k] -= b[i] * d;
69 }
70 }
71}
72
73
74#ifdef GEMMI_DEBUG_LEVMAR
75inline void debug_print(const std::string& name, std::vector<double> &a) {
76 fprintf(stderr, " %s:", name.c_str());
77 for (double& x : a)
78 fprintf(stderr, " %g", x);
79 fprintf(stderr, "\n");
80}
81#else
82inline void debug_print(const std::string&, std::vector<double>&) {}
83#endif
84
85struct LevMar {
86 // termination criteria
87 int eval_limit = 100;
88 double lambda_limit = 1e+15;
89 double stop_rel_change = 1e-5;
90
91 // adjustable parameters (normally the default values work fine)
92 double lambda_up_factor = 10;
93 double lambda_down_factor = 0.1;
94 double lambda_start = 0.001;
95
96 // values set in fit() that can be inspected later
98 int eval_count; // number of function evaluations
99
100 // arrays used during refinement
101 std::vector<double> alpha; // matrix
102 std::vector<double> beta; // vector
103 std::vector<double> temp_alpha, temp_beta; // working arrays
104
105
106 template<typename Target>
107 double fit(Target& target) {
108 eval_count = 0;
109 std::vector<double> initial_a = target.get_parameters();
110 debug_print("ini", initial_a);
111 initial_wssr = this->compute_wssr(target.compute_values(), target.points);
112 std::vector<double> best_a = initial_a;
113 size_t na = initial_a.size();
114
115 double lambda = lambda_start;
116 alpha.resize(na * na);
117 beta.resize(na);
118
119 double wssr = initial_wssr;
120 this->compute_derivatives(target);
121
122 int small_change_counter = 0;
123 for (int iter = 0; ; iter++) {
124 if (eval_limit > 0 && eval_count >= eval_limit)
125 break;
126
127 // prepare next parameters -> temp_beta
129 for (size_t j = 0; j < na; j++)
130 temp_alpha[na * j + j] *= (1.0 + lambda);
131 temp_beta = beta;
132
133 // Matrix solution (Ax=b) temp_alpha * da == temp_beta
135
136 for (size_t i = 0; i < na; i++)
137 // put new a[] into temp_beta[]
138 temp_beta[i] += best_a[i];
139
140 target.set_parameters(temp_beta);
141 double new_wssr = this->compute_wssr(target.compute_values(), target.points);
142#ifdef GEMMI_DEBUG_LEVMAR
143 fprintf(stderr, " #%d WSSR=%.8g %+g%% (%+.4g%%) lambda=%g\n",
144 iter, new_wssr, 100. * (new_wssr / initial_wssr - 1.),
145 100. * (new_wssr / wssr - 1.), lambda);
146 if (new_wssr < wssr)
148#else
149 (void) iter;
150#endif
151 if (new_wssr < wssr) {
152 double rel_change = (wssr - new_wssr) / wssr;
153 wssr = new_wssr;
155
156 if (wssr == 0)
157 break;
158 // termination criterion: negligible change of wssr
160 if (++small_change_counter >= 2)
161 break;
162 } else {
164 }
165 this->compute_derivatives(target);
167 } else { // worse fitting
168 if (lambda > lambda_limit) // termination criterion: large lambda
169 break;
171 }
172 }
173
174 target.set_parameters(wssr < initial_wssr ? best_a : initial_a);
175 return wssr;
176 }
177
178private:
179 template<typename Target>
180 void compute_derivatives(const Target& target) {
181 assert(alpha.size() == beta.size() * beta.size());
182 int na = (int)beta.size();
183 assert(na != 0);
184#ifdef GEMMI_DEBUG_LEVMAR
185 check_derivatives(const_cast<Target&>(target));
186#endif
187 std::fill(alpha.begin(), alpha.end(), 0.0);
188 std::fill(beta.begin(), beta.end(), 0.0);
189 // Iterating over points is tiled to limit memory usage. It's also a little
190 // faster than a single loop over all points for large number of points.
191 const size_t kMaxTileSize = 1024;
192 std::vector<double> dy_da;
193 size_t n = target.points.size();
194 for (size_t tstart = 0; tstart < n; tstart += kMaxTileSize) {
195 size_t tsize = std::min(n - tstart, kMaxTileSize);
196 std::vector<double> yy(tsize, 0.);
197 dy_da.resize(tsize * na);
198 std::fill(dy_da.begin(), dy_da.end(), 0.);
199 target.compute_values_and_derivatives(tstart, tsize, yy, dy_da);
200 for (size_t i = 0; i != tsize; ++i) {
201 double weight = target.points[tstart + i].get_weight();
202 double dy_sig = weight * (target.points[tstart + i].get_y() - yy[i]);
203 double* t = &dy_da[i * na];
204 for (int j = 0; j != na; ++j) {
205 if (t[j] != 0) {
206 t[j] *= weight;
207 for (int k = j; k != -1; --k)
208 alpha[na * j + k] += t[j] * t[k];
209 beta[j] += dy_sig * t[j];
210 }
211 }
212 }
213 }
214
215 // Only half of the alpha matrix was filled above. Fill the rest.
216 for (int j = 1; j < na; j++)
217 for (int k = 0; k < j; k++)
218 alpha[na * k + j] = alpha[na * j + k];
219 }
220
221#ifdef GEMMI_DEBUG_LEVMAR
222 template<typename Target>
223 void check_derivatives(Target& target) {
224 assert(!beta.empty());
225 assert(alpha.size() == beta.size() * beta.size());
226 std::vector<double> yy(target.points.size(), 0.);
227 std::vector<double> dy_da(target.points.size() * beta.size());
228 target.compute_values_and_derivatives(0, target.points.size(), yy, dy_da);
229 std::vector<double> yy2 = target.compute_values();
230 assert(yy.size() == yy2.size());
231 for (size_t i = 0; i != yy.size(); ++i) {
232 double m = std::max(std::fabs(yy[i]), std::fabs(yy2[i]));
233 if (m > 1e-5 && std::fabs(yy[i] - yy2[i]) > 1e-6 * m)
234 fprintf(stderr, "!! value %zu: %g != %g\n", i, yy[i], yy2[i]);
235 }
236 const double numerical_h = 1e-3;
237 const double small_number = 1e-6; // prevents h==0
238 std::vector<double> aa = target.get_parameters();
239 assert(aa.size() == beta.size());
240 for (size_t k = 0; k < aa.size(); k++) {
241 double acopy = aa[k];
242 double h = std::max(std::fabs(acopy), small_number) * numerical_h;
243 aa[k] -= h;
244 target.set_parameters(aa);
245 std::vector<double> y_left = target.compute_values();
246 aa[k] = acopy + h;
247 target.set_parameters(aa);
248 std::vector<double> y_right = target.compute_values();
249 aa[k] = acopy;
250 for (size_t i = 0; i != target.points.size(); ++i) {
251 double symbolic = dy_da[i * aa.size() + k];
252 double numeric = (y_right[i] - y_left[i]) / (2 * h);
253 double m = std::max(std::fabs(symbolic), std::fabs(numeric));
254 if (m > 1e-3 && std::fabs(symbolic - numeric) > 0.02 * m)
255 fprintf(stderr, "!! dy[%zu]/da[%zu]: %g != %g\n", i, k, symbolic, numeric);
256 if (i == 30)
257 break;
258 }
259 }
260 target.set_parameters(aa);
261 }
262#endif
263
264 template<typename Point>
265 double compute_wssr(const std::vector<double>& yy, const std::vector<Point>& data) {
266 // long double here notably increases the accuracy of calculations
267 long double wssr = 0;
268 for (int j = 0; j < (int)yy.size(); j++) {
269 double dy = data[j].get_weight() * (data[j].get_y() - yy[j]);
270 wssr += dy * dy;
271 }
272 ++eval_count;
273 return (double) wssr;
274 }
275};
276
277} // namespace gemmi
278#endif
void jordan_solve(std::vector< double > &a, std::vector< double > &b)
This function solves a set of linear algebraic equations using Gauss-Jordan elimination with partial ...
Definition levmar.hpp:28
void debug_print(const std::string &, std::vector< double > &)
Definition levmar.hpp:82
void fail(const std::string &msg)
Definition fail.hpp:59
std::vector< double > alpha
Definition levmar.hpp:101
double fit(Target &target)
Definition levmar.hpp:107
double lambda_down_factor
Definition levmar.hpp:93
double initial_wssr
Definition levmar.hpp:97
double lambda_up_factor
Definition levmar.hpp:92
double stop_rel_change
Definition levmar.hpp:89
std::vector< double > beta
Definition levmar.hpp:102
double lambda_start
Definition levmar.hpp:94
std::vector< double > temp_alpha
Definition levmar.hpp:103
std::vector< double > temp_beta
Definition levmar.hpp:103
double lambda_limit
Definition levmar.hpp:88