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