MADNESS 0.10.1
molopt.h
Go to the documentation of this file.
1#ifndef MADNESS_MOLOPT_H
2#define MADNESS_MOLOPT_H
3
4#include "Results.h"
9
10#include <algorithm>
11#include <string>
12
13namespace madness {
14
15// Generalized Optimization using targetT to provide energy and gradient of a
16// molecule targetT must have the following methods:
17// void energy_and_gradient(Molecule& molecule, double& energy, Tensor<double>&
18// gradient) double value(const Tensor<double>& x) // value of target at x
19// Let's express that in code so we can get compile time errors if we don't
20// have the right methods
21//
22//
23//
24
25class MolOpt {
26private:
27 const int maxiter; //< Maximum number of iterations
28 const double maxstep; //< Maximum step in any one coordinate (currently
29 // Cartesian in a.u.)
30 const double etol; //< Convergence test for energy change
31 const double gtol; //< Convergence test for maximum gradient element
32 const double xtol; //< Convergence test for Cartesian step in a.u.
33 const double energy_precision; //< Assumed precision in energy
34 const double gradient_precision; //< Assumed precision in the gradient
35 const int print_level; //< print_level=0 is none; 1 is default; 2 is debug
36 const std::string update; //< update = "bfgs" (default) or "sr1"
37
38 Tensor<double> hessian;
39
40 /// Returns new search direction given gradient and projected/shifted hessian
41 Tensor<double> new_search_direction(const Tensor<double> &g,
42 const Tensor<double> &h) const {
43 Tensor<double> dx, s;
44 double tol = gradient_precision; // threshold for small hessian eigenvalues
45 double trust = std::min(maxstep * g.dim(0), 1.0);
46
47 Tensor<double> v, e;
48 syev(h, v, e);
49 if (print_level > 1)
50 print("hessian eigenvalues", e);
51
52 // Transform gradient into spectral basis
53 Tensor<double> gv = inner(g, v);
54 if (print_level > 1)
55 print("spectral gradient", gv);
56
57 // Take step applying restrictions
58 int nneg = 0, nsmall = 0, nrestrict = 0;
59 for (int i = 0; i < e.dim(0); i++) {
60 if (e[i] > 900.0) {
61 // This must be a translation or rotation ... skip it
62 if (print_level > 1)
63 print("skipping redundant mode", i);
64 } else if (e[i] <
65 -tol) { // BGFS hessian should be positive ... SR1 may not be
66 if (print_level > 0)
67 printf(" forcing negative eigenvalue to be positive %d %.1e\n", i,
68 e[i]);
69 nneg++;
70 e[i] = tol; // or -e[i] ??
71 } else if (e[i] < tol) {
72 if (print_level > 0)
73 printf(" forcing small eigenvalue to be positive %d %.1e\n", i,
74 e[i]);
75 nsmall++;
76 e[i] = tol;
77 }
78
79 gv[i] = -gv[i] / e[i]; // Newton step
80
81 if (std::abs(gv[i]) > trust) { // Step restriction
82 double gvnew = trust * std::abs(gv(i)) / gv[i];
83 if (print_level > 0)
84 printf(" restricting step in spectral direction %d %.1e --> %.1e\n",
85 i, gv[i], gvnew);
86 nrestrict++;
87 gv[i] = gvnew;
88 }
89 }
90 if (print_level > 0 && (nneg || nsmall || nrestrict))
91 printf(" nneg=%d nsmall=%d nrestrict=%d\n", nneg, nsmall, nrestrict);
92
93 // Transform back from spectral basis
94 gv = inner(v, gv);
95
96 if (print_level > 1)
97 print("cartesian dx before restriction", gv);
98
99 // Now apply step restriction in real space
100 bool printing = false;
101 for (int i = 0; i < gv.dim(0); i++) {
102 if (fabs(gv[i]) > maxstep) {
103 gv[i] = maxstep * gv[i] / fabs(gv[i]);
104 if (print_level > 0) {
105 if (!printing)
106 printf(" restricting step in Cartesian direction");
107 printing = true;
108 printf(" %d", i);
109 }
110 }
111 }
112 if (printing)
113 printf("\n");
114
115 return gv;
116 }
117
118 /// Makes the projector onto independent coordinates
119
120 /// For Cartesians \code P*x removes the rotations and translations;
121 /// eventually will add support for redundant internal coordinates
122 Tensor<double> make_projector(const Molecule &molecule) {
123 const int natom = molecule.natom();
124 const Tensor<double> coords = molecule.get_all_coords(); // (natom,3)
125
126 // Construct normalized vectors in V in the direction of the translations
127 // and infinitesimal rotations
128 Tensor<double> V(6, natom, 3); // First 3 translations, second 3 rotations
129
130 for (int k = 0; k < 3; k++) // Translations already orthonormal
131 V(k, _, k) = 1.0 / std::sqrt(static_cast<double>(natom));
132
133 Tensor<double> centroid(3);
134 for (int k = 0; k < 3; k++)
135 centroid(k) = coords(_, k).sum() / natom;
136 if (print_level > 1)
137 print("centroid", centroid);
138
139 for (int i = 0; i < natom; i++) {
140 double x = coords(i, 0) - centroid[0];
141 double y = coords(i, 1) - centroid[1];
142 double z = coords(i, 2) - centroid[2];
143
144 V(3, i, 0) = 0; // Rotn about x axis
145 V(3, i, 1) = z;
146 V(3, i, 2) = -y;
147
148 V(4, i, 0) = z; // Rotn about y axis
149 V(4, i, 1) = 0;
150 V(4, i, 2) = -x;
151
152 V(5, i, 0) = -y;
153 V(5, i, 1) = x;
154 V(5, i, 2) = 0; // Rotn about z axis
155 }
156
157 V = V.reshape(6, 3 * natom);
158
159 if (print_level > 1)
160 print("V before orthonormal");
161 if (print_level > 1)
162 print(V);
163
164 // Normalize rotations, orthonormalize rotns and translations,
165 // noting may end up with a zero vector for linear molecules
166 for (int i = 3; i < 6; i++) {
167 V(i, _).scale(1.0 / V(i, _).normf());
168 for (int j = 0; j < i; j++) {
169 double s = V(i, _).trace(V(j, _));
170 V(i, _) -= V(j, _) * s;
171 }
172 double vnorm = V(i, _).normf();
173 if (vnorm > 1e-6) {
174 V(i, _) *= 1.0 / vnorm;
175 } else {
176 V(i, _) = 0.0;
177 }
178 }
179
180 // The projector is 1 - VT*V
181 V = -inner(transpose(V), V);
182 for (int i = 0; i < 3 * natom; i++)
183 V(i, i) += 1.0;
184
185 return V;
186 }
187
188 /// a1 is initial step (usually pick 1)
189 /// energy0 is energy at zero step
190 /// dxgrad is gradient projected onto search dir
191 ///
192 template <typename targetT>
193 double line_search(Molecule &molecule, targetT &target,
194 const Tensor<double> &dx, double energy0, double dxgrad,
195 double a1 = 1.0) {
196 double energy1;
197 double hess, a2;
198 const char *lsmode = "";
199
200 Tensor<double> x = molecule.get_all_coords().flat();
201
202 // Ensure we are walking downhill (BFGS should ensure that, but SR1 may not)
203 if (dxgrad * a1 > 0.0) {
204 if (print_level > 0)
205 print(" line search gradient +ve ", a1, dxgrad);
206 a1 = -a1;
207 }
208
209 // Compute energy at new point
210 energy1 = target.value(x + a1 * dx);
211
212 // Fit to a parabola using energy0, g0, energy1
213 hess = 2.0 * (energy1 - energy0 - a1 * dxgrad) / (a1 * a1);
214 a2 = -dxgrad / hess; // Newton step
215
216 if (std::abs(energy1 - energy0) <
217 energy_precision) { // Insufficient precision
218 a2 = a1;
219 lsmode = "fixed";
220 } else if (hess > 0.0) { // Positive curvature
221 if ((energy1 - energy0) <= -energy_precision) { // a1 step went downhill
222 lsmode = "downhill";
223 if (std::abs(a2) >
224 4.0 * std::abs(a1)) { // Walking down hill but don't go too far
225 lsmode = "restrict";
226 a2 = 4.0 * a1;
227 }
228 } else { // a1 step went uphill ... we have bracketed the minimum.
229 lsmode = "bracket";
230 }
231 } else { // Negative curvature
232 if ((energy1 - energy0) < energy_precision) { // keep walking down hill
233 lsmode = "negative";
234 a2 = 2e0 * a1;
235 } else {
236 lsmode = "punt"; // negative curvature but no apparent progress
237 a2 = a1;
238 }
239 }
240
241 if (std::abs(a2 - a1) <
242 0.2 * std::abs(a1)) { // Take full step to avoid reconverging SCF
243 a2 = a1;
244 lsmode = "fixed2";
245 }
246
247 // Predicted next energy
248 double energy2 = energy0 + dxgrad * a2 + 0.5 * hess * a2 * a2;
249
250 if (print_level > 0) {
251 printf("\n line search grad=%.2e hess=%.2e mode=%s newstep=%.3f\n",
252 dxgrad, hess, lsmode, a2);
253 printf(" predicted %.12e\n\n", energy2);
254 }
255
256 return a2;
257 }
258
259public:
260 MolOpt(int maxiter = 20, double maxstep = 0.1, double etol = 1e-4,
261 double gtol = 1e-3, double xtol = 1e-3, double energy_precision = 1e-5,
262 double gradient_precision = 1e-4, int print_level = 1,
263 std::string update = "BFGS")
264 : maxiter(maxiter), maxstep(maxstep),
265 etol(std::max(etol, energy_precision)),
266 gtol(std::max(gtol, gradient_precision)), xtol(xtol),
267 energy_precision(energy_precision),
268 gradient_precision(gradient_precision), print_level(print_level),
270
271 {
272 if (print_level > 0) {
273 std::cout << endl;
274 print_justified("Molecular Geometry Optimization");
275 std::cout << endl;
276 print(" maximum iterations", maxiter);
277 print(" maximum step", maxstep);
278 print(" energy convergence", etol);
279 print(" gradient convergence", gtol);
280 print(" cartesian convergence", xtol);
281 print(" energy precision", energy_precision);
282 print(" gradient precision", gradient_precision);
283 print(" hessian update", update);
284 }
285 }
286
287 void set_hessian(const Tensor<double> &h) { hessian = h; }
288
289 const Tensor<double> &get_hessian() const { return hessian; }
290
291 void initialize_hessian(const Molecule &molecule) {
292 const int N = 3 * molecule.natom();
293 hessian = Tensor<double>(N, N);
294 for (int i = 0; i < N; i++)
295 hessian(i, i) = 0.5;
296 }
297
298 template <typename targetT>
299 Molecule optimize(Molecule molecule,
300 targetT &target) { ////!!!!!!! pass by value
301 const int natom = molecule.natom();
302
303 // Code structured so it will be straightforward to introduce redundant
304 // internal coordinates
305
306 if (hessian.size() == 0)
307 initialize_hessian(molecule);
308
309 double ep = 0.0; // Previous energy
310 Tensor<double> gp(3 * natom); // Previous gradient
311 Tensor<double> dx(3 * natom); // Current search direction
312 gp = 0.0;
313 dx = 0.0;
314
315 for (int iter = 0; iter < maxiter; iter++) {
316 if (print_level > 0)
317 print("\n\n Geometry optimization iteration", iter, "\n");
318 if (print_level > 0)
319 molecule.print();
320
321 double e;
322 Tensor<double> g;
323
324 target.energy_and_gradient(molecule, e, g);
325
326 double de = e - ep;
327 double dxmax = dx.absmax();
328 double gmax = g.absmax();
329
330 bool dxconv = (iter > 0) && (dxmax < xtol);
331 bool gconv = gmax < gtol;
332 bool econv = (iter > 0) && (std::abs(de) < etol);
333 bool converged = econv && dxconv && gconv;
334
335 if (!converged && gmax < gradient_precision) {
336 if (print_level > 0)
337 print("\nInsufficient precision in gradient to proceed further -- "
338 "forcing convergence\n");
339 converged = true;
340 }
341
342 if (print_level > 0) {
343 const char *tf[] = {"F", "T"};
344 print(" ");
345 printf(
346 " energy delta-e max-dx max-g e dx g\n");
347 printf(" ---------------- --------- --------- --------- --- --- "
348 "---\n");
349 printf(" %15.6f %9.2e %9.2e %9.2e %s %s %s\n", e, de, dxmax,
350 gmax, tf[econv], tf[dxconv], tf[gconv]);
351 // print(e, de, econv, dxmax, dxconv, dxnorm, gmax, gconv, gnorm,
352 // converged);
353 print(" ");
354 }
355
356 if (converged) {
357 if (print_level > 0)
358 print("\n Geometry optimization converged!\n");
359 if (print_level > 0)
360 molecule.print();
361 break;
362 }
363
364 // Construct projector
365 Tensor<double> P = make_projector(molecule);
366
367 // Project the gradient before updating Hessian
368 g = inner(P, g);
369 if (print_level > 1)
370 print("gradient after projection", g);
371
372 if (iter > 0) {
373 if ((g - gp).absmax() < 2.0 * gradient_precision) {
374 if (print_level > 0)
375 print(" skipping hessian update due to insufficient precision in "
376 "gradient");
377 } else if (update == "bfgs") {
378 QuasiNewton::hessian_update_bfgs(dx, g - gp, hessian);
379 } else if (update == "sr1") {
380 QuasiNewton::hessian_update_sr1(dx, g - gp, hessian);
381 } else {
382 throw "unknown update";
383 }
384 }
385
386 ep = e;
387 gp = g;
388
389 // Construct the projected and shifted hessian = PHP + shift*(1-P)
390 const double shift = 1000.0; // this value assumed in new_search_dir
391 Tensor<double> PHPS = inner(P, inner(hessian, P)) - shift * P;
392 if (print_level > 1) {
393 print("projector");
394 print(P);
395 }
396 for (int i = 0; i < 3 * natom; i++)
397 PHPS(i, i) += shift;
398 if (print_level > 1) {
399 print("PHPS");
400 print(PHPS);
401 }
402
403 // Construct new search direction by diagonalizing Hessian and taking
404 // spectral step
405 dx = new_search_direction(g, PHPS);
406 if (print_level > 1)
407 print("dx", dx);
408
409 // Line search
410 double alpha = line_search(molecule, target, dx, e, dx.trace(g), 1.0);
411 if (print_level > 1)
412 print("step", alpha);
413 dx.scale(alpha);
414 if (print_level > 1)
415 print("scaled dx", dx);
416
417 // Take the step
418 Tensor<double> x = molecule.get_all_coords().flat();
419 x += dx;
420 molecule.set_all_coords(x.reshape(natom, 3));
421
422 if (print_level > 1)
423 print("new molecular coords");
424 }
425 // return the optimized molecule
426 return molecule;
427 }
428
429 template <typename targetT>
430 auto optimize_app(Molecule molecule,
431 targetT &target)
432 -> OptimizationResults { ////!!!!!!! pass by value
433 const int natom = molecule.natom();
434
435 // Code structured so it will be straightforward to introduce redundant
436 // internal coordinates
437 OptimizationResults results;
438
439 if (hessian.size() == 0)
440 initialize_hessian(molecule);
441
442 double ep = 0.0; // Previous energy
443 Tensor<double> gp(3 * natom); // Previous gradient
444 Tensor<double> dx(3 * natom); // Current search direction
445 gp = 0.0;
446 dx = 0.0;
447
448 int iter = 0;
449 for (iter = 0; iter < maxiter; iter++) {
450 if (print_level > 0)
451 print("\n\n Geometry optimization iteration", iter, "\n");
452 if (print_level > 0)
453 molecule.print();
454
455 double e;
456 Tensor<double> g;
457
458 target.energy_and_gradient(molecule, e, g);
459
460 double de = e - ep;
461 double dxmax = dx.absmax();
462 results.max_gradient = g.absmax();
463
464 bool dxconv = (iter > 0) && (dxmax < xtol);
465 bool gconv = results.max_gradient < gtol;
466 bool econv = (iter > 0) && (std::abs(de) < etol);
467 bool converged = econv && dxconv && gconv;
468
469 if (!converged && results.max_gradient < gradient_precision) {
470 if (print_level > 0)
471 print("\nInsufficient precision in gradient to proceed further -- "
472 "forcing convergence\n");
473 converged = true;
474 }
475
476 if (print_level > 0) {
477 const char *tf[] = {"F", "T"};
478 print(" ");
479 printf(
480 " energy delta-e max-dx max-g e dx g\n");
481 printf(" ---------------- --------- --------- --------- --- --- "
482 "---\n");
483 printf(" %15.6f %9.2e %9.2e %9.2e %s %s %s\n", e, de, dxmax,
484 results.max_gradient, tf[econv], tf[dxconv], tf[gconv]);
485 // print(e, de, econv, dxmax, dxconv, dxnorm, gmax, gconv, gnorm,
486 // converged);
487 print(" ");
488 }
489
490 if (converged) {
491 if (print_level > 0)
492 print("\n Geometry optimization converged!\n");
493 if (print_level > 0)
494 molecule.print();
495 break;
496 }
497
498 // Construct projector
499 Tensor<double> P = make_projector(molecule);
500
501 // Project the gradient before updating Hessian
502 g = inner(P, g);
503 if (print_level > 1)
504 print("gradient after projection", g);
505
506 if (iter > 0) {
507 if ((g - gp).absmax() < 2.0 * gradient_precision) {
508 if (print_level > 0)
509 print(" skipping hessian update due to insufficient precision in "
510 "gradient");
511 } else if (update == "bfgs") {
512 QuasiNewton::hessian_update_bfgs(dx, g - gp, hessian);
513 } else if (update == "sr1") {
514 QuasiNewton::hessian_update_sr1(dx, g - gp, hessian);
515 } else {
516 throw "unknown update";
517 }
518 }
519
520 ep = e;
521 gp = g;
522
523 // Construct the projected and shifted hessian = PHP + shift*(1-P)
524 const double shift = 1000.0; // this value assumed in new_search_dir
525 Tensor<double> PHPS = inner(P, inner(hessian, P)) - shift * P;
526 if (print_level > 1) {
527 print("projector");
528 print(P);
529 }
530 for (int i = 0; i < 3 * natom; i++)
531 PHPS(i, i) += shift;
532 if (print_level > 1) {
533 print("PHPS");
534 print(PHPS);
535 }
536
537 // Construct new search direction by diagonalizing Hessian and taking
538 // spectral step
539 dx = new_search_direction(g, PHPS);
540 if (print_level > 1)
541 print("dx", dx);
542
543 // Line search
544 double alpha = line_search(molecule, target, dx, e, dx.trace(g), 1.0);
545 if (print_level > 1)
546 print("step", alpha);
547 dx.scale(alpha);
548 if (print_level > 1)
549 print("scaled dx", dx);
550
551 // Take the step
552 Tensor<double> x = molecule.get_all_coords().flat();
553 x += dx;
554 molecule.set_all_coords(x.reshape(natom, 3));
555
556 if (print_level > 1)
557 print("new molecular coords");
558 }
559
560 results.final_energy = ep;
561 results.nsteps = iter;
562 results.final_geometry = molecule;
563 // return the optimized molecule
564 return results;
565 }
566};
567} // namespace madness
568#endif // MADNESS_MOLOPT_H
void hessian_update_bfgs(const Tensor< double > &dx, const Tensor< double > &dg)
Definition kain.cc:329
void hessian_update_sr1(const Tensor< double > &s, const Tensor< double > &y)
Definition kain.cc:317
static double shift
Definition dirac-hatom.cc:19
std::complex< double > inner(const Fcwf &psi, const Fcwf &phi)
Definition fcwf.cc:275
Tensor< T > transpose(const Tensor< T > &t)
Returns a new deep copy of the transpose of the input tensor.
Definition tensor.h:2035
const int maxiter
Definition gygi_soltion.cc:68
static const double v
Definition hatom_sf_dirac.cc:20
#define max(a, b)
Definition lda.h:51
void print(const tensorT &t)
Definition mcpfit.cc:140
Namespace for all elements and tools of MADNESS.
Definition DFParameters.h:10
void print_justified(const char *s, int column, bool underline)
Print a string justified on the left to start at the given column with optional underlining.
Definition print.cc:75
void syev(const Tensor< T > &A, Tensor< T > &V, Tensor< typename Tensor< T >::scalar_type > &e)
Real-symmetric or complex-Hermitian eigenproblem.
Definition lapack.cc:969
Definition mraimpl.h:51
static long abs(long a)
Definition tensor.h:219
static const long k
Definition rk.cc:44
Defines interfaces for optimization and non-linear equation solvers.
static double V(const coordT &r)
Definition tdse.cc:288
Defines and implements most of Tensor.
Prototypes for a partial interface from Tensor to LAPACK.
int P
Definition test_binsorter.cc:9
void e()
Definition test_sig.cc:75
#define N
Definition testconv.cc:37
vector_complex_function_3d update(World &world, const vector_complex_function_3d &psi, vector_complex_function_3d &vpsi, const tensor_real &e, int iter)
Definition testcosine.cc:210
static const double alpha
Definition testcosine.cc:10
double g(const coord_t &r)
Definition testgconv.cc:116
double h(const coord_1d &r)
Definition testgconv.cc:175
static Molecule molecule
Definition testperiodicdft.cc:39
const double a2
Definition vnucso.cc:86
const double a1
Definition vnucso.cc:85
FLOAT target(const FLOAT &x)
Definition y.cc:295