1 /*
2 This file is part of MADNESS.
3
4 Copyright (C) 2007,2010 Oak Ridge National Laboratory
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20 For more information please contact:
21
22 Robert J. Harrison
23 Oak Ridge National Laboratory
24 One Bethel Valley Road
25 P.O. Box 2008, MS-6367
26
27 email: harrisonrj@ornl.gov
28 tel: 865-241-3937
29 fax: 865-572-0680
30 */
31
32 //#define WORLD_INSTANTIATE_STATIC_TEMPLATES
33
34 /*!
35 \file examples/nemo.h
36 \brief solve the HF equations using numerical exponential MOs
37
38 The source is
39 <a href=http://code.google.com/p/m-a-d-n-e-s-s/source/browse/local
40 /trunk/src/apps/examples/nemo.h>here</a>.
41
42 */
43
44 #ifndef NEMO_H_
45 #define NEMO_H_
46
47 #include <madness/mra/mra.h>
48 #include <madness/mra/funcplot.h>
49 #include <madness/mra/operator.h>
50 #include <madness/mra/lbdeux.h>
51 #include <chem/SCF.h>
52 #include <chem/SCFProtocol.h>
53 #include <chem/correlationfactor.h>
54 #include <chem/molecular_optimizer.h>
55 #include <madness/mra/nonlinsol.h>
56 #include <madness/mra/vmra.h>
57 #include <chem/pcm.h>
58 #include <chem/AC.h>
59
60 namespace madness {
61
62 class PNO;
63
64 // this class needs to be moved to vmra.h !!
65
66 // This class is used to store information for the non-linear solver
67 template<typename T, std::size_t NDIM>
68 class vecfunc {
69 public:
70 World& world;
71 std::vector<Function<T, NDIM> > x;
72
vecfunc(World & world,const std::vector<Function<T,NDIM>> & x1)73 vecfunc(World& world, const std::vector<Function<T, NDIM> >& x1) :
74 world(world), x(x1) {
75 }
76
vecfunc(const std::vector<Function<T,NDIM>> & x1)77 vecfunc(const std::vector<Function<T, NDIM> >& x1) :
78 world(x1[0].world()), x(x1) {
79 }
80
vecfunc(const vecfunc & other)81 vecfunc(const vecfunc& other) :
82 world(other.world), x(other.x) {
83 }
84
85 vecfunc& operator=(const vecfunc& other) {
86 x = other.x;
87 return *this;
88 }
89
90 vecfunc operator-(const vecfunc& b) const {
91 return vecfunc(world, sub(world, x, b.x));
92 }
93
94 vecfunc operator+=(const vecfunc& b) { // Operator+= necessary
95 x = add(world, x, b.x);
96 return *this;
97 }
98
99 vecfunc operator*(double a) const { // Scale by a constant necessary
100
101 PROFILE_BLOCK(Vscale);
102 std::vector<Function<T,NDIM> > result(x.size());
103 for (unsigned int i=0; i<x.size(); ++i) {
104 result[i]=mul(a,x[i],false);
105 }
106 world.gop.fence();
107
108 // scale(world, x, a);
109 return result;
110 }
111 };
112
113 /// the non-linear solver requires an inner product
114 template<typename T, std::size_t NDIM>
inner(const vecfunc<T,NDIM> & a,const vecfunc<T,NDIM> & b)115 T inner(const vecfunc<T, NDIM>& a, const vecfunc<T, NDIM>& b) {
116 Tensor<T> i = inner(a.world, a.x, b.x);
117 return i.sum();
118 }
119
120 // The default constructor for functions does not initialize
121 // them to any value, but the solver needs functions initialized
122 // to zero for which we also need the world object.
123 template<typename T, std::size_t NDIM>
124 struct allocator {
125 World& world;
126 const int n;
127
128 /// @param[in] world the world
129 /// @param[in] nn the number of functions in a given vector
allocatorallocator130 allocator(World& world, const int nn) :
131 world(world), n(nn) {
132 }
133
134 /// allocate a vector of n empty functions
operatorallocator135 vecfunc<T, NDIM> operator()() {
136 return vecfunc<T, NDIM>(world, zero_functions<T, NDIM>(world, n));
137 }
138 };
139
140
141
142 /// The Nemo class
143 class Nemo: public MolecularOptimizationTargetInterface {
144 typedef std::shared_ptr<real_convolution_3d> poperatorT;
145 friend class PNO;
146
147 public:
148
149 /// ctor
150
151 /// @param[in] world1 the world
152 /// @param[in] calc the SCF
Nemo(World & world1,std::shared_ptr<SCF> calc)153 Nemo(World& world1, std::shared_ptr<SCF> calc) :
154 world(world1), calc(calc), ttt(0.0), sss(0.0), coords_sum(-1.0), ac(world,calc) {
155
156 if (do_pcm()) pcm=PCM(world,this->molecule(),calc->param.pcm_data,true);
157
158 }
159
construct_nuclear_correlation_factor()160 void construct_nuclear_correlation_factor() {
161
162 // construct the nuclear correlation factor:
163 if (not nuclear_correlation)
164 nuclear_correlation=create_nuclear_correlation_factor(world,*calc);
165
166 // re-project the ncf
167 nuclear_correlation->initialize(FunctionDefaults<3>::get_thresh());
168 R = nuclear_correlation->function();
169 R.set_thresh(FunctionDefaults<3>::get_thresh());
170 R_square = nuclear_correlation->square();
171 R_square.set_thresh(FunctionDefaults<3>::get_thresh());
172 }
173
value()174 double value() {return value(calc->molecule.get_all_coords());}
175
176 double value(const Tensor<double>& x);
177
178 /// compute the nuclear gradients
179 Tensor<double> gradient(const Tensor<double>& x);
180
provides_gradient()181 bool provides_gradient() const {return true;}
182
183 /// returns the molecular hessian matrix at structure x
184 Tensor<double> hessian(const Tensor<double>& x);
185
186 /// purify and symmetrize the hessian
187
188 /// The hessian should be symmetric, but it is not, because
189 /// \f[
190 /// \langle i^{Y_B}|H^{X_A}|i\rangle \neq \langle i|H^{X_A}|i^{Y_B}\rangle
191 /// \f]
192 /// does holds analytically, but not numerically. If the two numbers
193 /// differ, pick the more trustworthy, which is the one with a heavy
194 /// atom causing the perturbed density and the light atom being the
195 /// nuclear singularity.
196 /// @param[in] hessian the raw hessian
197 /// @return a symmetrized hessian
198 Tensor<double> purify_hessian(const Tensor<double>& hessian) const;
199
200
201 /// solve the CPHF equations for the nuclear displacements
202
203 /// this function computes that part of the orbital response that is
204 /// orthogonal to the occupied space. If no NCF's are used this
205 /// corresponds to the normal response. If NCF's are used the part
206 /// parallel to the occupied space must be added!
207 /// \f[
208 /// F^X = F^\perp + F^\parallel
209 /// \f]
210 /// cf parallel_CPHF()
211 /// @param[in] iatom the atom A to be moved
212 /// @param[in] iaxis the coordinate X of iatom to be moved
213 /// @return \ket{i^X} or \ket{F^\perp}
214 vecfuncT solve_cphf(const int iatom, const int iaxis, const Tensor<double> fock,
215 const vecfuncT& guess, const vecfuncT& rhsconst,
216 const Tensor<double> incomplete_hessian, const vecfuncT& parallel,
217 const SCFProtocol& p, const std::string& xc_data) const;
218
219 /// solve the CPHF equation for all displacements
220
221 /// this function computes the nemo response F^X
222 /// \f[
223 /// F^X = F^\perp + F^\parallel
224 /// \f]
225 /// To reconstruct the unregularized orbital response (not recommended):
226 /// \f[
227 /// i^X = R^X F + R F^X
228 /// \f]
229 /// The orbital response i^X constructed in this way is automatically
230 /// orthogonal to the occupied space because of the parallel term F^\parallel
231 /// @return a vector of the nemo response F^X for all displacements
232 std::vector<vecfuncT> compute_all_cphf();
233
234 /// this function computes that part of the orbital response that is
235 /// parallel to the occupied space.
236 /// \f[
237 /// F^X = F^\perp + F^\parallel
238 /// \f]
239 /// If no NCF's are used F^\parallel vanishes.
240 /// If NCF's are used this term does not vanish because the derivatives of
241 /// the NCF does not vanish, and it is given by
242 /// \f[
243 /// F_i^\parallel = -\frac{1}{2}\sum_k|F_k ><F_k | (R^2)^X | F_i>
244 /// \f]
245 vecfuncT compute_cphf_parallel_term(const int iatom, const int iaxis) const;
246
247 /// compute the IR intensities in the double harmonic approximation
248
249 /// use the projected normal modes; units are km/mol
250 /// @param[in] normalmodes the normal modes
251 /// @param[in] dens_pt the perturbed densities for each nuclear displacement
252 Tensor<double> compute_IR_intensities(const Tensor<double>& normalmodes,
253 const vecfuncT& dens_pt) const;
254
get_calc()255 std::shared_ptr<SCF> get_calc() const {return calc;}
256
get_pcm()257 PCM get_pcm()const{return pcm;}
258
259 /// compute the Fock matrix from scratch
260 tensorT compute_fock_matrix(const vecfuncT& nemo, const tensorT& occ) const;
261
262 /// return a reference to the molecule
molecule()263 Molecule& molecule() {return calc->molecule;}
264
265 /// return a reference to the molecule
molecule()266 Molecule& molecule() const {
267 return calc->molecule;
268 }
269
270 /// make the density (alpha or beta)
271 real_function_3d make_density(const Tensor<double>& occ,
272 const vecfuncT& nemo) const;
273
274 /// make the density using different bra and ket vectors
275
276 /// e.g. for computing the perturbed density \sum_i \phi_i \phi_i^X
277 /// or when using nemos: \sum_i R2nemo_i nemo_i
278 real_function_3d make_density(const tensorT & occ,
279 const vecfuncT& bra, const vecfuncT& ket, const bool refine=false) const;
280
281 /// make the derivative of the density
282
283 /// \f$ \nabla\rho = 2R^X R \rho_R + R^2\nabla \rho_R \f$
284 /// @param[in] rhonemo the regularized density
285 /// @param[in] axis the component of the nabla operator
286 /// @return the gradient of the *reconstructed* density
287 real_function_3d make_ddensity(const real_function_3d& rhonemo,
288 const int axis) const;
289
290 /// compute the reduced densities sigma (gamma) for GGA functionals
291 real_function_3d make_sigma(const real_function_3d& rho1,
292 const real_function_3d& rho2) const;
293
294
295 /// the Laplacian of the density
296
297 /// The Laplacian should currently only be used for subsequent convolution
298 /// with a Green's function (which is reasonably stable), but not on its own!
299 ///
300 /// The Laplacian of the cuspy density is numerically fairly unstable:
301 /// - a singular term may be rewritten using the nuclear potential (see below)
302 /// - the Laplacian of the regularized density is still very noisy
303 ///
304 /// It may be computed as
305 /// \f[
306 /// \Delta \rho = \Delta (R^2 \rho_R)
307 /// = \Delta (R^2) \rho_R + 2\nabla R \nabla \rho_R + R^2 \Delta \rho_R
308 /// = 2 R^2 U1^2 \rho_R -4 R^2 ( U-V ) \rho_R + R^2 \Delta\rho_R
309 /// \f]
310 /// where we can use the identity
311 /// \f[
312 /// U=V + R^{-1}[T,R]
313 /// -2 R (U-V) = \Delta R + 2\nabla R\dot \nabla
314 /// \f]
315 /// first term comes from the definition of the U potential as the commutator
316 /// over the kinetic energy (aka the Laplacian)
317 /// @param[in] rhonemo the regularized density \rho_R
318 /// @return the laplacian of the reconstructed density \Delta (R^2\rho_R)
319 real_function_3d make_laplacian_density(const real_function_3d& rhonemo) const;
320
321 /// compute the kinetic energy potential using Eq. (16) of
322 /// R. A. King and N. C. Handy, “Kinetic energy functionals from the Kohn–Sham potential,”
323 /// Phys. Chem. Chem. Phys., vol. 2, no. 22, pp. 5049–5056, 2000.
324 real_function_3d kinetic_energy_potential(const vecfuncT& nemo) const;
325
326
327 /// smooth a function by projecting it onto k-1 and then average with k
328
329 /// kept it here for further testing
smoothen(real_function_3d & f)330 static void smoothen(real_function_3d& f) {
331 int k=f.get_impl()->get_k();
332 real_function_3d fproj=project(f,k-1);
333 real_function_3d freproj=project(fproj,k);
334 f=0.5*(f+freproj);
335 }
336
337 private:
338
339 /// the world
340 World& world;
341
342 std::shared_ptr<SCF> calc;
343
344 mutable double ttt, sss;
START_TIMER(World & world)345 void START_TIMER(World& world) const {
346 world.gop.fence(); ttt=wall_time(); sss=cpu_time();
347 }
348
END_TIMER(World & world,const std::string msg)349 void END_TIMER(World& world, const std::string msg) const {
350 END_TIMER(world,msg.c_str());
351 }
352
END_TIMER(World & world,const char * msg)353 void END_TIMER(World& world, const char* msg) const {
354 ttt=wall_time()-ttt; sss=cpu_time()-sss;
355 if (world.rank()==0) printf("timer: %20.20s %8.2fs %8.2fs\n", msg, sss, ttt);
356 }
357
358 struct timer {
359 World& world;
360 double ttt,sss;
timertimer361 timer(World& world) : world(world) {
362 world.gop.fence();
363 ttt=wall_time();
364 sss=cpu_time();
365 }
366
tagtimer367 void tag(const std::string msg) {
368 world.gop.fence();
369 double tt1=wall_time()-ttt;
370 double ss1=cpu_time()-sss;
371 if (world.rank()==0) printf("timer: %20.20s %8.2fs %8.2fs\n", msg.c_str(), ss1, tt1);
372 ttt=wall_time();
373 sss=cpu_time();
374 }
375
endtimer376 void end(const std::string msg) {
377 world.gop.fence();
378 double tt1=wall_time()-ttt;
379 double ss1=cpu_time()-sss;
380 if (world.rank()==0) printf("timer: %20.20s %8.2fs %8.2fs\n", msg.c_str(), ss1, tt1);
381 }
382
383 };
384
385 public:
386
387 /// the nuclear correlation factor
388 std::shared_ptr<NuclearCorrelationFactor> nuclear_correlation;
389
390 /// the nuclear correlation factor
391 real_function_3d R;
392
393 /// the square of the nuclear correlation factor
394 real_function_3d R_square;
395
396 private:
397
398 /// sum of square of coords at last solved geometry
399 mutable double coords_sum;
400
401 /// a poisson solver
402 std::shared_ptr<real_convolution_3d> poisson;
403
404 // /// asymptotic correction for DFT
405 // AC<3> ac;
406 //
407 // /// apply the AC scheme of Tozer/Handy with the multipole approximation
408 // Function<double,3> apply_ac(const Function<double,3>& vxc)const{
409 // return ac.apply(vxc);
410 // }
411 //
412 // /// apply the AC scheme of Tozer/Handy using the hartree potential
413 // Function<double,3> apply_ac(const Function<double,3>& vxc, const Function<double,3>& vhartree)const{
414 // return ac.apply(vxc,vhartree);
415 // }
416 //
417 // /// apply the AC scheme of Tozer/Handy
418 // Function<double,3> apply_ac_excited(Function<double,3>& vxc, const Function<double,3>& vhartree)const{
419 // return ac.apply_potential(vxc,vhartree);
420 // }
421
422 /// polarizable continuum model
423 PCM pcm;
424 AC<3> ac;
425
426 /// adapt the thresholds consistently to a common value
set_protocol(const double thresh)427 void set_protocol(const double thresh) {
428
429 calc->set_protocol<3>(world,thresh);
430
431 // (re) construct nuclear potential and correlation factors
432 timer timer1(world);
433 construct_nuclear_correlation_factor();
434 timer1.end("reproject ncf");
435
436 // (re) construct the Poisson solver
437 poisson = std::shared_ptr<real_convolution_3d>(
438 CoulombOperatorPtr(world, calc->param.lo, FunctionDefaults<3>::get_thresh()));
439
440 // set thresholds for the MOs
441 set_thresh(world,calc->amo,thresh);
442 set_thresh(world,calc->bmo,thresh);
443
444 }
445
446 /// solve the HF equations
447 double solve(const SCFProtocol& proto);
448
449 /// given nemos, compute the HF energy
450 double compute_energy(const vecfuncT& psi, const vecfuncT& Jpsi,
451 const vecfuncT& Kpsi) const;
452
453 /// given nemos, compute the HF energy using the regularized expressions for T and V
454 double compute_energy_regularized(const vecfuncT& nemo, const vecfuncT& Jnemo,
455 const vecfuncT& Knemo, const vecfuncT& Unemo) const;
456
457 /// compute the reconstructed orbitals, and all potentials applied on nemo
458
459 /// to use these potentials in the fock matrix computation they must
460 /// be multiplied by the nuclear correlation factor
461 /// @param[in] nemo the nemo orbitals
462 /// @param[out] psi the reconstructed, full orbitals
463 /// @param[out] Jnemo Coulomb operator applied on the nemos
464 /// @param[out] Knemo exchange operator applied on the nemos
465 /// @param[out] pcmnemo PCM (solvent) potential applied on the nemos
466 /// @param[out] Unemo regularized nuclear potential applied on the nemos
467 void compute_nemo_potentials(const vecfuncT& nemo, vecfuncT& psi,
468 vecfuncT& Jnemo, vecfuncT& Knemo, vecfuncT& pcmnemo,
469 vecfuncT& Unemo) const;
470
471 /// normalize the nemos
472 void normalize(vecfuncT& nemo) const;
473
474 /// orthonormalize the vectors
475
476 /// @param[in,out] nemo the vectors to be orthonormalized
477 void orthonormalize(vecfuncT& nemo) const;
478
479 /// return the Coulomb potential
480 real_function_3d get_coulomb_potential(const vecfuncT& psi) const;
481
482 /// compute the incomplete hessian
483
484 /// incomplete hessian is the nuclear-nuclear contribution, and the
485 /// contribution from the second derivative of the nuclear potential,
486 /// and also the derivative of the nuclear correlation factor.
487 /// i.e. all contributions that *do not* contain the regularized perturbed
488 /// density, but it will contain parts of the perturbed density
489 Tensor<double> make_incomplete_hessian() const;
490
491 /// compute the complementary incomplete hessian
492
493 /// @param[in] xi the response functions including the parallel part
494 Tensor<double> make_incomplete_hessian_response_part(
495 const std::vector<vecfuncT>& xi) const;
496
497 /// compute the constant term for the CPHF equations
498
499 /// mainly all terms with the nuclear correlation factor's derivatives
500 vecfuncT make_cphf_constant_term(const int iatom, const int iaxis,
501 const vecfuncT& R2nemo, const real_function_3d& rhonemo) const;
502
503 public:
504
is_dft()505 bool is_dft() const {return calc->xc.is_dft();}
506
do_pcm()507 bool do_pcm() const {return calc->param.pcm_data != "none";}
508
do_ac()509 bool do_ac() const {return calc->param.ac_data != "none";}
510
get_ac()511 AC<3> get_ac() const {return ac;}
512
513 private:
514
515 /// localize the nemo orbitals
516 vecfuncT localize(const vecfuncT& nemo, const double dconv, const bool randomize) const;
517
518 /// return the threshold for vanishing elements in orbital rotations
trantol()519 double trantol() const {
520 return calc->vtol / std::min(30.0, double(get_calc()->amo.size()));
521 }
522
523 template<typename solverT>
524 void rotate_subspace(World& world, const tensorT& U, solverT& solver,
525 int lo, int nfunc) const;
526
Q2(const tensorT & s)527 tensorT Q2(const tensorT& s) const {
528 tensorT Q = -0.5*s;
529 for (int i=0; i<s.dim(0); ++i) Q(i,i) += 1.5;
530 return Q;
531 }
532
533 void make_plots(const real_function_3d &f,const std::string &name="function")const{
534 double width = FunctionDefaults<3>::get_cell_min_width()/2.0 - 1.e-3;
535 plot_plane(world,f,name);
536 coord_3d start(0.0); start[0]=-width;
537 coord_3d end(0.0); end[0]=width;
538 plot_line(("line_"+name).c_str(),1000,start,end,f);
539 }
540
541 /// save a function
542 template<typename T, size_t NDIM>
543 void save_function(const std::vector<Function<T,NDIM> >& f, const std::string name) const;
544
545 /// load a function
546 template<typename T, size_t NDIM>
547 void load_function(std::vector<Function<T,NDIM> >& f, const std::string name) const;
548
549 };
550
551 /// rotate the KAIN subspace (cf. SCF.cc)
552 template<typename solverT>
rotate_subspace(World & world,const tensorT & U,solverT & solver,int lo,int nfunc)553 void Nemo::rotate_subspace(World& world, const tensorT& U, solverT& solver,
554 int lo, int nfunc) const {
555 std::vector < vecfunc<double, 3> > &ulist = solver.get_ulist();
556 std::vector < vecfunc<double, 3> > &rlist = solver.get_rlist();
557 for (unsigned int iter = 0; iter < ulist.size(); ++iter) {
558 vecfuncT& v = ulist[iter].x;
559 vecfuncT& r = rlist[iter].x;
560 vecfuncT vnew = transform(world, vecfuncT(&v[lo], &v[lo + nfunc]), U,
561 trantol(), false);
562 vecfuncT rnew = transform(world, vecfuncT(&r[lo], &r[lo + nfunc]), U,
563 trantol(), true);
564
565 world.gop.fence();
566 for (int i=0; i<nfunc; i++) {
567 v[i] = vnew[i];
568 r[i] = rnew[i];
569 }
570 }
571 world.gop.fence();
572 }
573
574 /// save a function
575 template<typename T, size_t NDIM>
save_function(const std::vector<Function<T,NDIM>> & f,const std::string name)576 void Nemo::save_function(const std::vector<Function<T,NDIM> >& f, const std::string name) const {
577 if (world.rank()==0) print("saving vector of functions",name);
578 archive::ParallelOutputArchive ar(world, name.c_str(), 1);
579 ar & f.size();
580 for (const Function<T,NDIM>& ff:f) ar & ff;
581 }
582
583 /// save a function
584 template<typename T, size_t NDIM>
load_function(std::vector<Function<T,NDIM>> & f,const std::string name)585 void Nemo::load_function(std::vector<Function<T,NDIM> >& f, const std::string name) const {
586 if (world.rank()==0) print("loading vector of functions",name);
587 archive::ParallelInputArchive ar(world, name.c_str(), 1);
588 std::size_t fsize=0;
589 ar & fsize;
590 f.resize(fsize);
591 for (std::size_t i=0; i<fsize; ++i) ar & f[i];
592 }
593
594 }
595
596 #endif /* NEMO_H_ */
597
598