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 "bfprod.h"
18 #include "fourierprod.h"
19 #include "lmtrans.h"
20 #include "momentum_series.h"
21 #include "../basis.h"
22 #include "../basislibrary.h"
23 #include "../dftgrid.h"
24 #include "../dftfuncs.h"
25 #include "../elements.h"
26 #include "../density_fitting.h"
27 #include "../linalg.h"
28 #include "../lmgrid.h"
29 #include "../mathf.h"
30 #include "../scf.h"
31 #include "../settings.h"
32 #include "../stringutil.h"
33 #include "../tempered.h"
34 #include "../timer.h"
35 #include "../xyzutils.h"
36 #include "xrsscf.h"
37 
38 // Needed for libint init
39 #include "../eriworker.h"
40 
41 #include <algorithm>
42 #include <armadillo>
43 #include <cstdio>
44 #include <cstdlib>
45 #include <cfloat>
46 #include <istream>
47 
48 #ifdef _OPENMP
49 #include <omp.h>
50 #endif
51 
52 #ifdef SVNRELEASE
53 #include "../version.h"
54 #endif
55 
56 Settings settings;
57 
58 
59 /**
60  * Was loading a success?
61  *
62  * LOAD_FAIL: Failed to load. (start from scratch)
63  * LOAD_SUCC: Succesfully loaded completed calculation. (no calculation needed)
64  * LOAD_DIFF: Succesfully loaded calculation of a different type. (use as starting point)
65  */
66 enum loadresult {LOAD_FAIL, LOAD_SUCC, LOAD_DIFF};
67 
68 
parse_method(const std::string & method)69 enum xrs_method parse_method(const std::string & method) {
70   enum xrs_method met;
71   if(stricmp(method,"TP")==0)
72     met=TP;
73   else if(stricmp(method,"FCH")==0)
74     met=FCH;
75   else if(stricmp(method,"XCH")==0)
76     met=XCH;
77   else throw std::runtime_error("Unrecognized method.\n");
78 
79   return met;
80 }
81 
82 
83 /// Augment the basis set with diffuse functions
augment_basis(const BasisSet & basis)84 BasisSet augment_basis(const BasisSet & basis) {
85   // Get indices of atoms to augment
86   std::vector<size_t> augind=parse_range(splitline(settings.get_string("XRSAugment"))[0]);
87   // Convert to C++ indexing
88   for(size_t i=0;i<augind.size();i++) {
89     if(augind[i]==0)
90       throw std::runtime_error("Error - nuclear index must be positive!\n");
91     augind[i]--;
92   }
93 
94   bool verbose=settings.get_bool("Verbose");
95   if(verbose) {
96     printf("\nAugmenting basis set with diffuse functions.\n");
97     fflush(stdout);
98   }
99 
100   // Form augmented basis
101   BasisSet augbas(basis);
102 
103   // Basis set for augmentation functions
104   BasisSetLibrary augbaslib;
105   augbaslib.load_basis(settings.get_string("XRSDoubleBasis"));
106 
107   // Loop over excited atoms
108   for(size_t iaug=0;iaug<augind.size();iaug++) {
109     // Index of atom is
110     const size_t ind=augind[iaug];
111 
112     // The symbol of the atom
113     std::string el=basis.get_symbol(ind);
114 
115     // The basis to use for the atom.
116     ElementBasisSet elbas;
117 
118     try {
119       // Check first if a special set is wanted for given center
120       elbas=augbaslib.get_element(el,ind+1);
121     } catch(std::runtime_error & err) {
122       // Did not find a special basis, use the general one instead.
123       elbas=augbaslib.get_element(el,0);
124     }
125 
126     // Get original number of shells
127     size_t Nsh_orig=augbas.get_Nshells();
128     // Add shells, no sorting.
129     augbas.add_shells(ind,elbas,false);
130     // Convert contractions on the added shells
131     for(size_t ish=Nsh_orig;ish<augbas.get_Nshells();ish++)
132       augbas.convert_contraction(ish);
133   }
134 
135   // Finalize augmentation basis
136   augbas.finalize();
137 
138   return augbas;
139 }
140 
141 /**
142  * Double basis set method
143  *
144  * Augment the basis with diffuse functions and diagonalize the Fock
145  * matrix in the unoccupied space. The occupied orbitals and their
146  * energies stay the same in the approximation.
147  */
augmented_solution(const BasisSet & basis,const uscf_t & sol,size_t nocca,size_t noccb,dft_t dft,BasisSet & augbas,arma::mat & Caug,arma::vec & Eaug,bool spin,enum xrs_method method)148 void augmented_solution(const BasisSet & basis, const uscf_t & sol, size_t nocca, size_t noccb, dft_t dft, BasisSet & augbas, arma::mat & Caug, arma::vec & Eaug, bool spin, enum xrs_method method) {
149   Timer ttot;
150 
151   augbas=augment_basis(basis);
152   // Need to update pointers in augbas
153   augbas.update_nuclear_shell_list();
154 
155 
156   // Amount of functions in original basis set is
157   const size_t Nbf=basis.get_Nbf();
158   // Total number of functions in augmented set is
159   const size_t Ntot=augbas.get_Nbf();
160   // Amount of augmentation functions is
161   const size_t Naug=Ntot-Nbf;
162 
163   bool verbose=settings.get_bool("Verbose");
164 
165   if(verbose) {
166     printf("\nAugmented original basis (%i functions) with %i diffuse functions.\n",(int) Nbf,(int) Naug);
167     printf("Computing unoccupied orbitals.\n");
168     fflush(stdout);
169 
170     fprintf(stderr,"Calculating unoccupied orbitals in augmented basis.\n");
171     fflush(stderr);
172   }
173 
174 
175   // Augmented solution
176   uscf_t augsol(sol);
177 
178   // Form density matrix.
179   arma::mat Paaug(Ntot,Ntot), Pbaug(Ntot,Ntot), Paug(Ntot,Ntot);
180 
181   augsol.Pa.zeros(Ntot,Ntot);
182   augsol.Pa.submat(0,0,Nbf-1,Nbf-1)=sol.Pa;
183 
184   augsol.Pb.zeros(Ntot,Ntot);
185   augsol.Pb.submat(0,0,Nbf-1,Nbf-1)=sol.Pb;
186 
187   augsol.P=augsol.Pa+augsol.Pb;
188 
189   augsol.Ca=project_orbitals(sol.Ca,basis,augbas);
190   augsol.Cb=project_orbitals(sol.Cb,basis,augbas);
191 
192   // Checkpoint
193   std::string augchk=settings.get_string("AugChk");
194   bool delchk=false;
195   if(stricmp(augchk,"")==0) {
196     delchk=true;
197     augchk=tempname();
198   }
199 
200   {
201     // Copy base checkpoint to augmented checkpoint
202     std::string chk=settings.get_string("SaveChk");
203     char cmd[chk.size()+augchk.size()+5];
204     sprintf(cmd,"cp %s %s",chk.c_str(),augchk.c_str());
205     int cperr=system(cmd);
206     if(cperr) {
207       ERROR_INFO();
208       throw std::runtime_error("Error copying checkpoint file.\n");
209     }
210   }
211 
212   Timer taug;
213 
214   // Open augmented checkpoint file in write mode, don't truncate it!
215   Checkpoint chkpt(augchk,true,false);
216   // Update the basis set
217   chkpt.write(augbas);
218 
219   {
220     // Initialize solver
221     XRSSCF solver(augbas,chkpt,spin);
222     // The fitting basis set from the non-augmented basis is enough,
223     // since the density is restricted there.
224     BasisSet fitbas=basis.density_fitting();
225     solver.set_fitting(fitbas);
226 
227     // Load occupation number vectors
228     std::vector<double> occa, occb;
229     chkpt.read("occa",occa);
230     chkpt.read("occb",occb);
231 
232     // Construct Fock matrix
233     DFTGrid grid(&augbas,verbose,dft.lobatto);
234     DFTGrid nlgrid(&augbas,verbose,dft.lobatto);
235     if(dft.adaptive) {
236       // Form DFT quadrature grid
237       grid.construct(augsol.Pa,augsol.Pb,dft.gridtol,dft.x_func,dft.c_func);
238     } else {
239       // Fixed size grid
240       bool strictint(settings.get_bool("StrictIntegrals"));
241       grid.construct(dft.nrad,dft.lmax,dft.x_func,dft.c_func,strictint);
242       if(dft.nl)
243 	nlgrid.construct(dft.nlnrad,dft.nllmax,true,false,false,strictint,true);
244     }
245 
246     // Range separated functional?
247     double omega, kfull, kshort;
248     range_separation(dft.x_func,omega,kfull,kshort);
249     // Evaluators for range separated part
250     if(omega!=0.0)
251       solver.fill_rs(omega);
252 
253     switch(method) {
254     case(TP):
255       solver.Fock_half_hole(augsol,dft,occa,occb,grid,nlgrid);
256       break;
257 
258     case(FCH):
259     case(XCH):
260       solver.Fock_full_hole(augsol,dft,occa,occb,grid,nlgrid,method==XCH);
261     }
262   }
263 
264   if(verbose) {
265     printf("Fock operator constructed in augmented space in %s.\n",taug.elapsed().c_str());
266     fprintf(stderr,"Constructed augmented Fock operator (%s).\n",taug.elapsed().c_str());
267   }
268 
269   // Diagonalize unoccupied space. Loop over spin
270   for(size_t ispin=0;ispin<2;ispin++) {
271 
272     // Form Fock operator
273     arma::mat H;
274     if(!ispin)
275       H=augsol.Ha;
276     else
277       H=augsol.Hb;
278 
279     arma::mat AOtoO;
280     if(!ispin)
281       AOtoO=project_orbitals(sol.Ca,basis,augbas);
282     else
283       AOtoO=project_orbitals(sol.Cb,basis,augbas);
284 
285     // Amount of occupied orbitals
286     size_t nocc;
287     if(!ispin)
288       nocc=nocca;
289     else
290       nocc=noccb;
291 
292     // Convert Fock operator to unoccupied MO basis.
293     taug.set();
294     arma::mat H_MO=arma::trans(AOtoO.cols(nocc,AOtoO.n_cols-1))*H*AOtoO.cols(nocc,AOtoO.n_cols-1);
295     if(verbose) {
296       printf("H_MO formed in %s.\n",taug.elapsed().c_str());
297       fflush(stdout);
298     }
299 
300     // Diagonalize Fockian to find orbitals and energies
301     taug.set();
302     arma::vec Eval;
303     arma::mat Evec;
304     eig_sym_ordered(Eval,Evec,H_MO);
305 
306     if(verbose) {
307       printf("H_MO diagonalized in unoccupied subspace in %s.\n",taug.elapsed().c_str());
308       fflush(stdout);
309     }
310 
311     // Store energies
312     arma::vec Ea;
313     Ea.zeros(AOtoO.n_cols);
314     // Occupied orbital energies
315     if(!spin)
316       Ea.subvec(0,nocc-1)=sol.Ea.subvec(0,nocc-1);
317     else
318       Ea.subvec(0,nocc-1)=sol.Eb.subvec(0,nocc-1);
319     // Virtuals
320     Ea.subvec(nocc,AOtoO.n_cols-1)=Eval;
321 
322     // Back-transform orbitals to AO basis
323     arma::mat Ca;
324     Ca.zeros(Ntot,AOtoO.n_cols);
325     // Occupied orbitals, padded with zeros
326     Ca.submat(0,0,Nbf-1,nocc-1)=AOtoO.submat(0,0,Nbf-1,nocc-1);
327     // Unoccupied orbitals
328     Ca.cols(nocc,AOtoO.n_cols-1)=AOtoO.cols(nocc,AOtoO.n_cols-1)*Evec;
329 
330     // Save results
331     if(!ispin) {
332       augsol.Ca=Ca;
333       augsol.Ea=Ea;
334     } else {
335       augsol.Cb=Ca;
336       augsol.Eb=Ea;
337     }
338 
339     // Return variables
340     if((bool) ispin==spin) {
341       Eaug=Ea;
342       Caug=Ca;
343     }
344   }
345 
346   // Write out updated solution vectors
347   chkpt.write("Ca",augsol.Ca);
348   chkpt.write("Cb",augsol.Cb);
349   chkpt.write("Ea",augsol.Ea);
350   chkpt.write("Eb",augsol.Eb);
351   // and the density matrix
352   chkpt.write("Pa",augsol.Pa);
353   chkpt.write("Pb",augsol.Pb);
354   chkpt.write("P",augsol.P);
355   // and the energy
356   chkpt.write(augsol.en);
357 
358   if(delchk) {
359     int err=remove(augchk.c_str());
360     if(err) {
361       ERROR_INFO();
362       throw std::runtime_error("Error removing temporary file.\n");
363     }
364   }
365 
366   if(verbose) {
367     printf("Augmentation took %s.\n",ttot.elapsed().c_str());
368     fprintf(stderr,"Augmentation done (%s).\n",taug.elapsed().c_str());
369     fflush(stdout);
370     fflush(stderr);
371   }
372 }
373 
374 typedef struct {
375   // Transition energy
376   double E;
377   // Total oscillator strength
378   double w;
379   // Decomposition of strength
380   std::vector<double> wdec;
381 } spectrum_t;
382 
383 /// Compute transitions to unoccupied states.
compute_transitions(const BasisSet & basis,const arma::mat & C,const arma::vec & E,size_t iat,size_t ixc,size_t nocc)384 std::vector<spectrum_t> compute_transitions(const BasisSet & basis, const arma::mat & C, const arma::vec & E, size_t iat, size_t ixc, size_t nocc) {
385   // Returned array
386   std::vector<spectrum_t> ret;
387 
388   // Coordinates of excited atom
389   coords_t xccen=basis.get_nuclear_coords(iat);
390   // Dipole moment matrix
391   std::vector<arma::mat> mom1=basis.moment(1,xccen.x,xccen.y,xccen.z);
392   // Compute RHS of transition
393   arma::vec rhs_x=mom1[getind(1,0,0)]*C.col(ixc);
394   arma::vec rhs_y=mom1[getind(0,1,0)]*C.col(ixc);
395   arma::vec rhs_z=mom1[getind(0,0,1)]*C.col(ixc);
396 
397   for(size_t ix=nocc;ix<C.n_cols;ix++) {
398     spectrum_t sp;
399     // Transition energy is
400     sp.E=(E[ix]-E[ixc]);
401 
402     // Compute oscillator strengths in x, y and z
403     double wx=arma::dot(rhs_x,C.col(ix));
404     double wy=arma::dot(rhs_y,C.col(ix));
405     double wz=arma::dot(rhs_z,C.col(ix));
406 
407     // Spherical average of oscillator strength in XRS is
408     sp.w=2.0/3.0*(wx*wx + wy*wy + wz*wz);
409     // Store decomposition
410     sp.wdec.push_back(wx);
411     sp.wdec.push_back(wy);
412     sp.wdec.push_back(wz);
413 
414     ret.push_back(sp);
415   }
416 
417   return ret;
418 }
419 
compute_qdep_transitions_series(const BasisSet & basis,const arma::mat & C,const arma::vec & E,size_t ixc,size_t nocc,std::vector<double> qval)420 std::vector< std::vector<spectrum_t> > compute_qdep_transitions_series(const BasisSet & basis, const arma::mat & C, const arma::vec & E, size_t ixc, size_t nocc, std::vector<double> qval) {
421   Timer t;
422 
423   // Get the grid for computing the spherical averages.
424   std::vector<angular_grid_t> grid=form_angular_grid(2*basis.get_max_am());
425   // We normalize the weights so that for purely dipolar transitions we
426   // get the same output as with using the dipole matrix.
427   for(size_t i=0;i<grid.size();i++) {
428     // Dipole integral is only wrt theta - divide off phi part.
429     grid[i].w/=2.0*M_PI;
430   }
431 
432   // Amount of transitions is
433   size_t Ntrans=C.n_cols-nocc;
434 
435   // Initialize return array
436   std::vector< std::vector<spectrum_t> > ret(qval.size());
437   for(size_t iq=0;iq<qval.size();iq++) {
438     ret[iq].resize(Ntrans);
439     for(size_t ix=nocc;ix<C.n_cols;ix++) {
440       // Transition energy is
441       ret[iq][ix-nocc].E=(E[ix]-E[ixc]);
442       // Transition speed is
443       ret[iq][ix-nocc].w=0.0;
444     }
445   }
446 
447   // Copy the orbitals to complex form.
448   arma::cx_mat Cm(C.n_rows,Ntrans);
449   for(size_t ir=0;ir<C.n_rows;ir++)
450     for(size_t ix=nocc;ix<C.n_cols;ix++)
451       Cm(ir,ix-nocc)=C(ir,ix);
452 
453   printf("Computing transitions using series method.\n");
454   arma::cx_vec tmp;
455 
456   // Evaluator
457   momentum_transfer_series ser(&basis);
458 
459   // Loop over q values.
460   for(size_t iq=0;iq<qval.size();iq++) {
461     printf("\tq = %8.3f ... ",qval[iq]);
462     fflush(stdout);
463     Timer tq;
464 
465     // Loop over angular mesh
466     for(size_t ig=0;ig<grid.size();ig++) {
467 
468       // Current q is
469       arma::vec q(3);
470       q(0)=qval[iq]*grid[ig].r.x;
471       q(1)=qval[iq]*grid[ig].r.y;
472       q(2)=qval[iq]*grid[ig].r.z;
473       // and the weight is
474       double w=grid[ig].w;
475 
476       // Get momentum transfer matrix
477       arma::cx_mat momtrans=ser.get(q,10*DBL_EPSILON,10*DBL_EPSILON);
478       // Contract with initial state
479       tmp=momtrans*C.col(ixc);
480 
481       // Compute matrix elements for all transitions
482 #ifdef _OPENMP
483 #pragma omp parallel for
484 #endif
485       // Loop over transitions
486       for(size_t it=0;it<Ntrans;it++) {
487 	// The matrix element is
488 	std::complex<double> el=arma::dot(tmp,Cm.col(it));
489 	// so the transition velocity is increased by
490 	ret[iq][it].w+=w*std::norm(el);
491       }
492     }
493 
494     printf("done (%s)\n",tq.elapsed().c_str());
495   }
496 
497   return ret;
498 }
499 
500 
compute_qdep_transitions_fourier(const BasisSet & basis,const arma::mat & C,const arma::vec & E,size_t ixc,size_t nocc,std::vector<double> qval)501 std::vector< std::vector<spectrum_t> > compute_qdep_transitions_fourier(const BasisSet & basis, const arma::mat & C, const arma::vec & E, size_t ixc, size_t nocc, std::vector<double> qval) \
502 {
503   Timer t;
504 
505   printf("Computing transitions using Fourier method.\n");
506 
507   // Form products of basis functions.
508   const size_t Nbf=basis.get_Nbf();
509 
510   std::vector<prod_gaussian_3d> bfprod=compute_products(basis);
511 
512   printf("Products done in %s.\n",t.elapsed().c_str());
513   t.set();
514 
515   // Form Fourier transforms of the products.
516   std::vector<prod_fourier> bffour=fourier_transform(bfprod);
517   printf("Fourier transform done in %s.\n",t.elapsed().c_str());
518   t.set();
519 
520   // Get the grid for computing the spherical averages.
521   std::vector<angular_grid_t> grid=form_angular_grid(2*basis.get_max_am());
522   // We normalize the weights so that for purely dipolar transitions we
523   // get the same output as with using the dipole matrix.
524   for(size_t i=0;i<grid.size();i++) {
525     // Dipole integral is only wrt theta - divide off phi part.
526     grid[i].w/=2.0*M_PI;
527   }
528 
529   // Amount of transitions is
530   size_t Ntrans=C.n_cols-nocc;
531 
532   // Initialize return array
533   std::vector< std::vector<spectrum_t> > ret(qval.size());
534   for(size_t iq=0;iq<qval.size();iq++) {
535     ret[iq].resize(Ntrans);
536     for(size_t ix=nocc;ix<C.n_cols;ix++) {
537       // Transition energy is
538       ret[iq][ix-nocc].E=(E[ix]-E[ixc]);
539       // Transition speed is
540       ret[iq][ix-nocc].w=0.0;
541     }
542   }
543 
544   // Copy the orbitals to complex form.
545   arma::cx_mat Cm(C.n_rows,Ntrans);
546   for(size_t ir=0;ir<C.n_rows;ir++)
547     for(size_t ix=nocc;ix<C.n_cols;ix++)
548       Cm(ir,ix-nocc)=C(ir,ix);
549 
550   // Loop over q values.
551   arma::cx_vec tmp;
552 
553   for(size_t iq=0;iq<qval.size();iq++) {
554     printf("\tq = %8.3f ... ",qval[iq]);
555     fflush(stdout);
556     Timer tq;
557 
558     // Loop over angular mesh
559     for(size_t ig=0;ig<grid.size();ig++) {
560 
561       // Current q is
562       arma::vec q(3);
563       q(0)=qval[iq]*grid[ig].r.x;
564       q(1)=qval[iq]*grid[ig].r.y;
565       q(2)=qval[iq]*grid[ig].r.z;
566       // and the weight is
567       double w=grid[ig].w;
568 
569       // Get momentum transfer matrix
570       arma::cx_mat momtrans=momentum_transfer(bffour,Nbf,q);
571 
572       // Contract with initial state
573       tmp=momtrans*C.col(ixc);
574 
575       // Compute matrix elements for all transitions
576 #ifdef _OPENMP
577 #pragma omp parallel for
578 #endif
579       // Loop over transitions
580       for(size_t it=0;it<Ntrans;it++) {
581 	// The matrix element is
582 	std::complex<double> el=arma::dot(tmp,Cm.col(it));
583 	// so the transition velocity is increased by
584 	ret[iq][it].w+=w*std::norm(el);
585       }
586     }
587 
588     printf("done (%s)\n",tq.elapsed().c_str());
589   }
590 
591   return ret;
592 }
593 
594 
compute_qdep_transitions_local(const BasisSet & basis,const arma::mat & C,const arma::vec & E,size_t iat,size_t ixc,size_t nocc,std::vector<double> q)595 std::vector< std::vector<spectrum_t> > compute_qdep_transitions_local(const BasisSet & basis, const arma::mat & C, const arma::vec & E, size_t iat, size_t ixc, size_t nocc, std::vector<double> q) {
596   // Get wanted quadrature info
597   int Nrad=settings.get_int("XRSNrad");
598   int Lmax=settings.get_int("XRSLmax");
599   int Lquad=settings.get_int("XRSLquad");
600 
601   if(Lquad<Lmax) {
602     Lquad=Lmax;
603     fprintf(stderr,"Increasing the quadrature order to Lmax.\n");
604   }
605 
606   // Do lm expansion of orbitals
607   lmtrans lm(C,basis,basis.get_nuclear_coords(iat),Nrad,Lmax,Lquad);
608 
609   printf("\n");
610   lm.print_info();
611   printf("\n");
612 
613   // Save radial distribution of excited orbital
614   lm.write_prob(ixc,"excited_orb.dat");
615 
616   // Returned array
617   std::vector< std::vector<spectrum_t> > ret(q.size());
618   for(size_t iq=0;iq<q.size();iq++)
619     ret[iq].resize(C.n_cols-nocc);
620 
621   // Loop over q
622   printf("Computing transitions.\n");
623   for(size_t iq=0;iq<q.size();iq++) {
624     printf("\tq = %8.3f ... ",q[iq]);
625     fflush(stdout);
626     Timer tq;
627 
628     // Compute Bessel functions
629     bessel_t bes=lm.compute_bessel(q[iq]);
630 
631     // Loop over transitions
632 #ifdef _OPENMP
633 #pragma omp parallel for schedule(dynamic,1)
634 #endif
635     for(size_t ix=nocc;ix<C.n_cols;ix++) {
636       Timer ttrans;
637 
638       spectrum_t sp;
639       // Transition energy is
640       sp.E=(E[ix]-E[ixc]);
641 
642       // Get oscillator strength
643       std::vector<double> osc=lm.transition_velocity(ixc,ix,bes);
644       // Total strength is
645       sp.w=osc[0];
646       // Store the rest
647       osc.erase(osc.begin());
648       sp.wdec=osc;
649 
650       // Store result
651       ret[iq][ix-nocc]=sp;
652 
653       //      printf("\t%3i -> %3i (%s)\n",(int) ixc+1,(int) ix+1,ttrans.elapsed().c_str());
654     }
655 
656     printf("done (%s)\n",tq.elapsed().c_str());
657   }
658 
659 
660   return ret;
661 }
662 
save_spectrum(const std::vector<spectrum_t> & sp,const char * fname="dipole.dat")663 void save_spectrum(const std::vector<spectrum_t> & sp, const char *fname="dipole.dat") {
664   // Compute transitions
665   FILE *out=fopen(fname,"w");
666 
667   for(size_t i=0;i<sp.size();i++) {
668     // Print out energy in eV oscillator strength and its components
669     fprintf(out,"%e %e",sp[i].E*HARTREEINEV,sp[i].w);
670     for(size_t j=0;j<sp[i].wdec.size();j++)
671       fprintf(out," % e",sp[i].wdec[j]);
672     fprintf(out,"\n");
673   }
674   fclose(out);
675 }
676 
save_xas_spectrum(const std::vector<spectrum_t> & sp,const char * fname="dipole_xas.dat")677 void save_xas_spectrum(const std::vector<spectrum_t> & sp, const char *fname="dipole_xas.dat") {
678   // Compute transitions
679   FILE *out=fopen(fname,"w");
680 
681   for(size_t i=0;i<sp.size();i++) {
682     // Print out energy in eV and transition amplitude
683     fprintf(out,"%e %e\n",sp[i].E*HARTREEINEV,sp[i].E*sp[i].w);
684   }
685   fclose(out);
686 }
687 
load(const BasisSet & basis,Checkpoint & chkpt,uscf_t & sol,arma::vec & core)688 enum loadresult load(const BasisSet & basis, Checkpoint & chkpt, uscf_t & sol, arma::vec & core) {
689   // Was the load a success?
690   enum loadresult ok=LOAD_SUCC;
691 
692   // Basis set used in the checkpoint file
693   BasisSet loadbas;
694 
695   try {
696     chkpt.read("Ca",sol.Ca);
697     chkpt.read("Cb",sol.Cb);
698     chkpt.read("Ea",sol.Ea);
699     chkpt.read("Eb",sol.Eb);
700     chkpt.read("Pa",sol.Pa);
701     chkpt.read("Pb",sol.Pb);
702 
703     chkpt.read(loadbas);
704   } catch(std::runtime_error & err) {
705     ok=LOAD_FAIL;
706     //    fprintf(stderr,"Loading failed due to \"%s\".\n",err.what());
707   }
708 
709   if(ok) {
710     // Check consistency
711     if(!(basis==loadbas)) {
712       ok=LOAD_FAIL;
713       //      fprintf(stderr,"Basis sets differ!\n");
714     }
715   }
716 
717   if(ok) {
718     // Get number of basis functions
719     size_t Nbf=basis.get_Nbf();
720 
721     if(sol.Ca.n_rows != Nbf)
722       ok=LOAD_FAIL;
723 
724     if(sol.Cb.n_rows != Nbf)
725       ok=LOAD_FAIL;
726 
727     if(sol.Ea.n_elem != sol.Ca.n_cols)
728       ok=LOAD_FAIL;
729 
730     if(sol.Eb.n_elem != sol.Cb.n_cols)
731       ok=LOAD_FAIL;
732 
733     if(sol.Ea.n_elem != sol.Eb.n_elem)
734       ok=LOAD_FAIL;
735 
736     if(sol.Pa.n_rows != Nbf || sol.Pa.n_cols != Nbf)
737       ok=LOAD_FAIL;
738 
739     if(sol.Pb.n_rows != Nbf || sol.Pb.n_cols != Nbf)
740       ok=LOAD_FAIL;
741 
742     //    if(!ok) fprintf(stderr,"Dimensions do not match!\n");
743   }
744 
745   // Was the calculation converged?
746   if(ok) {
747     bool conv;
748     chkpt.read("Converged",conv);
749 
750     if(!conv) {
751       //      fprintf(stderr,"Calculation was not converged.\n");
752       ok=LOAD_FAIL;
753     }
754   }
755 
756   // Check consistency of spin
757   if(ok) {
758     bool spin;
759     chkpt.read("XRSSpin",spin);
760     if(spin!=settings.get_bool("XRSSpin")) {
761       //      fprintf(stderr,"Excited spin does not match.\n");
762       ok=LOAD_FAIL;
763     }
764   }
765 
766   // Check consistency of method
767   if(ok) {
768     std::string method;
769     chkpt.read("XRSMethod",method);
770     if(stricmp(method,settings.get_string("XRSMethod"))!=0) {
771       ok=LOAD_DIFF;
772       //      fprintf(stderr,"Calculation methods do not match.\n");
773     }
774   }
775 
776   if(ok) {
777     // Everything is OK, finish by loading initial state core orbital
778     if(chkpt.exist("Ccore")) {
779       printf("Loaded initial state orbital from checkpoint.\n");
780       chkpt.read("Ccore",core);
781     } else
782       // Failed to read core orbital
783       ok=LOAD_FAIL;
784 
785   } else {
786     // Failed to load or solution was not consistent.
787     sol.Ca=arma::mat();
788     sol.Cb=arma::mat();
789     sol.Ea=arma::vec();
790     sol.Eb=arma::vec();
791     sol.Pa=arma::mat();
792     sol.Pb=arma::mat();
793   }
794 
795   return ok;
796 }
797 
print_header()798 void print_header() {
799 #ifdef _OPENMP
800   printf("ERKALE - XRS from Hel, OpenMP version, running on %i cores.\n",omp_get_max_threads());
801 #else
802   printf("ERKALE - XRS from Hel, serial version.\n");
803 #endif
804   print_copyright();
805   print_license();
806 #ifdef SVNRELEASE
807   printf("At svn revision %s.\n\n",SVNREVISION);
808 #endif
809   print_hostname();
810 }
811 
main_guarded(int argc,char ** argv)812 int main_guarded(int argc, char **argv) {
813   print_header();
814 
815   if(argc!=2) {
816     printf("Usage: $ %s runfile\n",argv[0]);
817     return 0;
818   }
819 
820   // Initialize libint
821   init_libint_base();
822 
823   Timer t;
824   t.print_time();
825 
826   // Parse settings
827   settings.add_scf_settings();
828 
829   // Change default log file
830   settings.set_string("Logfile","erkale_xrs.log");
831 
832   // Use convergence settings similar to StoBe. XCH calculations
833   // are hard to converge to the otherwise default settings.
834   settings.set_double("ConvThr",1e-5);
835   settings.set_double("DFTDelta",100);
836 
837   // Add xrs specific settings
838   settings.add_string("LoadChk","Initialize with ground state calculation from file","");
839   settings.add_string("SaveChk","Try initializing with and save results to file","erkale_xrs.chk");
840   settings.add_string("AugChk","Save augmented calculation to file (if applicable)","erkale_xrs_aug.chk");
841 
842   settings.add_string("XRSDoubleBasis","The augmentation basis to use for double-basis set calculations","X-AUTO");
843 
844   settings.add_bool("XRSSpin","Spin to excite (false for alpha, true for beta)",false);
845   settings.add_string("XRSInitialState","Initial atomic state to excite","1s");
846   settings.add_int("XRSInitialOrbital","Index of orbital in state to excite", 1);
847   settings.add_string("XRSMethod", "Which kind of calculation to perform: TP, XCH or FCH","TP");
848   settings.add_string("XRSAugment","Which atoms to augment with diffuse functions? E.g. 1,3:5,10","");
849   settings.add_string("XRSQval","List or range of Q values to compute","");
850   settings.add_string("XRSQMethod","Method of computing momentum transfer matrix: Local, Fourier or Series","Fourier");
851 
852   settings.add_int("XRSNrad","Local: how many point to use in radial integration",200);
853   settings.add_int("XRSLmax","Local: expand orbitals up to Lmax",5);
854   settings.add_int("XRSLquad","Local: perform angular expansion using quadrature of Lquad order",30);
855 
856   settings.parse(std::string(argv[1]),true);
857 
858   // Redirect output?
859   std::string logfile=settings.get_string("Logfile");
860   if(stricmp(logfile,"stdout")!=0) {
861     // Redirect stdout to file
862     FILE *outstream=freopen(logfile.c_str(),"w",stdout);
863     if(outstream==NULL) {
864       ERROR_INFO();
865       throw std::runtime_error("Unable to redirect output!\n");
866     }
867 
868     // Print out the header in the logfile too
869     print_header();
870     fprintf(stderr,"\n");
871   }
872 
873   // Get used settings
874   const bool verbose=settings.get_bool("Verbose");
875   const bool spin=settings.get_bool("XRSSpin");
876 
877   // Parse method
878   enum xrs_method method=parse_method(settings.get_string("XRSMethod"));
879 
880   // Print out settings
881   if(verbose)
882     settings.print();
883 
884   // Read in atoms.
885   std::vector<atom_t> atoms;
886   std::string atomfile=settings.get_string("System");
887   if(file_exists(atomfile))
888     atoms=load_xyz(atomfile,!settings.get_bool("InputBohr"));
889   else {
890     // Check if a directory has been set
891     char * libloc=getenv("ERKALE_SYSDIR");
892     if(libloc) {
893       std::string filename=std::string(libloc)+"/"+atomfile;
894       if(file_exists(filename))
895 	atoms=load_xyz(filename,!settings.get_bool("InputBohr"));
896       else
897 	throw std::runtime_error("Unable to open xyz input file!\n");
898     } else
899       throw std::runtime_error("Unable to open xyz input file!\n");
900   }
901 
902   // Get index of excited atom
903   size_t xcatom=get_excited_atom_idx(atoms);
904 
905   // Read in basis set
906   BasisSetLibrary baslib;
907   std::string basfile=settings.get_string("Basis");
908   baslib.load_basis(basfile);
909 
910   // Construct basis set
911   BasisSet basis;
912   construct_basis(basis,atoms,baslib);
913 
914   // Get exchange and correlation functionals and grid settings
915   dft_t dft_init(parse_dft(true));
916   dft_t dft(parse_dft(false));
917 
918   // Final convergence settings
919   double convthr(settings.get_double("ConvThr"));
920 
921   // Make initialization parameters more relaxed
922   double initfac=settings.get_double("DFTDelta");
923   double init_convthr(convthr*initfac);
924 
925   // TP solution
926   uscf_t sol;
927   sol.en.E=0.0;
928 
929   // Index of excited orbital
930   size_t xcorb;
931 
932   // Number of occupied states
933   int nocca, noccb;
934   get_Nel_alpha_beta(basis.Ztot()-settings.get_int("Charge"),settings.get_int("Multiplicity"),nocca,noccb);
935 
936   // Ground state energy
937   energy_t gsen;
938   bool didgs=false;
939 
940   // Initial state
941   std::string state=settings.get_string("XRSInitialState");
942   // Initial orbital
943   int iorb=settings.get_int("XRSInitialOrbital")-1;
944 
945   // Try to load orbitals and energies
946   arma::vec core;
947   enum loadresult loadok=LOAD_FAIL;
948   if(file_exists(settings.get_string("SaveChk"))) {
949     Checkpoint testload(settings.get_string("SaveChk"),false);
950     loadok=load(basis,testload,sol,core);
951 
952     if(loadok==LOAD_SUCC)
953       fprintf(stderr,"Loaded converged calculation from checkpoint file.\n");
954     else if(loadok==LOAD_DIFF) {
955       std::string met;
956       testload.read("XRSMethod",met);
957       fprintf(stderr,"Initializing with previous %s calculation.\n",met.c_str());
958     } else
959       fprintf(stderr,"No converged calculation found in SaveChk.\n");
960 
961     // Can we get a ground state energy from the checkpoint file?
962     if(loadok==LOAD_DIFF && testload.exist("E_GS")) {
963       testload.read("E_GS",gsen.E);
964       didgs=true;
965     }
966   }
967 
968   // No existing calculation found or system was different => perform calculation
969   if(loadok==LOAD_DIFF || loadok==LOAD_FAIL) {
970 
971     // Create checkpoint
972     Checkpoint chkpt(settings.get_string("SaveChk"),true);
973     chkpt.write(basis);
974     chkpt.write("XRSSpin",settings.get_bool("XRSSpin"));
975     chkpt.write("XRSMethod",settings.get_string("XRSMethod"));
976 
977     // Index of core orbital
978     int icore=-1;
979 
980     // Initialize calculation with ground state if necessary
981     if(loadok==LOAD_FAIL) {
982       if(settings.get_string("LoadChk").size()==0)
983 	throw std::runtime_error("Need a ground-state calculation in LoadChk to do calculation!\n");
984 
985       printf("Initializing with ground-state calculation from %s.\n",settings.get_string("LoadChk").c_str());
986 
987       // Read checkpoint file
988       Checkpoint load(settings.get_string("LoadChk"),false);
989 
990       // Restricted calculation?
991       bool restr;
992       load.read("Restricted",restr);
993 
994       // Load ground-state energy
995       load.read(gsen);
996       didgs=true;
997 
998       // Load basis
999       BasisSet oldbas;
1000       load.read(oldbas);
1001 
1002       // Check that basis sets are the same
1003       if(!(basis == oldbas))
1004 	throw std::runtime_error("Must use the same basis for all phases of XRS calculation!\n");
1005 
1006       // Read orbitals
1007       if(restr) {
1008 	load.read("C",sol.Ca);
1009 	load.read("E",sol.Ea);
1010 	load.read("H",sol.Ha);
1011 	load.read("P",sol.P);
1012 	sol.Eb=sol.Ea;
1013 	sol.Cb=sol.Ca;
1014 	sol.Hb=sol.Ha;
1015 	sol.Pa=sol.Pb=sol.P/2.0;
1016       } else {
1017 	// Load energies and orbitals
1018 	load.read("Ca",sol.Ca);
1019 	load.read("Ea",sol.Ea);
1020 	load.read("Ha",sol.Ha);
1021 	load.read("Pa",sol.Pa);
1022 	load.read("Cb",sol.Cb);
1023 	load.read("Eb",sol.Eb);
1024 	load.read("Hb",sol.Hb);
1025 	load.read("Pb",sol.Pb);
1026 	load.read("P",sol.P);
1027       }
1028 
1029       // Determine orbitals, update energies and densities
1030       if(spin) {
1031 	icore=localize(basis,noccb,xcatom,sol.Cb,state,iorb);
1032 	sol.Eb=arma::diagvec(arma::trans(sol.Cb)*sol.Hb*sol.Cb);
1033 
1034 	std::vector<double> occb;
1035 	if(method==XCH)
1036 	  occb=xch_occ(icore,noccb);
1037 	else if(method==FCH)
1038 	  occb=fch_occ(icore,noccb);
1039 	else if(method==TP)
1040 	  occb=tp_occ(icore,noccb);
1041 	sol.Pb=form_density(sol.Cb,occb);
1042 
1043       } else {
1044 	icore=localize(basis,nocca,xcatom,sol.Ca,state,iorb);
1045 	sol.Ea=arma::diagvec(arma::trans(sol.Ca)*sol.Ha*sol.Ca);
1046 
1047 	std::vector<double> occa;
1048 	if(method==XCH)
1049 	  occa=xch_occ(icore,nocca);
1050 	else if(method==FCH)
1051 	  occa=fch_occ(icore,nocca);
1052 	else if(method==TP)
1053 	  occa=tp_occ(icore,nocca);
1054 	sol.Pa=form_density(sol.Ca,occa);
1055       }
1056       sol.P=sol.Pa+sol.Pb;
1057     }
1058 
1059     // Proceed with TP calculation. Initialize solver
1060     XRSSCF solver(basis,chkpt,spin);
1061 
1062     // Set initial state core orbital
1063     if(loadok==LOAD_FAIL) { // LOAD_FAIL
1064       if(spin)
1065 	solver.set_core(sol.Cb.col(icore));
1066       else
1067 	solver.set_core(sol.Ca.col(icore));
1068     } else
1069       // LOAD_DIFF
1070       solver.set_core(core);
1071 
1072     // Write number of electrons to file
1073     chkpt.write("Nel",nocca+noccb);
1074     chkpt.write("Nel-a",nocca);
1075     chkpt.write("Nel-b",noccb);
1076 
1077     // Store core orbital
1078     chkpt.write("Ccore",solver.get_core());
1079 
1080     // Do we have the ground state energy?
1081     if(didgs)
1082       chkpt.write("E_GS",gsen.E);
1083 
1084     // Do calculation
1085     if(method==FCH || method==XCH) {
1086       if(dft.adaptive)
1087 	solver.full_hole(sol,init_convthr,dft_init,method==XCH);
1088       xcorb=solver.full_hole(sol,convthr,dft,method==XCH);
1089 
1090       // Get excited state energy
1091       energy_t excen;
1092       chkpt.read(excen);
1093 
1094       // Do we have a ground state energy?
1095       if(didgs) {
1096 	if(method==XCH) {
1097 	  printf("\nAbsolute energy correction: first peak should be at %.2f eV.\n",(excen.E-gsen.E)*HARTREEINEV);
1098 	  fprintf(stderr,"\nAbsolute energy correction: first peak should be at %.2f eV.\n",(excen.E-gsen.E)*HARTREEINEV);
1099 	} else {
1100 	  printf("Vertical photoionization energy is %.2f eV.\n",(excen.E-gsen.E)*HARTREEINEV);
1101 	  fprintf(stderr,"Vertical photoionization energy is %.2f eV.\n",(excen.E-gsen.E)*HARTREEINEV);
1102 	}
1103       } else {
1104 	printf("Cannot estimate excitation energy without a ground state calculation.\n");
1105 	fprintf(stderr,"Cannot estimate excitation energy without a ground state calculation.\n");
1106       }
1107     } else {
1108       if(dft.adaptive)
1109 	solver.half_hole(sol,init_convthr,dft_init);
1110       xcorb=solver.half_hole(sol,convthr,dft);
1111     }
1112     printf("\n\n");
1113 
1114   } else {
1115     // Loaded calculation, just find excited state
1116 
1117     if(spin)
1118       xcorb=find_excited_orb(basis,core,sol.Cb,noccb);
1119     else
1120       xcorb=find_excited_orb(basis,core,sol.Ca,nocca);
1121   }
1122 
1123   // Augment the solutions if necessary
1124   BasisSet augbas;
1125   arma::mat C_aug;
1126   arma::vec E_aug;
1127 
1128   if(stricmp(settings.get_string("XRSAugment"),"")!=0)
1129     augmented_solution(basis,sol,nocca,noccb,dft,augbas,C_aug,E_aug,spin,method);
1130   else {
1131     // No augmentation necessary, just copy the solutions
1132     augbas=basis;
1133     if(spin) {
1134       C_aug=sol.Cb;
1135       E_aug=sol.Eb;
1136     } else {
1137       C_aug=sol.Ca;
1138       E_aug=sol.Ea;
1139     }
1140   }
1141 
1142   // Number of occupied states
1143   size_t nocc;
1144   if(spin)
1145     nocc=noccb;
1146   else
1147     nocc=nocca;
1148 
1149 
1150   // Compute dipole transitions
1151   std::vector<spectrum_t> sp=compute_transitions(augbas,C_aug,E_aug,xcatom,xcorb,nocc);
1152   // Save spectrum
1153   save_spectrum(sp);
1154   save_xas_spectrum(sp);
1155 
1156   // Get values of q to compute for
1157   std::vector<double> qvals=parse_range_double(settings.get_string("XRSQval"));
1158 
1159   // The q-dependent spectra
1160   std::vector< std::vector<spectrum_t> > qsp;
1161   // The filename
1162   std::string spname;
1163 
1164   if(qvals.size()) {
1165     // Series method
1166     if(stricmp(settings.get_string("XRSQMethod"),"Series")==0) {
1167       qsp=compute_qdep_transitions_series(augbas,C_aug,E_aug,xcorb,nocc,qvals);
1168       spname="trans_ser";
1169     }
1170 
1171     // Fourier method
1172     if(stricmp(settings.get_string("XRSQMethod"),"Fourier")==0) {
1173       qsp=compute_qdep_transitions_fourier(augbas,C_aug,E_aug,xcorb,nocc,qvals);
1174       spname="trans_four";
1175     }
1176 
1177     // Local method (Sakko et al)
1178     if(stricmp(settings.get_string("XRSQMethod"),"Local")==0) {
1179       qsp=compute_qdep_transitions_local(augbas,C_aug,E_aug,xcatom,xcorb,nocc,qvals);
1180       spname="trans_loc";
1181     }
1182 
1183     // Save transitions
1184     for(size_t i=0;i<qvals.size();i++) {
1185       char fname[80];
1186       sprintf(fname,"%s-%.2f.dat",spname.c_str(),qvals[i]);
1187       save_spectrum(qsp[i],fname);
1188     }
1189   }
1190 
1191   if(verbose) {
1192     printf("\nRunning program took %s.\n",t.elapsed().c_str());
1193     t.print_time();
1194   }
1195 
1196   return 0;
1197 }
1198 
main(int argc,char ** argv)1199 int main(int argc, char **argv) {
1200   try {
1201     return main_guarded(argc, argv);
1202   } catch (const std::exception &e) {
1203     std::cerr << "error: " << e.what() << std::endl;
1204     return 1;
1205   }
1206 }
1207