1 /*
2 * This source code is part of
3 *
4 * E R K A L E
5 * -
6 * 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
18
19 #include <cfloat>
20 #include "diis.h"
21 #include "lbfgs.h"
22
23 // Maximum allowed absolute weight for a Fock matrix
24 #define MAXWEIGHT 10.0
25 // Trigger cooloff if energy rises more than
26 #define COOLTHR 0.1
27
operator <(const diis_pol_entry_t & lhs,const diis_pol_entry_t & rhs)28 bool operator<(const diis_pol_entry_t & lhs, const diis_pol_entry_t & rhs) {
29 return lhs.E < rhs.E;
30 }
31
operator <(const diis_unpol_entry_t & lhs,const diis_unpol_entry_t & rhs)32 bool operator<(const diis_unpol_entry_t & lhs, const diis_unpol_entry_t & rhs) {
33 return lhs.E < rhs.E;
34 }
35
DIIS(const arma::mat & S_,const arma::mat & Sinvh_,bool usediis_,double diiseps_,double diisthr_,bool useadiis_,bool verbose_,size_t imax_)36 DIIS::DIIS(const arma::mat & S_, const arma::mat & Sinvh_, bool usediis_, double diiseps_, double diisthr_, bool useadiis_, bool verbose_, size_t imax_) {
37 S=S_;
38 Sinvh=Sinvh_;
39 usediis=usediis_;
40 useadiis=useadiis_;
41 verbose=verbose_;
42 imax=imax_;
43
44 // Start mixing in DIIS weight when error is
45 diiseps=diiseps_;
46 // and switch to full DIIS when error is
47 diisthr=diisthr_;
48
49 // No cooloff
50 cooloff=0;
51 }
52
rDIIS(const arma::mat & S_,const arma::mat & Sinvh_,bool usediis_,double diiseps_,double diisthr_,bool useadiis_,bool verbose_,size_t imax_)53 rDIIS::rDIIS(const arma::mat & S_, const arma::mat & Sinvh_, bool usediis_, double diiseps_, double diisthr_, bool useadiis_, bool verbose_, size_t imax_) : DIIS(S_,Sinvh_,usediis_,diiseps_,diisthr_,useadiis_,verbose_,imax_) {
54 }
55
uDIIS(const arma::mat & S_,const arma::mat & Sinvh_,bool combine_,bool usediis_,double diiseps_,double diisthr_,bool useadiis_,bool verbose_,size_t imax_)56 uDIIS::uDIIS(const arma::mat & S_, const arma::mat & Sinvh_, bool combine_, bool usediis_, double diiseps_, double diisthr_, bool useadiis_, bool verbose_, size_t imax_) : DIIS(S_,Sinvh_,usediis_,diiseps_,diisthr_,useadiis_,verbose_,imax_), combine(combine_) {
57 }
58
~DIIS()59 DIIS::~DIIS() {
60 }
61
~rDIIS()62 rDIIS::~rDIIS() {
63 }
64
~uDIIS()65 uDIIS::~uDIIS() {
66 }
67
clear()68 void rDIIS::clear() {
69 stack.clear();
70 }
71
clear()72 void uDIIS::clear() {
73 stack.clear();
74 }
75
erase_last()76 void rDIIS::erase_last() {
77 stack.erase(stack.begin());
78 }
79
erase_last()80 void uDIIS::erase_last() {
81 stack.erase(stack.begin());
82 }
83
update(const arma::mat & F,const arma::mat & P,double E,double & error)84 void rDIIS::update(const arma::mat & F, const arma::mat & P, double E, double & error) {
85 // New entry
86 diis_unpol_entry_t hlp;
87 hlp.F=F;
88 hlp.P=P;
89 hlp.E=E;
90
91 // Compute error matrix
92 arma::mat errmat(F*P*S);
93 // FPS - SPF
94 errmat-=arma::trans(errmat);
95 // and transform it to the orthonormal basis (1982 paper, page 557)
96 errmat=arma::trans(Sinvh)*errmat*Sinvh;
97 // and store it
98 hlp.err=arma::vectorise(errmat);
99
100 // DIIS error is
101 error=arma::max(arma::max(arma::abs(errmat)));
102
103 // Is stack full?
104 if(stack.size()==imax) {
105 erase_last();
106 }
107 // Add to stack
108 stack.push_back(hlp);
109
110 // Update ADIIS helpers
111 PiF_update();
112 }
113
PiF_update()114 void rDIIS::PiF_update() {
115 const arma::mat & Fn=stack[stack.size()-1].F;
116 const arma::mat & Pn=stack[stack.size()-1].P;
117
118 // Update matrices
119 PiF.zeros(stack.size());
120 for(size_t i=0;i<stack.size();i++)
121 PiF(i)=arma::trace((stack[i].P-Pn)*Fn);
122
123 PiFj.zeros(stack.size(),stack.size());
124 for(size_t i=0;i<stack.size();i++)
125 for(size_t j=0;j<stack.size();j++)
126 PiFj(i,j)=arma::trace((stack[i].P-Pn)*(stack[j].F-Fn));
127 }
128
update(const arma::mat & Fa,const arma::mat & Fb,const arma::mat & Pa,const arma::mat & Pb,double E,double & error)129 void uDIIS::update(const arma::mat & Fa, const arma::mat & Fb, const arma::mat & Pa, const arma::mat & Pb, double E, double & error) {
130 // New entry
131 diis_pol_entry_t hlp;
132 hlp.Fa=Fa;
133 hlp.Fb=Fb;
134 hlp.Pa=Pa;
135 hlp.Pb=Pb;
136 hlp.E=E;
137
138 // Compute error matrices
139 arma::mat errmata(Fa*Pa*S);
140 arma::mat errmatb(Fb*Pb*S);
141 // FPS - SPF
142 errmata-=arma::trans(errmata);
143 errmatb-=arma::trans(errmatb);
144 // and transform them to the orthonormal basis (1982 paper, page 557)
145 errmata=arma::trans(Sinvh)*errmata*Sinvh;
146 errmatb=arma::trans(Sinvh)*errmatb*Sinvh;
147 // and store it
148 if(combine) {
149 hlp.err=arma::vectorise(errmata+errmatb);
150 } else {
151 hlp.err.zeros(errmata.n_elem+errmatb.n_elem);
152 hlp.err.subvec(0,errmata.n_elem-1)=arma::vectorise(errmata);
153 hlp.err.subvec(errmata.n_elem,hlp.err.n_elem-1)=arma::vectorise(errmatb);
154 }
155
156 // DIIS error is
157 error=arma::max(arma::abs(hlp.err));
158
159 // Is stack full?
160 if(stack.size()==imax) {
161 erase_last();
162 }
163 // Add to stack
164 stack.push_back(hlp);
165
166 // Update ADIIS helpers
167 PiF_update();
168 }
169
PiF_update()170 void uDIIS::PiF_update() {
171 const arma::mat & Fan=stack[stack.size()-1].Fa;
172 const arma::mat & Fbn=stack[stack.size()-1].Fb;
173 const arma::mat & Pan=stack[stack.size()-1].Pa;
174 const arma::mat & Pbn=stack[stack.size()-1].Pb;
175
176 // Update matrices
177 PiF.zeros(stack.size());
178 for(size_t i=0;i<stack.size();i++)
179 PiF(i)=arma::trace((stack[i].Pa-Pan)*Fan) + arma::trace((stack[i].Pb-Pbn)*Fbn);
180
181 PiFj.zeros(stack.size(),stack.size());
182 for(size_t i=0;i<stack.size();i++)
183 for(size_t j=0;j<stack.size();j++)
184 PiFj(i,j)=arma::trace((stack[i].Pa-Pan)*(stack[j].Fa-Fan))+arma::trace((stack[i].Pb-Pbn)*(stack[j].Fb-Fbn));
185 }
186
get_energies() const187 arma::vec rDIIS::get_energies() const {
188 arma::vec E(stack.size());
189 for(size_t i=0;i<stack.size();i++)
190 E(i)=stack[i].E;
191 return E;
192 }
193
get_diis_error() const194 arma::mat rDIIS::get_diis_error() const {
195 arma::mat err(stack[0].err.n_elem,stack.size());
196 for(size_t i=0;i<stack.size();i++)
197 err.col(i)=stack[i].err;
198 return err;
199 }
200
get_energies() const201 arma::vec uDIIS::get_energies() const {
202 arma::vec E(stack.size());
203 for(size_t i=0;i<stack.size();i++)
204 E(i)=stack[i].E;
205 return E;
206 }
get_diis_error() const207 arma::mat uDIIS::get_diis_error() const {
208 arma::mat err(stack[0].err.n_elem,stack.size());
209 for(size_t i=0;i<stack.size();i++)
210 err.col(i)=stack[i].err;
211 return err;
212 }
213
get_w()214 arma::vec DIIS::get_w() {
215 // DIIS error
216 arma::mat de=get_diis_error();
217 double err=arma::max(arma::abs(de.col(de.n_cols-1)));
218
219 // Weight
220 arma::vec w;
221
222 if(useadiis && !usediis) {
223 w=get_w_adiis();
224 if(verbose) {
225 printf("ADIIS weights\n");
226 w.t().print();
227 }
228 } else if(!useadiis && usediis) {
229 // Use DIIS only if error is smaller than threshold
230 if(err>diisthr)
231 throw std::runtime_error("DIIS error too large for only DIIS to converge wave function.\n");
232
233 w=get_w_diis();
234
235 if(verbose) {
236 printf("DIIS weights\n");
237 w.t().print();
238 }
239 } else if(useadiis && usediis) {
240 // Sliding scale: DIIS weight
241 double diisw=std::max(std::min(1.0 - (err-diisthr)/(diiseps-diisthr), 1.0), 0.0);
242 // ADIIS weght
243 double adiisw=1.0-diisw;
244
245 // Determine cooloff
246 if(cooloff>0) {
247 diisw=0.0;
248 cooloff--;
249 } else {
250 // Check if energy has increased
251 arma::vec E=get_energies();
252 if(E.n_elem>1 && E(E.n_elem-1)-E(E.n_elem-2) > COOLTHR) {
253 cooloff=2;
254 diisw=0.0;
255 }
256 }
257
258 w.zeros(de.n_cols);
259
260 // DIIS and ADIIS weights
261 arma::vec wd, wa;
262 if(diisw!=0.0) {
263 wd=get_w_diis();
264 w+=diisw*wd;
265 }
266 if(adiisw!=0.0) {
267 wa=get_w_adiis();
268 w+=adiisw*wa;
269 }
270
271 if(verbose) {
272 if(adiisw!=0.0) {
273 printf("ADIIS weights\n");
274 wa.t().print();
275 }
276 if(diisw!=0.0) {
277 printf("CDIIS weights\n");
278 wd.t().print();
279 }
280 if(adiisw!=0.0 && diisw!=0.0) {
281 printf(" DIIS weights\n");
282 w.t().print();
283 }
284 }
285
286 } else
287 throw std::runtime_error("Nor DIIS or ADIIS has been turned on.\n");
288
289 return w;
290 }
291
get_w_diis() const292 arma::vec DIIS::get_w_diis() const {
293 arma::mat errs=get_diis_error();
294 return get_w_diis_wrk(errs);
295 }
296
get_w_diis_wrk(const arma::mat & errs) const297 arma::vec DIIS::get_w_diis_wrk(const arma::mat & errs) const {
298 // Size of LA problem
299 int N=(int) errs.n_cols;
300
301 // Array holding the errors
302 arma::mat B(N,N);
303 B.zeros();
304 // Compute errors
305 for(int i=0;i<N;i++)
306 for(int j=0;j<N;j++) {
307 B(i,j)=arma::dot(errs.col(i),errs.col(j));
308 }
309
310 /*
311 The C1-DIIS method is equivalent to solving the group of linear
312 equations
313 B w = lambda 1 (1)
314
315 where B is the error matrix, w are the DIIS weights, lambda is the
316 Lagrange multiplier that guarantees that the weights sum to unity,
317 and 1 stands for a unit vector (1 1 ... 1)^T.
318
319 By rescaling the weights as w -> w/lambda, equation (1) is
320 reverted to the form
321 B w = 1 (2)
322
323 which can easily be solved using SVD techniques.
324
325 Finally, the weights are renormalized to satisfy
326 \sum_i w_i = 1
327 which takes care of the Lagrange multipliers.
328 */
329
330 // Right-hand side of equation is
331 arma::vec rh(N);
332 rh.ones();
333
334 // Singular value decomposition
335 arma::mat U, V;
336 arma::vec sval;
337 if(!arma::svd(U,sval,V,B,"std")) {
338 throw std::logic_error("SVD failed in DIIS.\n");
339 }
340 //sval.print("Singular values");
341
342 // Form solution vector
343 arma::vec sol(N);
344 sol.zeros();
345 for(int i=0;i<N;i++) {
346 #if 0
347 // Perform Tikhonov regularization for the singular
348 // eigenvalues. This doesn't appear to work as well as just
349 // cutting out the spectrum.
350 double s(sval(i));
351 double t=1e-4;
352 double invs=s/(s*s + t*t);
353 sol += arma::dot(U.col(i),rh)*invs * V.col(i);
354 #else
355 // Just cut out the problematic part. Weirdly, it appears that any
356 // kind of screening of the singular eigenvalues results in poorer
357 // convergence behavior(!)
358 if(sval(i) != 0.0)
359 sol += arma::dot(U.col(i),rh)/sval(i) * V.col(i);
360 #endif
361 }
362
363 // Sanity check
364 if(arma::sum(sol)==0.0)
365 sol.ones();
366
367 // Normalize solution
368 //printf("Sum of weights is %e\n",arma::sum(sol));
369 sol/=arma::sum(sol);
370
371 return sol;
372 }
373
solve_F(arma::mat & F)374 void rDIIS::solve_F(arma::mat & F) {
375 arma::vec sol;
376 while(true) {
377 sol=get_w();
378 if(std::abs(sol(sol.n_elem-1))<=sqrt(DBL_EPSILON)) {
379 if(verbose) printf("Weight on last matrix too small, reducing to %i matrices.\n",(int) stack.size()-1);
380 erase_last();
381 PiF_update();
382 } else
383 break;
384 }
385
386 // Form weighted Fock matrix
387 F.zeros();
388 for(size_t i=0;i<stack.size();i++)
389 F+=sol(i)*stack[i].F;
390 }
391
solve_F(arma::mat & Fa,arma::mat & Fb)392 void uDIIS::solve_F(arma::mat & Fa, arma::mat & Fb) {
393 arma::vec sol;
394 while(true) {
395 sol=get_w();
396 if(std::abs(sol(sol.n_elem-1))<=sqrt(DBL_EPSILON)) {
397 if(verbose) printf("Weight on last matrix too small, reducing to %i matrices.\n",(int) stack.size()-1);
398 erase_last();
399 PiF_update();
400 } else
401 break;
402 }
403
404 // Form weighted Fock matrix
405 Fa.zeros();
406 Fb.zeros();
407 for(size_t i=0;i<stack.size();i++) {
408 Fa+=sol(i)*stack[i].Fa;
409 Fb+=sol(i)*stack[i].Fb;
410 }
411 }
412
solve_P(arma::mat & P)413 void rDIIS::solve_P(arma::mat & P) {
414 arma::vec sol;
415 while(true) {
416 sol=get_w();
417 if(std::abs(sol(sol.n_elem-1))<=sqrt(DBL_EPSILON)) {
418 if(verbose) printf("Weight on last matrix too small, reducing to %i matrices.\n",(int) stack.size()-1);
419 erase_last();
420 PiF_update();
421 } else
422 break;
423 }
424
425 // Form weighted density matrix
426 P.zeros();
427 for(size_t i=0;i<stack.size();i++)
428 P+=sol(i)*stack[i].P;
429 }
430
solve_P(arma::mat & Pa,arma::mat & Pb)431 void uDIIS::solve_P(arma::mat & Pa, arma::mat & Pb) {
432 arma::vec sol;
433 while(true) {
434 sol=get_w();
435 if(std::abs(sol(sol.n_elem-1))<=sqrt(DBL_EPSILON)) {
436 if(verbose) printf("Weight on last matrix too small, reducing to %i matrices.\n",(int) stack.size()-1);
437 erase_last();
438 PiF_update();
439 } else
440 break;
441 }
442
443 // Form weighted density matrix
444 Pa.zeros();
445 Pb.zeros();
446 for(size_t i=0;i<stack.size();i++) {
447 Pa+=sol(i)*stack[i].Pa;
448 Pb+=sol(i)*stack[i].Pb;
449 }
450 }
451
find_minE(const std::vector<std::pair<double,double>> & steps,double & Emin,size_t & imin)452 static void find_minE(const std::vector< std::pair<double,double> > & steps, double & Emin, size_t & imin) {
453 Emin=steps[0].second;
454 imin=0;
455 for(size_t i=1;i<steps.size();i++)
456 if(steps[i].second < Emin) {
457 Emin=steps[i].second;
458 imin=i;
459 }
460 }
461
compute_c(const arma::vec & x)462 static arma::vec compute_c(const arma::vec & x) {
463 // Compute contraction coefficients
464 return x%x/arma::dot(x,x);
465 }
466
compute_jac(const arma::vec & x)467 static arma::mat compute_jac(const arma::vec & x) {
468 // Compute jacobian of transformation: jac(i,j) = dc_i / dx_j
469
470 // Compute coefficients
471 arma::vec c(compute_c(x));
472 double xnorm=arma::dot(x,x);
473
474 arma::mat jac(c.n_elem,c.n_elem);
475 for(size_t i=0;i<c.n_elem;i++) {
476 double ci=c(i);
477 double xi=x(i);
478
479 for(size_t j=0;j<c.n_elem;j++) {
480 double xj=x(j);
481
482 jac(i,j)=-ci*2.0*xj/xnorm;
483 }
484
485 // Extra term on diagonal
486 jac(i,i)+=2.0*xi/xnorm;
487 }
488
489 return jac;
490 }
491
get_w_adiis() const492 arma::vec DIIS::get_w_adiis() const {
493 // Number of parameters
494 size_t N=PiF.n_elem;
495
496 if(N==1) {
497 // Trivial case.
498 arma::vec ret(1);
499 ret.ones();
500 return ret;
501 }
502
503 // Starting point: equal weights on all matrices
504 arma::vec x=arma::ones<arma::vec>(N)/N;
505
506 // BFGS accelerator
507 LBFGS bfgs;
508
509 // Step size
510 double steplen=0.01, fac=2.0;
511
512 for(size_t iiter=0;iiter<1000;iiter++) {
513 // Get gradient
514 //double E(get_E_adiis(x));
515 arma::vec g(get_dEdx_adiis(x));
516 if(arma::norm(g,2)<=1e-7) {
517 break;
518 }
519
520 // Search direction
521 bfgs.update(x,g);
522 arma::vec sd(-bfgs.solve());
523
524 // Do a line search on the search direction
525 std::vector< std::pair<double, double> > steps;
526 // First, we try a fraction of the current step length
527 {
528 std::pair<double, double> p;
529 p.first=steplen/fac;
530 p.second=get_E_adiis(x+sd*p.first);
531 steps.push_back(p);
532 }
533 // Next, we try the current step length
534 {
535 std::pair<double, double> p;
536 p.first=steplen;
537 p.second=get_E_adiis(x+sd*p.first);
538 steps.push_back(p);
539 }
540
541 // Minimum energy and index
542 double Emin;
543 size_t imin;
544
545 while(true) {
546 // Sort the steps in length
547 std::sort(steps.begin(),steps.end());
548
549 // Find the minimum energy
550 find_minE(steps,Emin,imin);
551
552 // Where is the minimum?
553 if(imin==0 || imin==steps.size()-1) {
554 // Need smaller step
555 std::pair<double,double> p;
556 if(imin==0) {
557 p.first=steps[imin].first/fac;
558 if(steps[imin].first<DBL_EPSILON)
559 break;
560 } else {
561 p.first=steps[imin].first*fac;
562 }
563 p.second=get_E_adiis(x+sd*p.first);
564 steps.push_back(p);
565 } else {
566 // Optimum is somewhere in the middle
567 break;
568 }
569 }
570
571 if((imin!=0) && (imin!=steps.size()-1)) {
572 // Interpolate: A b = y
573 arma::mat A(3,3);
574 arma::vec y(3);
575 for(size_t i=0;i<3;i++) {
576 A(i,0)=1.0;
577 A(i,1)=steps[imin+i-1].first;
578 A(i,2)=std::pow(A(i,1),2);
579
580 y(i)=steps[imin+i-1].second;
581 }
582
583 arma::mat b;
584 if(arma::solve(b,A,y) && b(2)>sqrt(DBL_EPSILON)) {
585 // Success in solution and parabola gives minimum.
586
587 // The minimum of the parabola is at
588 double x0=-b(1)/(2*b(2));
589
590 // Is this an interpolation?
591 if(A(0,1) < x0 && x0 < A(2,1)) {
592 // Do the calculation with the interpolated step
593 std::pair<double,double> p;
594 p.first=x0;
595 p.second=get_E_adiis(x+sd*p.first);
596 steps.push_back(p);
597
598 // Find the minimum energy
599 find_minE(steps,Emin,imin);
600 }
601 }
602 }
603
604 if(steps[imin].first<DBL_EPSILON)
605 break;
606
607 // Switch to the minimum geometry
608 x+=steps[imin].first*sd;
609 // Store optimal step length
610 steplen=steps[imin].first;
611
612 //printf("Step %i: energy decreased by %e, gradient norm %e\n",(int) iiter+1,steps[imin].second-E,arma::norm(g,2)); fflush(stdout);
613 }
614
615 // Calculate weights
616 return compute_c(x);
617 }
618
get_E_adiis(const arma::vec & x) const619 double DIIS::get_E_adiis(const arma::vec & x) const {
620 // Consistency check
621 if(x.n_elem != PiF.n_elem) {
622 throw std::domain_error("Incorrect number of parameters.\n");
623 }
624
625 arma::vec c(compute_c(x));
626
627 // Compute energy
628 double Eval=0.0;
629 Eval+=2.0*arma::dot(c,PiF);
630 Eval+=arma::as_scalar(arma::trans(c)*PiFj*c);
631
632 return Eval;
633 }
634
get_dEdx_adiis(const arma::vec & x) const635 arma::vec DIIS::get_dEdx_adiis(const arma::vec & x) const {
636 // Compute contraction coefficients
637 arma::vec c(compute_c(x));
638
639 // Compute derivative of energy
640 arma::vec dEdc=2.0*PiF + PiFj*c + arma::trans(PiFj)*c;
641
642 // Compute jacobian of transformation: jac(i,j) = dc_i / dx_j
643 arma::mat jac(compute_jac(x));
644
645 // Finally, compute dEdx by plugging in Jacobian of transformation
646 // dE/dx_i = dc_j/dx_i dE/dc_j
647 return arma::trans(jac)*dEdc;
648 }
649