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   $Id$
32 */
33 //#define WORLD_INSTANTIATE_STATIC_TEMPLATES
34 #include "hartreefock.h"
35 
36 namespace madness
37 {
38 
39   //*************************************************************************
40   template <typename T, int NDIM>
HartreeFockNuclearPotentialOp(World & world,funcT V,double coeff,double thresh)41   HartreeFockNuclearPotentialOp<T,NDIM>::HartreeFockNuclearPotentialOp(World& world,
42     funcT V, double coeff, double thresh) :
43   EigSolverOp<T,NDIM>(world, coeff, thresh)
44   {
45     // Message for the matrix element output
46     this->messageME("HartreeFockNuclearPotentialOp");
47     _V = V;
48   }
49   //*************************************************************************
50 
51   //*************************************************************************
52   template <typename T, int NDIM>
HartreeFockCoulombOp(World & world,double coeff,double thresh)53   HartreeFockCoulombOp<T,NDIM>::HartreeFockCoulombOp(World& world, double coeff,
54       double thresh) : EigSolverOp<T,NDIM>(world, coeff, thresh)
55   {
56     // Message for the matrix element output
57     this->messageME("HartreeFockCoulombOp");
58   }
59   //*************************************************************************
60 
61   //*************************************************************************
62   template <typename T, int NDIM>
HartreeFockExchangeOp(World & world,double coeff,double thresh)63   HartreeFockExchangeOp<T,NDIM>::HartreeFockExchangeOp(World& world, double coeff,
64       double thresh) : EigSolverOp<T,NDIM>(world, coeff, thresh)
65   {
66     // Message for the matrix element output
67     this->messageME("HartreeFockExchangeOp");
68   }
69   //*************************************************************************
70 
71   //*************************************************************************
72   template <typename T, int NDIM>
op_r(const funcT & rho,const funcT & rhon,const funcT & psi)73   Function<T,NDIM> HartreeFockNuclearPotentialOp<T,NDIM>::op_r(const funcT& rho, const funcT& rhon, const funcT& psi)
74   {
75     funcT rfunc = _V*psi;
76     return rfunc;
77   }
78   //*************************************************************************
79 
80   //*************************************************************************
81   template <typename T, int NDIM>
op_r(const funcT & rho,const funcT & rhon,const funcT & psi)82   Function<T,NDIM> HartreeFockCoulombOp<T,NDIM>::op_r(const funcT& rho, const funcT& rhon, const funcT& psi)
83   {
84     // Create Coulomb operator
85     SeparatedConvolution<T,3> cop =
86       CoulombOperator<T,3>(this->world(), FunctionDefaults<3>::get_k(), 1e-4, this->thresh());
87     // Apply the Coulomb operator
88     funcT Vc = apply(cop, rho);
89     funcT rfunc = Vc*psi;
90     return  rfunc;
91   }
92   //*************************************************************************
93 
94   //*************************************************************************
95   template <typename T, int NDIM>
op_o(const std::vector<funcT> & phis,const funcT & rhon,const funcT & psi)96   Function<T,NDIM> HartreeFockExchangeOp<T,NDIM>::op_o(const std::vector<funcT>& phis, const funcT& rhon, const funcT& psi)
97   {
98     // Return value
99     funcT rfunc = FunctionFactory<T,3>(this->world());
100     // Create Coulomb operator
101     SeparatedConvolution<T,NDIM> cop = CoulombOperator<T,NDIM>(this->world(),
102         FunctionDefaults<3>::get_k(), 1e-4, this->thresh());
103     // Use the psi and pj wavefunctions to build a product so that the K
104     // operator can be applied to the wavefunction indexed by pj, NOT PSI.
105     for (typename std::vector<funcT>::const_iterator pj = phis.begin(); pj != phis.end(); ++pj)
106     {
107       // Get phi(j) from iterator
108       const funcT& phij = (*pj);
109       // NOTE that psi is involved in this calculation
110       funcT prod = phij*psi;
111       // Transform Coulomb operator into a function (stubbed)
112       prod.truncate(this->thresh());
113       funcT Vex = apply(cop, prod);
114       // NOTE that the index is j.
115       rfunc += Vex*phij;
116     }
117     return rfunc;
118 
119   }
120   //*************************************************************************
121 
122   //*************************************************************************
123   template <typename T, int NDIM>
HartreeFock(World & world,funcT V,std::vector<funcT> phis,std::vector<double> eigs,bool bCoulomb,bool bExchange,double thresh)124   HartreeFock<T,NDIM>::HartreeFock(World& world, funcT V, std::vector<funcT> phis,
125     std::vector<double> eigs, bool bCoulomb, bool bExchange, double thresh)
126    : _world(world), _V(V), _thresh(thresh)
127   {
128     _bCoulomb = bCoulomb;
129     _bExchange = bExchange;
130     // Create ops list
131     std::vector<EigSolverOp<T,NDIM>*> ops;
132     // Add nuclear potential to ops list
133     ops.push_back(new HartreeFockNuclearPotentialOp<T,NDIM>(world, V, 1.0, thresh));
134     // Check for coulomb and exchange, and add as appropriate
135     if (bCoulomb)
136     {
137       ops.push_back(new HartreeFockCoulombOp<T,NDIM>(world, 2.0, thresh));
138     }
139     if (bExchange)
140     {
141       ops.push_back(new HartreeFockExchangeOp<T,NDIM>(world, -1.0, thresh));
142     }
143     // Create solver
144     _solver = new EigSolver<T,NDIM>(world, phis, eigs, ops, thresh, false);
145     _solver->addObserver(this);
146   }
147   //***************************************************************************
148 
149   //***************************************************************************
150     template <typename T, int NDIM>
~HartreeFock()151   HartreeFock<T,NDIM>::~HartreeFock()
152   {
153     delete _solver;
154   }
155   //***************************************************************************
156 
157   //***************************************************************************
158     template <typename T, int NDIM>
hartree_fock(int maxits)159   void HartreeFock<T,NDIM>::hartree_fock(int maxits)
160   {
161     _solver->solve(maxits);
162   }
163   //***************************************************************************
164 
165   //***************************************************************************
166     template <typename T, int NDIM>
calculate_ke_sp(funcT psi)167   double HartreeFock<T,NDIM>::calculate_ke_sp(funcT psi)
168   {
169     double kenergy = 0.0;
170     for (int axis = 0; axis < 3; axis++)
171     {
172       funcT dpsi = diff(psi, axis);
173       kenergy += 0.5 * inner(dpsi, dpsi);
174     }
175     return kenergy;
176   }
177   //***************************************************************************
178 
179   //***************************************************************************
180     template <typename T, int NDIM>
calculate_pe_sp(funcT psi)181   double HartreeFock<T,NDIM>::calculate_pe_sp(funcT psi)
182   {
183     funcT vpsi = _V*psi;
184     return vpsi.inner(psi);
185   }
186   //***************************************************************************
187 
188   //***************************************************************************
189     template <typename T, int NDIM>
calculate_coulomb_energy(const std::vector<funcT> & phis,const funcT & psi)190   double HartreeFock<T,NDIM>::calculate_coulomb_energy(const std::vector<funcT>& phis, const funcT& psi)
191   {
192     if (include_coulomb())
193     {
194       // Electron density
195       funcT density = FunctionFactory<T,3>(_world);
196       // Create Coulomb operator
197       SeparatedConvolution<T,NDIM> op =
198         CoulombOperator<T,3>(_world, FunctionDefaults<3>::get_k(), 1e-4, _thresh);
199       for (typename std::vector<funcT>::const_iterator pj = phis.begin(); pj != phis.end(); ++pj)
200       {
201         // Get phi(j) from iterator
202         const funcT& phij = (*pj);
203         // Compute the j-th density
204         funcT prod = phij*phij;
205         density += prod;
206       }
207       // Transform Coulomb operator into a function (stubbed)
208       density.truncate(_thresh);
209       funcT Vc = apply(op, density);
210       // Note that we are not using psi
211       // The density is built from all of the wavefunctions. The contribution
212       // psi will be subtracted out later during the exchange.
213       funcT vpsi = Vc*psi;
214       return inner(vpsi, psi);
215     }
216     return 0.0;
217   }
218   //***************************************************************************
219 
220   //***************************************************************************
221   template <typename T, int NDIM>
calculate_exchange_energy(const std::vector<funcT> & phis,const funcT & psi)222   double HartreeFock<T,NDIM>::calculate_exchange_energy(const std::vector<funcT>& phis,
223       const funcT& psi)
224   {
225     // Return value
226     funcT rfunc = FunctionFactory<double,NDIM>(world());
227     if (include_exchange())
228     {
229       // Create Coulomb operator
230       SeparatedConvolution<T,NDIM> op = CoulombOperator<T,NDIM>(world(),
231           FunctionDefaults<NDIM>::get_k(), 1e-4, thresh());
232       // Use the psi and pj wavefunctions to build a product so that the K
233       // operator can be applied to the wavefunction indexed by pj, NOT PSI.
234       for (typename std::vector<funcT>::const_iterator pj = phis.begin(); pj != phis.end(); ++pj)
235       {
236         // Get phi(j) from iterator
237         const funcT& phij = (*pj);
238         // NOTE that psi is involved in this calculation
239         funcT prod = phij*psi;
240         // Transform Coulomb operator into a function (stubbed)
241         funcT Vex = apply(op, prod);
242         // NOTE that the index is j.
243         rfunc += Vex*phij;
244       }
245     }
246     return inner(rfunc, psi);
247   }
248   //***************************************************************************
249 
250   //***************************************************************************
251   template <typename T, int NDIM>
calculate_tot_ke_sp(const std::vector<funcT> & phis)252   double HartreeFock<T,NDIM>::calculate_tot_ke_sp(const std::vector<funcT>& phis)
253   {
254     double tot_ke = 0.0;
255     for (unsigned int pi = 0; pi < phis.size(); pi++)
256     {
257       // Get psi from collection
258       const funcT psi = phis[pi];
259       // Calculate kinetic energy contribution from psi
260       tot_ke += calculate_ke_sp(psi);
261     }
262     return tot_ke;
263   }
264   //***************************************************************************
265 
266   //***************************************************************************
267   template <typename T, int NDIM>
calculate_tot_pe_sp(const std::vector<funcT> & phis)268   double HartreeFock<T,NDIM>::calculate_tot_pe_sp(const std::vector<funcT>& phis)
269   {
270     double tot_pe = 0.0;
271     for (unsigned int pi = 0; pi < phis.size(); pi++)
272     {
273       // Get psi from collection
274       const funcT psi = phis[pi];
275       // Calculate potential energy contribution from psi
276       tot_pe += calculate_pe_sp(psi);
277     }
278     return tot_pe;
279   }
280   //***************************************************************************
281 
282   //***************************************************************************
283   template <typename T, int NDIM>
calculate_tot_coulomb_energy(const std::vector<funcT> & phis)284   double HartreeFock<T,NDIM>::calculate_tot_coulomb_energy(const std::vector<funcT>& phis)
285   {
286     double tot_ce = 0.0;
287     for (unsigned int pi = 0; pi < phis.size(); pi++)
288     {
289       // Get psi from collection
290       const funcT psi = phis[pi];
291       // Calculate coulomb energy contribution from psi
292       tot_ce += calculate_coulomb_energy(phis, psi);
293     }
294     return tot_ce;
295   }
296   //***************************************************************************
297 
298   //***************************************************************************
299   template <typename T, int NDIM>
calculate_tot_exchange_energy(const std::vector<funcT> & phis)300   double HartreeFock<T,NDIM>::calculate_tot_exchange_energy(const std::vector<funcT>& phis)
301   {
302     double tot_ee = 0.0;
303     for (unsigned int pi = 0; pi < phis.size(); pi++)
304     {
305       // Get psi from collection
306       const funcT psi = phis[pi];
307       // Calculate exchange energy contribution from psi
308       tot_ee += calculate_exchange_energy(phis, psi);
309     }
310     return tot_ee;
311   }
312   //***************************************************************************
313 
314   //***************************************************************************
315   template <typename T, int NDIM>
iterateOutput(const std::vector<funcT> & phis,const std::vector<double> & eigs,const funcT & rho,const int & iter)316   void HartreeFock<T,NDIM>::iterateOutput(const std::vector<funcT>& phis,
317       const std::vector<double>& eigs,const funcT& rho, const int& iter)
318   {
319     if (iter%3 == 0)
320     {
321       if (world().rank() == 0) printf("Calculating energies ...\n");
322       if (world().rank() == 0) printf("Calculating KE ...\n");
323       double ke = 2.0 * calculate_tot_ke_sp(phis);
324       if (world().rank() == 0) printf("Calculating PE ...\n");
325       double pe = 2.0 * calculate_tot_pe_sp(phis);
326       if (world().rank() == 0) printf("Calculating CE ...\n");
327       double ce = calculate_tot_coulomb_energy(phis);
328       if (world().rank() == 0) printf("Calculating EE ...\n");
329       double ee = calculate_tot_exchange_energy(phis);
330       if (world().rank() == 0) printf("Calculating NE ...\n");
331       double ne = 0.0;
332       if (world().rank() == 0) printf("Kinetic energy:\t\t\t %.8f\n", ke);
333       if (world().rank() == 0) printf("Potential energy:\t\t %.8f\n", pe);
334       if (world().rank() == 0) printf("Two-electron energy:\t\t %.8f\n", 2.0*ce - ee);
335       if (world().rank() == 0) printf("Total energy:\t\t\t %.8f\n", ke + pe + 2.0*ce - ee + ne);
336       if (world().rank() == 0) printf("gs eigv: = \t\t\t %.4f\n", eigs[0]);
337     }
338   }
339   //***************************************************************************
340 }
341 //*****************************************************************************
342 
343