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 /// print the gradient the optimizer actually works with
189 ///
190 /// This is the PROJECTED gradient, and it is what every convergence test
191 /// below uses. The engines print their own raw derivatives -- a table that
192 /// carries the full spurious net force and torque, typically orders of
193 /// magnitude above the projected residual -- so without this the only
194 /// gradient visible in an optimization log is the one the optimizer does not
195 /// use, which reads as though no projection were happening at all.
196 ///
197 /// The removed net force is printed alongside precisely because it is not a
198 /// defect of the optimizer: translational invariance is broken only by the
199 /// numerics of the underlying gradient, and seeing how much was taken out is
200 /// the honest measure of that error.
201 void print_gradient(const Molecule &molecule, const Tensor<double> &graw,
202 const Tensor<double> &g) const {
203 const size_t natom = molecule.natom();
204 // a plain array, not a Tensor: Tensor::operator()(0) is ambiguous between
205 // the long and the const long* overloads, since 0 is also a null pointer
206 // constant
207 double net[3] = {0.0, 0.0, 0.0};
208 for (size_t i = 0; i < natom; ++i)
209 for (int c = 0; c < 3; ++c)
210 net[c] += graw[i * 3 + c];
211
212 print("\n Gradient (a.u.), translations and rotations projected out\n"
213 " ---------------------------------------------------------\n");
214 print(" atom x y z dE/dx "
215 "dE/dy dE/dz");
216 print(" ------ ------------ ------------ ------------ ------------ "
217 "------------ ------------");
218 for (size_t i = 0; i < natom; ++i) {
219 const Atom &atom = molecule.get_atom(i);
220 printf(" %5d %12.6f %12.6f %12.6f %12.6f %12.6f %12.6f\n", int(i), atom.x,
221 atom.y, atom.z, g[i * 3 + 0], g[i * 3 + 1], g[i * 3 + 2]);
222 }
223 printf(" max element of the projected gradient %12.3e\n", g.absmax());
224 printf(" net force removed by the projection %12.3e %12.3e %12.3e\n",
225 net[0], net[1], net[2]);
226 }
227
228 /// a1 is initial step (usually pick 1)
229 /// energy0 is energy at zero step
230 /// dxgrad is gradient projected onto search dir
231 ///
232 template <typename targetT>
233 double line_search(Molecule &molecule, targetT &target,
234 const Tensor<double> &dx, double energy0, double dxgrad,
235 double a1 = 1.0) {
236 double energy1;
237 double hess, a2;
238 const char *lsmode = "";
239
240 Tensor<double> x = molecule.get_all_coords().flat();
241
242 // Ensure we are walking downhill (BFGS should ensure that, but SR1 may not)
243 if (dxgrad * a1 > 0.0) {
244 if (print_level > 0)
245 print(" line search gradient +ve ", a1, dxgrad);
246 a1 = -a1;
247 }
248
249 // Compute energy at new point
250 energy1 = target.value(x + a1 * dx);
251
252 // Fit to a parabola using energy0, g0, energy1
253 hess = 2.0 * (energy1 - energy0 - a1 * dxgrad) / (a1 * a1);
254 a2 = -dxgrad / hess; // Newton step
255
256 if (std::abs(energy1 - energy0) <
257 energy_precision) { // Insufficient precision
258 a2 = a1;
259 lsmode = "fixed";
260 } else if (hess > 0.0) { // Positive curvature
261 if ((energy1 - energy0) <= -energy_precision) { // a1 step went downhill
262 lsmode = "downhill";
263 if (std::abs(a2) >
264 4.0 * std::abs(a1)) { // Walking down hill but don't go too far
265 lsmode = "restrict";
266 a2 = 4.0 * a1;
267 }
268 } else { // a1 step went uphill ... we have bracketed the minimum.
269 lsmode = "bracket";
270 }
271 } else { // Negative curvature
272 if ((energy1 - energy0) < energy_precision) { // keep walking down hill
273 lsmode = "negative";
274 a2 = 2e0 * a1;
275 } else {
276 lsmode = "punt"; // negative curvature but no apparent progress
277 a2 = a1;
278 }
279 }
280
281 if (std::abs(a2 - a1) <
282 0.2 * std::abs(a1)) { // Take full step to avoid reconverging SCF
283 a2 = a1;
284 lsmode = "fixed2";
285 }
286
287 // Predicted next energy
288 double energy2 = energy0 + dxgrad * a2 + 0.5 * hess * a2 * a2;
289
290 if (print_level > 0) {
291 printf("\n line search grad=%.2e hess=%.2e mode=%s newstep=%.3f\n",
292 dxgrad, hess, lsmode, a2);
293 printf(" predicted %.12e\n\n", energy2);
294 }
295
296 return a2;
297 }
298
299public:
300 MolOpt(int maxiter = 20, double maxstep = 0.1, double etol = 1e-4,
301 double gtol = 1e-3, double xtol = 1e-3, double energy_precision = 1e-5,
302 double gradient_precision = 1e-4, int print_level = 1,
303 std::string update = "BFGS")
306 gtol(std::max(gtol, gradient_precision)), xtol(xtol),
308 gradient_precision(gradient_precision), print_level(print_level),
310
311 {
312 if (print_level > 0) {
313 std::cout << endl;
314 print_justified("Molecular Geometry Optimization");
315 std::cout << endl;
316 print(" maximum iterations", maxiter);
317 print(" maximum step", maxstep);
318 print(" energy convergence", etol);
319 print(" gradient convergence", gtol);
320 print(" cartesian convergence", xtol);
321 print(" energy precision", energy_precision);
322 print(" gradient precision", gradient_precision);
323 print(" hessian update", update);
324 }
325 }
326
327 void set_hessian(const Tensor<double> &h) { hessian = h; }
328
329 const Tensor<double> &get_hessian() const { return hessian; }
330
331 void initialize_hessian(const Molecule &molecule) {
332 const int N = 3 * molecule.natom();
333 hessian = Tensor<double>(N, N);
334 for (int i = 0; i < N; i++)
335 hessian(i, i) = 0.5;
336 }
337
338 template <typename targetT>
339 Molecule optimize(Molecule molecule,
340 targetT &target) { ////!!!!!!! pass by value
341 const int natom = molecule.natom();
342
343 // Code structured so it will be straightforward to introduce redundant
344 // internal coordinates
345
346 if (hessian.size() == 0)
348
349 double ep = 0.0; // Previous energy
350 Tensor<double> gp(3 * natom); // Previous gradient
351 Tensor<double> dx(3 * natom); // Current search direction
352 gp = 0.0;
353 dx = 0.0;
354
355 for (int iter = 0; iter < maxiter; iter++) {
356 if (print_level > 0)
357 print("\n\n Geometry optimization iteration", iter, "\n");
358 if (print_level > 0)
359 molecule.print();
360
361 double e;
362 Tensor<double> g;
363
364 target.energy_and_gradient(molecule, e, g);
365
366 // Project out translations and rotations before testing convergence --
367 // the raw gradient carries a spurious net force/torque of the order of
368 // the gradient accuracy, which would keep max-g above gtol forever
369 Tensor<double> P = make_projector(molecule);
370 const Tensor<double> graw = copy(g);
371 g = inner(P, g);
372 // by default, and NOT only at high print levels: this is the gradient
373 // every test below is applied to
374 if (print_level > 0)
376
377 double de = e - ep;
378 double dxmax = dx.absmax();
379 double gmax = g.absmax();
380
381 bool dxconv = (iter > 0) && (dxmax < xtol);
382 bool gconv = gmax < gtol;
383 bool econv = (iter > 0) && (std::abs(de) < etol);
384 bool converged = econv && dxconv && gconv;
385
386 if (!converged && gmax < gradient_precision) {
387 if (print_level > 0)
388 print("\nInsufficient precision in gradient to proceed further -- "
389 "forcing convergence\n");
390 converged = true;
391 }
392
393 if (print_level > 0) {
394 const char *tf[] = {"F", "T"};
395 print(" ");
396 printf(
397 " energy delta-e max-dx max-g e dx g\n");
398 printf(" ---------------- --------- --------- --------- --- --- "
399 "---\n");
400 printf(" %15.6f %9.2e %9.2e %9.2e %s %s %s\n", e, de, dxmax,
401 gmax, tf[econv], tf[dxconv], tf[gconv]);
402 // print(e, de, econv, dxmax, dxconv, dxnorm, gmax, gconv, gnorm,
403 // converged);
404 print(" ");
405 }
406
407 if (converged) {
408 if (print_level > 0)
409 print("\n Geometry optimization converged!\n");
410 if (print_level > 0)
411 molecule.print();
412 break;
413 }
414
415 if (iter > 0) {
416 if ((g - gp).absmax() < 2.0 * gradient_precision) {
417 if (print_level > 0)
418 print(" skipping hessian update due to insufficient precision in "
419 "gradient");
420 } else if (update == "bfgs") {
422 } else if (update == "sr1") {
424 } else {
425 throw "unknown update";
426 }
427 }
428
429 ep = e;
430 gp = g;
431
432 // Construct the projected and shifted hessian = PHP + shift*(1-P)
433 const double shift = 1000.0; // this value assumed in new_search_dir
434 Tensor<double> PHPS = inner(P, inner(hessian, P)) - shift * P;
435 if (print_level > 1) {
436 print("projector");
437 print(P);
438 }
439 for (int i = 0; i < 3 * natom; i++)
440 PHPS(i, i) += shift;
441 if (print_level > 1) {
442 print("PHPS");
443 print(PHPS);
444 }
445
446 // Construct new search direction by diagonalizing Hessian and taking
447 // spectral step
448 dx = new_search_direction(g, PHPS);
449 if (print_level > 1)
450 print("dx", dx);
451
452 // Line search
453 double alpha = line_search(molecule, target, dx, e, dx.trace(g), 1.0);
454 if (print_level > 1)
455 print("step", alpha);
456 dx.scale(alpha);
457 if (print_level > 1)
458 print("scaled dx", dx);
459
460 // Take the step
461 Tensor<double> x = molecule.get_all_coords().flat();
462 x += dx;
463 molecule.set_all_coords(x.reshape(natom, 3));
464
465 if (print_level > 1)
466 print("new molecular coords");
467 }
468 // return the optimized molecule
469 return molecule;
470 }
471
472 template <typename targetT>
473 auto optimize_app(Molecule molecule,
475 -> OptimizationResults { ////!!!!!!! pass by value
476 const int natom = molecule.natom();
477
478 // Code structured so it will be straightforward to introduce redundant
479 // internal coordinates
480 OptimizationResults results;
481
482 if (hessian.size() == 0)
484
485 double ep = 0.0; // Previous energy
486 Tensor<double> gp(3 * natom); // Previous gradient
487 Tensor<double> dx(3 * natom); // Current search direction
488 gp = 0.0;
489 dx = 0.0;
490
491 int iter = 0;
492 for (iter = 0; iter < maxiter; iter++) {
493 if (print_level > 0)
494 print("\n\n Geometry optimization iteration", iter, "\n");
495 if (print_level > 0)
496 molecule.print();
497
498 double e;
499 Tensor<double> g;
500
501 target.energy_and_gradient(molecule, e, g);
502
503 // Project out translations and rotations before testing convergence --
504 // the raw gradient carries a spurious net force/torque of the order of
505 // the gradient accuracy, which would keep max-g above gtol forever
506 Tensor<double> P = make_projector(molecule);
507 const Tensor<double> graw = copy(g);
508 g = inner(P, g);
509 // by default, and NOT only at high print levels: this is the gradient
510 // every test below is applied to
511 if (print_level > 0)
513
514 double de = e - ep;
515 double dxmax = dx.absmax();
516 results.max_gradient = g.absmax();
517
518 bool dxconv = (iter > 0) && (dxmax < xtol);
519 bool gconv = results.max_gradient < gtol;
520 bool econv = (iter > 0) && (std::abs(de) < etol);
521 bool converged = econv && dxconv && gconv;
522
523 if (!converged && results.max_gradient < gradient_precision) {
524 if (print_level > 0)
525 print("\nInsufficient precision in gradient to proceed further -- "
526 "forcing convergence\n");
527 converged = true;
528 }
529
530 if (print_level > 0) {
531 const char *tf[] = {"F", "T"};
532 print(" ");
533 printf(
534 " energy delta-e max-dx max-g e dx g\n");
535 printf(" ---------------- --------- --------- --------- --- --- "
536 "---\n");
537 printf(" %15.6f %9.2e %9.2e %9.2e %s %s %s\n", e, de, dxmax,
538 results.max_gradient, tf[econv], tf[dxconv], tf[gconv]);
539 // print(e, de, econv, dxmax, dxconv, dxnorm, gmax, gconv, gnorm,
540 // converged);
541 print(" ");
542 }
543
544 if (converged) {
545 if (print_level > 0)
546 print("\n Geometry optimization converged!\n");
547 if (print_level > 0)
548 molecule.print();
549 break;
550 }
551
552 if (iter > 0) {
553 if ((g - gp).absmax() < 2.0 * gradient_precision) {
554 if (print_level > 0)
555 print(" skipping hessian update due to insufficient precision in "
556 "gradient");
557 } else if (update == "bfgs") {
559 } else if (update == "sr1") {
561 } else {
562 throw "unknown update";
563 }
564 }
565
566 ep = e;
567 gp = g;
568
569 // Construct the projected and shifted hessian = PHP + shift*(1-P)
570 const double shift = 1000.0; // this value assumed in new_search_dir
571 Tensor<double> PHPS = inner(P, inner(hessian, P)) - shift * P;
572 if (print_level > 1) {
573 print("projector");
574 print(P);
575 }
576 for (int i = 0; i < 3 * natom; i++)
577 PHPS(i, i) += shift;
578 if (print_level > 1) {
579 print("PHPS");
580 print(PHPS);
581 }
582
583 // Construct new search direction by diagonalizing Hessian and taking
584 // spectral step
585 dx = new_search_direction(g, PHPS);
586 if (print_level > 1)
587 print("dx", dx);
588
589 // Line search
590 double alpha = line_search(molecule, target, dx, e, dx.trace(g), 1.0);
591 if (print_level > 1)
592 print("step", alpha);
593 dx.scale(alpha);
594 if (print_level > 1)
595 print("scaled dx", dx);
596
597 // Take the step
598 Tensor<double> x = molecule.get_all_coords().flat();
599 x += dx;
600 molecule.set_all_coords(x.reshape(natom, 3));
601
602 if (print_level > 1)
603 print("new molecular coords");
604 }
605
606 results.final_energy = ep;
607 results.nsteps = iter;
608 results.final_geometry = molecule;
609 // return the optimized molecule
610 return results;
611 }
612};
613} // namespace madness
614#endif // MADNESS_MOLOPT_H
Definition mentity.h:71
double x
Definition mentity.h:73
double y
Definition mentity.h:73
double z
Definition mentity.h:73
static void hessian_update_bfgs(const Tensor< double > &dx, const Tensor< double > &dg, Tensor< double > &hessian)
make this static for other QN classed to have access to it
Definition solvers.cc:179
static void hessian_update_sr1(const Tensor< double > &s, const Tensor< double > &y, Tensor< double > &hessian)
make this static for other QN classed to have access to it
Definition solvers.cc:166
static double shift
Definition dirac-hatom.cc:19
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
Namespace for all elements and tools of MADNESS.
Definition DFParameters.h:10
static const Slice _(0,-1, 1)
void print(const T &t, const Ts &... ts)
Print items to std::cout (items separated by spaces) and terminate with a new line.
Definition print.h:227
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
NDIM const Function< R, NDIM > & g
Definition mra.h:2622
Function< T, CCPairFunction< T, NDIM >::LDIM > inner(const CCPairFunction< T, NDIM > &c, const Function< T, CCPairFunction< T, NDIM >::LDIM > &f, const std::tuple< int, int, int > v1, const std::tuple< int, int, int > v2)
Definition ccpairfunction.h:993
static XNonlinearSolver< std::vector< Function< T, NDIM > >, T, vector_function_allocator< T, NDIM > > nonlinear_vector_solver(World &world, const long nvec)
Definition nonlinsol.h:371
Function< T, NDIM > copy(const Function< T, NDIM > &f, const std::shared_ptr< WorldDCPmapInterface< Key< NDIM > > > &pmap, bool fence=true)
Create a new copy of the function with different distribution and optional fence.
Definition mra.h:2187
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 double c
Definition relops.cc:10
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 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