1 /*
2 * This source code is part of
3 *
4 * E R K A L E
5 * -
6 * HF/DFT from Hel
7 *
8 * Written by Susi Lehtola, 2010-2011
9 * Copyright (c) 2010-2011, Susi Lehtola
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 */
16
17 #include "basislibrary.h"
18 #include "basis.h"
19 #include "checkpoint.h"
20 #include "dftfuncs.h"
21 #include "dftgrid.h"
22 #include "elements.h"
23 #include "emd/emd.h"
24 #include "find_molecules.h"
25 #include "lebedev.h"
26 #include "linalg.h"
27 #include "mathf.h"
28 #include "xyzutils.h"
29 #include "properties.h"
30 #include "scf.h"
31 #include "settings.h"
32 #include "stringutil.h"
33 #include "timer.h"
34 #include "lbfgs.h"
35
36 // Needed for libint init
37 #include "eriworker.h"
38
39 #include <armadillo>
40 #include <cstdio>
41 #include <cstdlib>
42 #include <cfloat>
43
44 #ifdef _OPENMP
45 #include <omp.h>
46 #endif
47
48 #ifdef SVNRELEASE
49 #include "version.h"
50 #endif
51
52 // Settings
53 Settings settings;
54
55 // Optimization helpers
56 typedef struct {
57 // Atoms in the system
58 std::vector<atom_t> atoms;
59 // Basis set library
60 BasisSetLibrary baslib;
61 // Indices of dofs
62 std::vector<size_t> dofidx;
63
64 // Numeric gradient?
65 bool numgrad;
66 // Step size for numeric gradient
67 double step;
68 // Order of stencil
69 int npoints;
70 } opthelper_t;
71
72 enum minimizer {
73 // Fletcher-Reeves conjugate gradients
74 gCGFR,
75 // Polak-Ribiere conjugate gradient
76 gCGPR,
77 // Broyden-Fletcher-Goldfarb-Shanno
78 gBFGS,
79 // Steepest descent
80 gSD
81 };
82
83 // Convergence criteria
84 enum convergence {
85 LOOSE,
86 NORMAL,
87 TIGHT,
88 VERYTIGHT
89 };
90
get_displacement(const std::vector<atom_t> & g,const std::vector<atom_t> & o,double & dmax,double & drms)91 void get_displacement(const std::vector<atom_t> & g, const std::vector<atom_t> & o, double & dmax, double & drms) {
92 if(g.size()!=o.size()) {
93 ERROR_INFO();
94 throw std::runtime_error("Cannot compare different geometries.\n");
95 }
96
97 dmax=0.0;
98 drms=0.0;
99
100 // Compute displacements
101 for(size_t i=0;i<g.size();i++) {
102 double dsq=pow(g[i].x-o[i].x,2) + pow(g[i].y-o[i].y,2) + pow(g[i].z-o[i].z,2);
103
104 drms+=dsq;
105 if(dsq>dmax)
106 dmax=dsq;
107 }
108
109 // Take square roots here
110 drms=sqrt(drms/g.size());
111 dmax=sqrt(dmax);
112 }
113
calculate_projection(const std::vector<atom_t> & g,const std::vector<atom_t> & o,const arma::vec & f,const std::vector<size_t> & dofidx)114 double calculate_projection(const std::vector<atom_t> & g, const std::vector<atom_t> & o, const arma::vec & f, const std::vector<size_t> & dofidx) {
115 double dE=0.0;
116 for(size_t i=0;i<g.size();i++) {
117 size_t j=dofidx[i];
118 dE+=f(3*i)*(g[j].x-o[j].x) + f(3*i+1)*(g[j].y-o[j].y) + f(3*i+2)*(g[j].z-o[j].z);
119 }
120 return dE;
121 }
122
get_forces(const arma::vec & g,double & fmax,double & frms)123 void get_forces(const arma::vec & g, double & fmax, double & frms) {
124 fmax=0.0;
125 frms=0.0;
126
127 for(size_t i=0;i<g.n_elem/3;i++) {
128 double x=g(3*i);
129 double y=g(3*i+1);
130 double z=g(3*i+2);
131
132 double fsq=x*x + y*y + z*z;
133
134 frms+=fsq;
135 if(fsq>fmax)
136 fmax=fsq;
137 }
138
139 frms=sqrt(3*frms/g.n_elem);
140 fmax=sqrt(fmax);
141 }
142
get_atoms(const arma::vec & x,const opthelper_t & opts)143 std::vector<atom_t> get_atoms(const arma::vec & x, const opthelper_t & opts) {
144 // Update atomic positions
145 std::vector<atom_t> atoms(opts.atoms);
146
147 for(size_t i=0;i<opts.dofidx.size();i++) {
148 size_t j=opts.dofidx[i];
149 atoms[j].x=x(3*i);
150 atoms[j].y=x(3*i+1);
151 atoms[j].z=x(3*i+2);
152 }
153
154 return atoms;
155 }
156
157 enum calcd {
158 // Full calculation
159 FULLCALC,
160 // Just the forces
161 FORCECALC,
162 // Just the energy
163 ECALC,
164 // Nothing
165 NOCALC
166 };
167
168
run_calc(const BasisSet & basis,bool force)169 void run_calc(const BasisSet & basis, bool force) {
170 bool pz=false;
171 try {
172 pz=settings.get_bool("PZ");
173 } catch(std::runtime_error &) {
174 }
175
176 if(pz)
177 throw std::logic_error("Analytic forces not implemented for PZ-SIC!\n");
178
179 Timer t;
180
181 // Nothing to load - run full calculation.
182 calculate(basis,force);
183 if(force) {
184 Checkpoint solchk(settings.get_string("SaveChk"),false);
185 arma::vec f;
186 solchk.read("Force",f);
187 interpret_force(f).t().print("Analytic force");
188
189 double fmax, frms;
190 get_forces(f, fmax, frms);
191 printf("Max force = %.3e, rms force = %.3e, evaluated in %s\n",fmax,frms,t.elapsed().c_str());
192 }
193 }
194
run_calc_num(const BasisSet & basis,bool force,int npoints,double h)195 void run_calc_num(const BasisSet & basis, bool force, int npoints, double h) {
196 std::string savename=settings.get_string("SaveChk");
197 std::string tempname=".tempchk";
198
199 // Run calculation
200 Timer t;
201 calculate(basis,false);
202 if(force)
203 printf("Energy evaluated in %s\n",t.elapsed().c_str());
204
205 // All we needed was the energy.
206 if(!force)
207 return;
208 // Turn off verbose setting
209 settings.set_bool("Verbose",false);
210
211 // We have converged the energy, next compute force by finite
212 // differences.
213 arma::mat fm;
214 fm.zeros(3,basis.get_Nnuc());
215
216 // Nuclear coordinates. Take the transpose so that (x,y,z) are
217 // stored consecutively in memory
218 arma::mat nuccoord(basis.get_nuclear_coords().t());
219
220 // Get the stencil
221 if(npoints<2)
222 throw std::runtime_error("Invalid stencil, must be >=2.\n");
223 // Points to evaluate at
224 arma::vec dx=arma::linspace<arma::vec>(-(npoints-1)/2.0,(npoints-1)/2.0,npoints);
225 // Weights at the points
226 arma::vec w;
227 {
228 arma::mat c(dx.n_elem,2);
229 stencil(0.0,dx,c);
230 w=c.col(1);
231
232 // Eliminate any small weights
233 for(size_t i=0;i<w.n_elem;i++)
234 if(std::abs(w(i))<DBL_EPSILON*npoints) {
235 dx.subvec(i,dx.n_elem-2)=dx.subvec(i+1,dx.n_elem-1);
236 dx.resize(dx.n_elem-1);
237
238 w.subvec(i,w.n_elem-2)=w.subvec(i+1,w.n_elem-1);
239 w.resize(w.n_elem-1);
240 }
241
242 static bool printout=true;
243 if(printout) {
244 // Only print out stencil once
245 printf("\n%13s %13s\n","xsten","wsten");
246 for(size_t i=0;i<w.n_elem;i++)
247 printf("% e % e\n",dx(i),w(i));
248 printout=false;
249 }
250
251 // Put in spacing
252 dx*=h;
253 w/=h;
254 }
255
256 t.set();
257
258 // Loop over degrees of freedom
259 size_t Ndof=3*basis.get_Nnuc()-3;
260 printf("Calculating %i displacements with %i point stencil\n",(int) Ndof,(int) dx.n_elem);
261 fflush(stdout);
262 for(size_t idof=0;idof<Ndof;idof++) {
263 // No x or y force
264 if(settings.get_bool("LinearSymmetry"))
265 if(idof%3!=2)
266 continue;
267
268 Timer tdof;
269 // Energies
270 arma::vec E(dx.n_elem);
271
272 for(size_t isten=0;isten<dx.n_elem;isten++) {
273 // Coordinates of nuclei.
274 arma::mat dcoord(nuccoord);
275 dcoord[idof]+=dx(isten);
276 // Take the back-transpose
277 dcoord=dcoord.t();
278
279 // Basis set
280 BasisSet dbas(basis);
281 dbas.set_nuclear_coords(dcoord);
282
283 // Energy
284 energy_t en;
285
286 Settings settings0(settings);
287 settings.set_string("LoadChk",savename);
288 settings.set_string("SaveChk",tempname);
289 {
290 // Run calculation
291 calculate(dbas,false);
292 Checkpoint solchk(settings.get_string("SaveChk"),false);
293 // Current energy is
294 solchk.read(en);
295 E(isten)=en.E;
296 }
297 remove(tempname.c_str());
298 // Restore original settings
299 settings=settings0;
300 }
301
302 // Calculate force: - grad E
303 fm(idof)=-arma::dot(w,E);
304
305 printf("%i/%i done in %s\n",(int) idof+1,(int) Ndof,tdof.elapsed().c_str());
306 fflush(stdout);
307 }
308
309 // Force on last nucleus is just the negative of the sum of all the
310 // forces on the other nuclei
311 fm.col(fm.n_cols-1)=-arma::sum(fm.cols(0,fm.n_cols-2),1);
312 //fm.print("Numerical forces");
313
314 // Open the checkpoint in write mode, don't truncate it
315 Checkpoint chkpt(savename,true,false);
316 arma::vec f(arma::vectorise(fm));
317 chkpt.write("Force",f);
318 chkpt.close();
319
320 interpret_force(f).t().print("Numerical force");
321 double fmax, frms;
322 get_forces(f, fmax, frms);
323 printf("Max force = %.3e, rms force = %.3e, evaluated in %s\n",fmax,frms,t.elapsed().c_str());
324 }
325
calculate(const arma::vec & x,const opthelper_t & p,double & E,arma::vec & g,bool force)326 void calculate(const arma::vec & x, const opthelper_t & p, double & E, arma::vec & g, bool force) {
327 Timer t;
328
329 // Get the atomic positions
330 std::vector<atom_t> atoms=get_atoms(x,p);
331 // print_xyz(atoms);
332
333 // Construct basis set
334 BasisSet basis;
335 construct_basis(basis,atoms,p.baslib);
336
337 // Perform the electronic structure calculation
338 if(p.numgrad)
339 run_calc_num(basis,force,p.npoints,p.step);
340 else
341 run_calc(basis,force);
342
343 // Solution checkpoint
344 Checkpoint solchk(settings.get_string("SaveChk"),false);
345
346 // Energy
347 energy_t en;
348 solchk.read(en);
349 E=en.E;
350
351 // Force
352 arma::vec f;
353 if(force) {
354 solchk.read("Force",f);
355
356 // Set components
357 g.zeros(3*p.dofidx.size());
358 for(size_t i=0;i<p.dofidx.size();i++) {
359 size_t j=p.dofidx[i];
360 g(3*i)=-f(3*j);
361 g(3*i+1)=-f(3*j+1);
362 g(3*i+2)=-f(3*j+2);
363 }
364 }
365 }
366
getchk(size_t n)367 std::string getchk(size_t n) {
368 std::ostringstream oss;
369 oss << "geomcalc_" << getpid() << "_" << n << ".chk";
370 return oss.str();
371 }
372
373 // Helper for line search
374 typedef struct {
375 // Step length
376 double s;
377 // Energy
378 double E;
379 // Calculation
380 size_t icalc;
381 } linesearch_t;
382
operator <(const linesearch_t & lh,const linesearch_t & rh)383 bool operator<(const linesearch_t & lh, const linesearch_t & rh) {
384 return lh.s < rh.s;
385 }
386
print_header()387 void print_header() {
388 #ifdef _OPENMP
389 printf("ERKALE - Geometry optimization from Hel, OpenMP version, running on %i cores.\n",omp_get_max_threads());
390 #else
391 printf("ERKALE - Geometry optimization from Hel, serial version.\n");
392 #endif
393 print_copyright();
394 print_license();
395 #ifdef SVNRELEASE
396 printf("At svn revision %s.\n\n",SVNREVISION);
397 #endif
398 print_hostname();
399 }
400
find_minimum(const std::vector<linesearch_t> & steps,double & Emin,double & smin,size_t & imin)401 void find_minimum(const std::vector<linesearch_t> & steps, double & Emin, double & smin, size_t & imin) {
402 Emin=steps[0].E;
403 smin=steps[0].s;
404 imin=0;
405 for(size_t i=1;i<steps.size();i++)
406 if(steps[i].E < Emin) {
407 Emin=steps[i].E;
408 smin=steps[i].s;
409 imin=i;
410 }
411 }
412
main_guarded(int argc,char ** argv)413 int main_guarded(int argc, char **argv) {
414 print_header();
415
416 if(argc!=2) {
417 printf("Usage: $ %s runfile\n",argv[0]);
418 return 0;
419 }
420
421 // Initialize libint
422 init_libint_base();
423 // Initialize libderiv
424 init_libderiv_base();
425
426 Timer tprog;
427 tprog.print_time();
428
429 // Parse settings
430 settings.add_scf_settings();
431 settings.add_string("SaveChk","File to use as checkpoint","erkale.chk");
432 settings.add_string("LoadChk","File to load old results from","");
433 settings.add_bool("ForcePol","Force polarized calculation",false);
434 settings.add_string("Optimizer","Optimizer to use: CGFR, CGPR, BFGS, SD","BFGS");
435 settings.add_int("CGReset","Reset CG direction to SD every N steps", 5);
436 settings.add_int("MaxSteps","Maximum amount of geometry steps",256);
437 settings.add_string("Criterion","Convergence criterion to use: LOOSE, NORMAL, TIGHT, VERYTIGHT","NORMAL");
438 settings.add_string("OptMovie","xyz movie to store progress in","optimize.xyz");
439 settings.add_string("Result","File to save optimized geometry in","optimized.xyz");
440 settings.set_string("Logfile","erkale_geom.log");
441 settings.add_bool("NumGrad","Use finite-difference gradient?",false);
442 settings.add_int("Stencil","Order of finite-difference stencil for numgrad",2);
443 settings.add_double("Stepsize","Finite-difference stencil step size",1e-6);
444 settings.add_double("LineStepFac","Line search step length factor",sqrt(10.0));
445 settings.add_double("InitialStep","Initial step size in bohr",0.2);
446 settings.add_double("ParaThr","Threshold for parabolic fit interval in bohr (dimer only)",1e-2);
447 settings.add_double("BoundThr","Threshold for bond distance of bound molecules (dimer only)",10.0);
448 settings.add_double("FuncThr","Threshold for function value change in search (dimer only)",1e-6);
449 settings.parse(std::string(argv[1]),true);
450 settings.print();
451
452 // Don't try saving or loading Cholesky integrals
453 settings.set_int("CholeskyMode",0);
454
455 int maxiter=settings.get_int("MaxSteps");
456 std::string optmovie=settings.get_string("OptMovie");
457 std::string result=settings.get_string("Result");
458 bool numgrad=settings.get_bool("NumGrad");
459 int stencil=settings.get_int("Stencil");
460 double step=settings.get_double("Stepsize");
461 double steplen=settings.get_double("InitialStep");
462 double Rcutoff=settings.get_double("BoundThr");
463 double fac=settings.get_double("LineStepFac");
464 double parathr=settings.get_double("ParaThr");
465 double functhr=settings.get_double("FuncThr");
466 int cgreset=settings.get_int("CGReset");
467
468 // Interpret optimizer
469 enum minimizer alg;
470 std::string method=settings.get_string("Optimizer");
471 if(stricmp(method,"CGFR")==0)
472 alg=gCGFR;
473 else if(stricmp(method,"CGPR")==0)
474 alg=gCGPR;
475 else if(stricmp(method,"BFGS")==0)
476 alg=gBFGS;
477 else if(stricmp(method,"SD")==0)
478 alg=gSD;
479 else {
480 ERROR_INFO();
481 throw std::runtime_error("Unknown optimization method.\n");
482 }
483
484 // Interpret optimizer
485 enum convergence crit;
486 method=settings.get_string("Criterion");
487 if(stricmp(method,"LOOSE")==0)
488 crit=LOOSE;
489 else if(stricmp(method,"NORMAL")==0)
490 crit=NORMAL;
491 else if(stricmp(method,"TIGHT")==0)
492 crit=TIGHT;
493 else if(stricmp(method,"VERYTIGHT")==0)
494 crit=VERYTIGHT;
495 else {
496 ERROR_INFO();
497 throw std::runtime_error("Unknown optimization method.\n");
498 }
499
500 // Redirect output?
501 std::string logfile=settings.get_string("Logfile");
502 if(stricmp(logfile,"stdout")!=0) {
503 // Redirect stdout to file
504 FILE *outstream=freopen(logfile.c_str(),"w",stdout);
505 if(outstream==NULL) {
506 ERROR_INFO();
507 throw std::runtime_error("Unable to redirect output!\n");
508 }
509
510 fprintf(stderr,"\n");
511 // Print out the header in the logfile as well
512 print_header();
513 }
514
515 // Read in atoms.
516 std::string atomfile=settings.get_string("System");
517 const std::vector<atom_t> origgeom=load_xyz(atomfile,!settings.get_bool("InputBohr"));
518 std::vector<atom_t> atoms(origgeom);
519
520 // Are any atoms fixed?
521 std::vector<size_t> dofidx;
522 for(size_t i=0;i<atoms.size();i++) {
523 bool fixed=false;
524
525 if(atoms[i].el.size()>3)
526 if(stricmp(atoms[i].el.substr(atoms[i].el.size()-3),"-Fx")==0) {
527 fixed=true;
528 atoms[i].el=atoms[i].el.substr(0,atoms[i].el.size()-3);
529 }
530
531 // Add to degrees of freedom
532 if(!fixed)
533 dofidx.push_back(i);
534 }
535
536 // Read in basis set
537 BasisSetLibrary baslib;
538 std::string basfile=settings.get_string("Basis");
539 baslib.load_basis(basfile);
540 printf("\n");
541
542 // Save to output
543 save_xyz(atoms,"Initial configuration",optmovie,false);
544
545 // Minimizer options
546 opthelper_t pars;
547 pars.atoms=atoms;
548 pars.baslib=baslib;
549 pars.dofidx=dofidx;
550 pars.numgrad=numgrad;
551 pars.npoints=stencil+1;
552 pars.step=step;
553
554 /* Starting point */
555 arma::vec x(3*dofidx.size());
556 for(size_t i=0;i<dofidx.size();i++) {
557 x(3*i)=atoms[dofidx[i]].x;
558 x(3*i+1)=atoms[dofidx[i]].y;
559 x(3*i+2)=atoms[dofidx[i]].z;
560 }
561
562 // Steps taken in optimization, index of reference calc
563 size_t ncalc=0, iref=0;
564 // Stored checkpoints
565 std::vector<size_t> chkstore;
566
567 // Energy and force, as well as old energy and force
568 double E=0.0, Eold;
569 arma::vec f, fold;
570 // Current and old search direction
571 arma::vec sd, sdold;
572
573 // Old geometry
574 std::vector<atom_t> oldgeom(atoms);
575
576 // Helper
577 LBFGS bfgs;
578
579 // Save calculation to
580 Settings settings0(settings);
581 std::string savechk0(settings.get_string("SaveChk"));
582 settings.set_string("SaveChk",getchk(ncalc));
583
584 if(atoms.size()==2) {
585 // Specialized code for diatomic molecules
586 double R0=round(dist(atoms[0],atoms[1])/steplen)*steplen;
587
588 // Nuclear coordinates
589 x.zeros(6);
590 x(2)=-R0/2;
591 x(5)=R0/2;
592
593 // Calculate energy at the starting point
594 calculate(x,pars,E,f,false);
595 chkstore.push_back(ncalc);
596 ncalc++;
597 // Turn off verbose setting for any later calcs
598 settings.set_bool("Verbose",false);
599 try {
600 // Also, don't localize, since it would screw up the converged guess
601 settings.set_string("PZloc","false");
602 // And don't run stability analysis, since we are only doing small displacements
603 settings.set_int("PZstab",0);
604 } catch(std::runtime_error &) {
605 }
606
607 // Bond distance search structure
608 std::vector<linesearch_t> steps;
609 linesearch_t p;
610 p.s=0;
611 p.icalc=ncalc-1;
612 p.E=E;
613 steps.push_back(p);
614
615 printf("\nDimer calculation\n");
616 printf("%8s %14s %12s\n","R (bohr)","Energy","Delta R");
617 printf("%8.5f % 14.6f\n",R0,E);
618
619 fprintf(stderr,"%8s %14s %12s\n","R (bohr)","Energy","Delta R");
620 fprintf(stderr,"%8.5f % 14.6f\n",R0,E);
621
622 // Search direction is bond length
623 sd.zeros(6);
624 sd(2)=-0.5;
625 sd(5)=0.5;
626
627 // Minimum energy and index
628 double Emin;
629 double smin;
630 size_t imin;
631
632 // Go closer
633 size_t ileft=0;
634 do {
635 p.s=-1.0*((++ileft)*steplen);
636 p.icalc=ncalc++;
637
638 // Load reference from earlier calculation
639 settings.set_string("LoadChk",getchk(p.icalc-1));
640 // Save calculation to
641 settings.set_string("SaveChk",getchk(p.icalc));
642
643 calculate(x+p.s*sd,pars,p.E,f,false);
644 chkstore.push_back(p.icalc);
645 steps.insert(steps.begin(),p);
646
647 printf("%8.5f % 14.6f\n",R0+p.s,p.E);
648 fflush(stdout);
649
650 fprintf(stderr,"%8.5f % 14.6f\n",R0+p.s,p.E);
651 fflush(stderr);
652
653 // Find the minimum energy
654 find_minimum(steps,Emin,smin,imin);
655 } while(imin < 2);
656
657 bool bound=true;
658
659 size_t iright=0;
660 do {
661 p.s=(++iright)*steplen;
662 p.icalc=ncalc++;
663
664 double Rlen=R0+p.s;
665 // System is not bound
666 if(Rlen>Rcutoff) {
667 bound=false;
668 break;
669 }
670
671 // Load reference from earlier calculation
672 if(iright==1)
673 settings.set_string("LoadChk",getchk(0));
674 else
675 settings.set_string("LoadChk",getchk(p.icalc-1));
676 // Save calculation to
677 settings.set_string("SaveChk",getchk(p.icalc));
678 calculate(x+p.s*sd,pars,p.E,f,false);
679 chkstore.push_back(p.icalc);
680 steps.push_back(p);
681
682 printf("%8.5f % 14.6f\n",R0+p.s,p.E);
683 fflush(stdout);
684
685 fprintf(stderr,"%8.5f % 14.6f\n",R0+p.s,p.E);
686 fflush(stderr);
687
688 // Find the minimum energy
689 find_minimum(steps,Emin,smin,imin);
690 } while(imin >= steps.size()-1);
691
692 // Threshold
693 double deltaRthr;
694 switch(crit) {
695 case(LOOSE):
696 deltaRthr=1.0e-2;
697 break;
698 case(NORMAL):
699 deltaRthr=1.0e-3;
700 break;
701 case(TIGHT):
702 deltaRthr=1.0e-4;
703 break;
704 case(VERYTIGHT):
705 deltaRthr=1.0e-5;
706 break;
707
708 default:
709 throw std::logic_error("Case not handled.\n");
710 }
711
712 // Minimum is bracketed, proceed with search
713 double deltaR;
714 bool convd=false;
715 int nrefined=0;
716 if(bound) {
717 while(true) {
718 // Sort the steps in length
719 std::sort(steps.begin(),steps.end());
720
721 // Find minimum position
722 find_minimum(steps,Emin,smin,imin);
723
724 // Do parabolic fit
725 double a=steps[imin-1].s;
726 double b=steps[imin].s;
727 double c=steps[imin+1].s;
728 double fa=steps[imin-1].E;
729 double fb=steps[imin].E;
730 double fc=steps[imin+1].E;
731
732 // Is there any variation in the function value?
733 double fmin=std::min(std::min(fa,fb),fc);
734 double fmax=std::max(std::max(fa,fb),fc);
735 if(fmax-fmin<=functhr) {
736 printf("Function value converged within threshold\n");
737 convd=true;
738 break;
739 }
740
741 // New position is at
742 double xs = b - 0.5*((b-a)*(b-a)*(fb-fc) - (b-c)*(b-c)*(fb-fa))/((b-a)*(fb-fc) - (b-c)*(fb-fa));
743
744 // Length of search intervals are
745 double len1(b-a);
746 double len2(c-b);
747 deltaR=std::max(len1,len2);
748
749 // Is this within the search interval?
750 bool parafit = (deltaR <= parathr) && xs>=a && xs<=c;
751 if(!parafit) {
752 // No, use golden ratio search
753 const double golden =(sqrt(5.0)-1.0)/2.0;
754 // Split larger interval
755 if(len1 > len2) {
756 xs = a + golden*(b-a);
757 } else {
758 xs = b + golden*(c-b);
759 }
760 }
761
762 // Determine point closest to the minimum
763 size_t rmin=0;
764 double dRmin=std::abs(steps[0].s-xs);
765 for(size_t i=1;i<steps.size();i++) {
766 double dR=std::abs(steps[i].s-xs);
767 if(dR<dRmin) {
768 rmin=i;
769 dRmin=dR;
770 }
771 }
772 settings.set_string("LoadChk",getchk(rmin));
773 p.s=xs;
774 p.icalc=ncalc++;
775 settings.set_string("SaveChk",getchk(p.icalc));
776 chkstore.push_back(p.icalc);
777 calculate(x+p.s*sd,pars,p.E,f,false);
778 steps.push_back(p);
779
780 printf("%8.5f % 14.6f %e\n",R0+p.s,p.E,deltaR);
781 fflush(stdout);
782
783 fprintf(stderr,"%8.5f % 14.6f %e\n",R0+p.s,p.E,deltaR);
784 fflush(stderr);
785
786 if(deltaR < deltaRthr) {
787 convd=true;
788 break;
789 }
790
791 // Increment iteration count
792 nrefined++;
793 if(nrefined>=maxiter) {
794 break;
795 }
796 }
797 }
798 if(convd)
799 printf("Converged\n");
800 else {
801 printf("Failed to attain converge\n");
802 return 1;
803 }
804
805 // Update geometry
806 find_minimum(steps,Emin,smin,imin);
807 x+=sd*smin;
808
809 } else {
810
811 // Calculate energy at the starting point
812 calculate(x,pars,E,f,false);
813 chkstore.push_back(ncalc);
814 ncalc++;
815 // Turn off verbose setting for any later calcs
816 settings.set_bool("Verbose",false);
817 try {
818 // Also, don't localize, since it would screw up the converged guess
819 settings.set_string("PZloc","false");
820 // And don't run stability analysis, since we are only doing small displacements
821 settings.set_int("PZstab",0);
822 } catch(std::runtime_error &) {
823 }
824
825 printf("\n\nStarting geometry optimization\n");
826 printf("%4s %18s %9s %9s\n","iter","E","fmax","frms");
827
828 fprintf(stderr,"\n%3s %18s %10s %10s %10s %10s %10s %10s %s\n", "it", "E", "dE", "dEfrac", "dmax ", "drms ", "fmax ", "frms ", "t");
829 fflush(stderr);
830
831 for(int iiter=0;iiter<maxiter;iiter++) {
832 Timer titer;
833
834 // Store old values of gradient and search direction
835 fold=f;
836 sdold=sd;
837
838 // Load reference from earlier calculation
839 settings.set_string("LoadChk",getchk(iref));
840 // Save calculation to
841 settings.set_string("SaveChk",getchk(ncalc));
842
843 // Calculate energy and force at current position
844 calculate(x,pars,E,f,true);
845 chkstore.push_back(ncalc);
846 // Change reference
847 iref=ncalc;
848 // Increment step value
849 ncalc++;
850
851 // Store old value of energy
852 Eold=E;
853
854 // Save geometry step
855 {
856 char comment[80];
857 sprintf(comment,"Step %i",(int) iiter);
858 save_xyz(get_atoms(x,pars),comment,optmovie,true);
859 }
860
861 // Search direction
862 sd=-f;
863 std::string steptype="SD";
864
865 if(iiter>0) {
866 if((alg==gCGPR || alg==gCGFR) && (iiter%cgreset!=0)) {
867 // Polak-Ribière
868 double gamma;
869 if(alg==gCGPR) {
870 gamma=arma::dot(f,f-fold)/arma::dot(fold,fold);
871 steptype="CGPR";
872 } else {
873 gamma=arma::dot(f,f)/arma::dot(fold,fold);
874 steptype="CGFR";
875 }
876
877 arma::vec sdnew=sd+gamma*sdold;
878 if(arma::dot(f,fold)>=0.2*arma::dot(fold,fold)) {
879 steptype="Powell restart - SD";
880 } else
881 sd=sdnew;
882
883 } else if(alg==gBFGS) {
884 // Update BFGS
885 bfgs.update(x,f);
886 // Get search direction
887 sd=-bfgs.solve();
888 steptype="BFGS";
889 }
890
891 // Check we are still going downhill / BFGS direction isn't nan
892 if(!(arma::dot(sd,f)<=0)) {
893 steptype+=": Bad search direction. SD";
894 sd=-f;
895 }
896 }
897
898 // Get forces
899 double fmax, frms;
900 get_forces(f, fmax, frms);
901
902 // Macroiteration status
903 printf("\n%s step\n",steptype.c_str());
904 printf("%4i % 18.10f %.3e %.3e\n",iiter,E,fmax,frms);
905 fflush(stdout);
906
907 // Legend
908 printf("\t%2s %12s %13s\n","i","step","dE");
909
910 // Do a line search on the search direction by bracketing the
911 // minimum
912 std::vector<linesearch_t> steps;
913
914 // Initial entry is
915 {
916 // Step length and energy
917 linesearch_t p;
918 p.s=0.0;
919 p.E=E;
920 p.icalc=iref;
921 steps.push_back(p);
922 }
923
924 // First, try the step length as is
925 {
926 Timer ts;
927
928 // Step length and energy
929 linesearch_t p;
930 p.s=steplen;
931 p.icalc=ncalc;
932
933 double Et;
934 arma::vec ft;
935 settings.set_string("LoadChk",getchk(iref));
936 settings.set_string("SaveChk",getchk(ncalc));
937 calculate(x+p.s*sd,pars,Et,ft,false);
938 iref=ncalc;
939 chkstore.push_back(ncalc);
940 ncalc++;
941
942 p.E=Et;
943 steps.push_back(p);
944
945 printf("\t%2i %e % e %s\n",(int) steps.size()-1,p.s,Et-E,ts.elapsed().c_str());
946 fflush(stdout);
947 }
948
949 // Minimum energy and index
950 double Emin;
951 size_t imin;
952
953 while(true) {
954 // Sort the steps in length
955 std::sort(steps.begin(),steps.end());
956
957 // Find the minimum energy
958 double smin;
959 find_minimum(steps,Emin,smin,imin);
960
961 // Where is the minimum?
962 if(imin==0 || imin==1 || imin==steps.size()-1) {
963 Timer ts;
964
965 linesearch_t p;
966 if(imin==0 || (steps.size()>2 && imin==1)) {
967 // Need smaller step
968 p.s=steps[1].s/fac;
969
970 if(p.s*arma::max(arma::abs(sd)) < DBL_EPSILON) {
971 printf("Step length too small, stopping line search.\n");
972 break;
973 }
974 } else {
975 // Need bigger step
976 p.s=steps[imin].s*fac;
977 }
978 p.icalc=ncalc;
979
980 double Et;
981 arma::vec ft;
982 settings.set_string("LoadChk",getchk(steps[imin].icalc));
983 settings.set_string("SaveChk",getchk(ncalc));
984 calculate(x+p.s*sd,pars,Et,ft,false);
985 chkstore.push_back(ncalc);
986 ncalc++;
987
988 p.E=Et;
989 steps.push_back(p);
990 printf("\t%2i %e % e %s\n",(int) steps.size()-1,p.s,Et-E,ts.elapsed().c_str());
991 fflush(stdout);
992
993 } else {
994 // Optimum is somewhere in the middle
995 break;
996 }
997 }
998
999 // Find the minimum energy
1000 double smin;
1001 find_minimum(steps,Emin,smin,imin);
1002 if(imin == 0) {
1003 printf("Energy not decreasing.\n");
1004 break;
1005 }
1006
1007 {
1008 // Interpolate: A b = y
1009 arma::mat A(3,3);
1010 arma::vec y(3);
1011 for(size_t i=0;i<3;i++) {
1012 A(i,0)=1.0;
1013 A(i,1)=steps[imin+i-1].s;
1014 A(i,2)=std::pow(A(i,1),2);
1015
1016 y(i)=steps[imin+i-1].E;
1017 }
1018
1019 arma::mat b;
1020 if(arma::solve(b,A,y) && b(2)>sqrt(DBL_EPSILON)) {
1021 // Success in solution and parabola gives minimum.
1022
1023 // The minimum of the parabola is at
1024 double x0=-b(1)/(2*b(2));
1025
1026 // Is this an interpolation?
1027 if(A(0,1) < x0 && x0 < A(2,1)) {
1028 Timer ts;
1029
1030 // Figure out which reference is the closest
1031 iref=steps[imin-1].icalc;
1032 double dminv=std::abs(x0-A(0,1));
1033 for(size_t i=1;i<A.n_rows;i++) {
1034 double d=std::abs(x0-A(i,1));
1035 if(d<dminv) {
1036 dminv=d;
1037 iref=steps[imin+i-1].icalc;
1038 }
1039 }
1040
1041 // Do the calculation with the interpolated step
1042 linesearch_t p;
1043 p.s=x0;
1044 p.icalc=ncalc;
1045
1046 double Et;
1047 arma::vec ft;
1048 settings.set_string("LoadChk",getchk(iref));
1049 settings.set_string("SaveChk",getchk(ncalc));
1050 calculate(x+p.s*sd,pars,Et,ft,false);
1051 chkstore.push_back(ncalc);
1052 ncalc++;
1053
1054 p.E=Et;
1055 steps.push_back(p);
1056 printf("\t%2i %e % e %s\n",(int) steps.size()-1,p.s,Et-E,ts.elapsed().c_str());
1057 fflush(stdout);
1058
1059 // Resort the steps in length
1060 std::sort(steps.begin(),steps.end());
1061
1062 // Find the minimum energy
1063 find_minimum(steps,Emin,smin,imin);
1064 }
1065 }
1066 }
1067
1068 // Switch to the minimum geometry
1069 x+=steps[imin].s*sd;
1070 iref=steps[imin].icalc;
1071
1072 // Store optimal step length
1073 steplen=steps[imin].s;
1074
1075 printf("Best step size %e changed energy by % e\n",steplen,steps[imin].E-E);
1076
1077 // Copy checkpoint file
1078 {
1079 std::ostringstream oss;
1080 oss << "\\cp " << getchk(iref) << " " << savechk0;
1081 if(system(oss.str().c_str()))
1082 throw std::runtime_error("Error copying checkpoint.\n");
1083 }
1084
1085 // Erase all unnecessary calcs
1086 for(size_t i=0;i<chkstore.size();i++)
1087 if(chkstore[i]!=iref)
1088 remove(getchk(chkstore[i]).c_str());
1089
1090 // New geometry
1091 std::vector<atom_t> geom=get_atoms(x,pars);
1092
1093 // Calculate displacements
1094 double dmax, drms;
1095 get_displacement(geom, oldgeom, dmax, drms);
1096 // Calculate projected change of energy
1097 double dEproj=calculate_projection(geom,oldgeom,f,pars.dofidx);
1098 // Actual change of energy is
1099 double dE=steps[imin].E - Eold;
1100
1101 // Store new geometry
1102 oldgeom=geom;
1103
1104 // Check convergence
1105 bool fmaxconv=false, frmsconv=false;
1106 bool dmaxconv=false, drmsconv=false;
1107
1108 switch(crit) {
1109
1110 case(LOOSE):
1111 if(fmax < 2.5e-3)
1112 fmaxconv=true;
1113 if(frms < 1.7e-3)
1114 frmsconv=true;
1115 if(dmax < 1.0e-2)
1116 dmaxconv=true;
1117 if(drms < 6.7e-3)
1118 drmsconv=true;
1119 break;
1120
1121 case(NORMAL):
1122 if(fmax < 4.5e-4)
1123 fmaxconv=true;
1124 if(frms < 3.0e-4)
1125 frmsconv=true;
1126 if(dmax < 1.8e-3)
1127 dmaxconv=true;
1128 if(drms < 1.2e-3)
1129 drmsconv=true;
1130 break;
1131
1132 case(TIGHT):
1133 if(fmax < 1.5e-5)
1134 fmaxconv=true;
1135 if(frms < 1.0e-5)
1136 frmsconv=true;
1137 if(dmax < 6.0e-5)
1138 dmaxconv=true;
1139 if(drms < 4.0e-5)
1140 drmsconv=true;
1141 break;
1142
1143 case(VERYTIGHT):
1144 if(fmax < 2.0e-6)
1145 fmaxconv=true;
1146 if(frms < 1.0e-6)
1147 frmsconv=true;
1148 if(dmax < 6.0e-6)
1149 dmaxconv=true;
1150 if(drms < 4.0e-6)
1151 drmsconv=true;
1152 break;
1153
1154 default:
1155 ERROR_INFO();
1156 throw std::runtime_error("Not implemented!\n");
1157 }
1158
1159 double dEfrac;
1160 if(dEproj!=0.0)
1161 dEfrac=dE/dEproj;
1162 else
1163 dEfrac=0.0;
1164
1165 const static char cconv[]=" *";
1166
1167 fprintf(stderr,"%3i % 18.10f % .3e % .3e %.3e%c %.3e%c %.3e%c %.3e%c %s\n", iiter, E, dE, dEfrac, dmax, cconv[dmaxconv], drms, cconv[drmsconv], fmax, cconv[fmaxconv], frms, cconv[frmsconv], titer.elapsed().c_str());
1168 fflush(stderr);
1169
1170 bool convd=dmaxconv && drmsconv && fmaxconv && frmsconv;
1171
1172 if(convd) {
1173 fprintf(stderr,"Converged.\n");
1174 break;
1175 }
1176 }
1177 }
1178
1179 // Run calculation to get final checkpoint
1180 settings=settings0;
1181 settings.set_string("LoadChk",savechk0);
1182 settings.set_string("SaveChk",savechk0);
1183 calculate(x,pars,E,f,false);
1184 save_xyz(get_atoms(x,pars),"Optimized configuration",result,false);
1185
1186 // Remove the rest
1187 for(size_t i=0;i<chkstore.size();i++)
1188 remove(getchk(chkstore[i]).c_str());
1189
1190 printf("Running program took %s.\n",tprog.elapsed().c_str());
1191
1192 return 0;
1193 }
1194
main(int argc,char ** argv)1195 int main(int argc, char **argv) {
1196 try {
1197 return main_guarded(argc, argv);
1198 } catch (const std::exception &e) {
1199 std::cerr << "error: " << e.what() << std::endl;
1200 return 1;
1201 }
1202 }
1203