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