1 //
2 // solvent.cc
3 //
4 // Copyright (C) 1997 Limit Point Systems, Inc.
5 //
6 // Author: Curtis Janssen <cljanss@limitpt.com>
7 // Maintainer: LPS
8 //
9 // This file is part of the SC Toolkit.
10 //
11 // The SC Toolkit is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU Library General Public License as published by
13 // the Free Software Foundation; either version 2, or (at your option)
14 // any later version.
15 //
16 // The SC Toolkit is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 // GNU Library General Public License for more details.
20 //
21 // You should have received a copy of the GNU Library General Public License
22 // along with the SC Toolkit; see the file COPYING.LIB.  If not, write to
23 // the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24 //
25 // The U.S. Government is granted a limited license as per AL 91-7.
26 //
27 
28 #ifdef __GNUC__
29 #pragma implementation
30 #endif
31 
32 #include <util/misc/timer.h>
33 #include <util/misc/formio.h>
34 #include <util/state/stateio.h>
35 #include <chemistry/qc/basis/petite.h>
36 #include <chemistry/qc/wfn/solvent.h>
37 
38 #include <math/isosurf/volume.h>
39 #include <chemistry/qc/dft/integrator.h>
40 #include <chemistry/qc/dft/functional.h>
41 
42 #include <iomanip>
43 
44 using namespace std;
45 using namespace sc;
46 
47 namespace sc {
48 
49 //. The \clsnm{NElFunctional} computes the number of electrons.
50 //. It is primarily for testing the integrator.
51 class NElInShapeFunctional: public DenFunctional {
52   private:
53     Ref<Volume> vol_;
54     double isoval_;
55   public:
56     NElInShapeFunctional(const Ref<Volume> &, double);
57     ~NElInShapeFunctional();
58 
59     void point(const PointInputData&, PointOutputData&);
60 };
61 
62 /////////////////////////////////////////////////////////////////////////////
63 // NElFunctional
64 
65 static ClassDesc NElInShapeFunctional_cd(
66   typeid(NElInShapeFunctional),"NElInShapeFunctional",1,"public DenFunctional",
67   0, 0, 0);
68 
NElInShapeFunctional(const Ref<Volume> & vol,double isoval)69 NElInShapeFunctional::NElInShapeFunctional(const Ref<Volume>& vol,
70                                            double isoval)
71 {
72   vol_ = vol;
73   isoval_ = isoval;
74 }
75 
~NElInShapeFunctional()76 NElInShapeFunctional::~NElInShapeFunctional()
77 {
78 }
79 
80 void
point(const PointInputData & id,PointOutputData & od)81 NElInShapeFunctional::point(const PointInputData &id,
82                             PointOutputData &od)
83 {
84   vol_->set_x(id.r);
85   if (vol_->value() <= isoval_) {
86       od.energy = id.a.rho + id.b.rho;
87     }
88   else {
89       od.energy = 0.0;
90     }
91 }
92 
93 /////////////////////////////////////////////////////////////////////////////
94 
95 static ClassDesc BEMSolventH_cd(
96   typeid(BEMSolventH),"BEMSolventH",1,"public AccumH",
97   0, create<BEMSolventH>, create<BEMSolventH>);
98 
BEMSolventH(const Ref<KeyVal> & keyval)99 BEMSolventH::BEMSolventH(const Ref<KeyVal>&keyval):
100   AccumH(keyval)
101 {
102   charge_positions_ = 0;
103   normals_ = 0;
104   efield_dot_normals_ = 0;
105   charges_ = 0;
106   charges_n_ = 0;
107   solvent_ << keyval->describedclassvalue("solvent");
108   gamma_ = keyval->doublevalue("gamma");
109   if (keyval->error() != KeyVal::OK) {
110       Ref<Units> npm = new Units("dyne/cm");
111       gamma_ = 72.75 * npm->to_atomic_units();
112     }
113   // If onebody add a term to the one body hamiltonian, h.
114   // Otherwise the energy contribution is scalar.
115   onebody_ = keyval->booleanvalue("onebody");
116   if (keyval->error() != KeyVal::OK) onebody_ = 1;
117   // Normalize the charges if normalize_q is set.
118   normalize_q_ = keyval->booleanvalue("normalize_q");
119   if (keyval->error() != KeyVal::OK) normalize_q_ = 1;
120   // Compute separately contributes to the energy from surfaces
121   // charges induced by the nuclear and electronic charge densities.
122   separate_surf_charges_ = keyval->booleanvalue("separate_surf_charges");
123   if (keyval->error() != KeyVal::OK) separate_surf_charges_ = 0;
124   // The Cammi-Tomasi Y term is set equal to the J term (as it formally is).
125   y_equals_j_ = keyval->booleanvalue("y_equals_j");
126   if (keyval->error() != KeyVal::OK) y_equals_j_ = 0;
127   // As a test, integrate the number of electrons inside the surface.
128   integrate_nelectron_ = keyval->booleanvalue("integrate_nelectron");
129   if (keyval->error() != KeyVal::OK) integrate_nelectron_ = 0;
130 }
131 
BEMSolventH(StateIn & s)132 BEMSolventH::BEMSolventH(StateIn&s):
133   SavableState(s),
134   AccumH(s)
135 {
136   charge_positions_ = 0;
137   normals_ = 0;
138   efield_dot_normals_ = 0;
139   charges_ = 0;
140   charges_n_ = 0;
141   escalar_ = 0;
142 
143   wfn_ << SavableState::restore_state(s);
144   //solvent_.restore_state(s);
145   abort();
146 }
147 
~BEMSolventH()148 BEMSolventH::~BEMSolventH()
149 {
150   // just in case
151   done();
152 }
153 
154 void
save_data_state(StateOut & s)155 BEMSolventH::save_data_state(StateOut&s)
156 {
157   AccumH::save_data_state(s);
158 
159   SavableState::save_state(wfn_.pointer(),s);
160   //solvent_.save_state(s);
161   abort();
162 }
163 
164 void
init(const Ref<Wavefunction> & wfn)165 BEMSolventH::init(const Ref<Wavefunction>& wfn)
166 {
167   tim_enter("solvent");
168   tim_enter("init");
169   wfn_ = wfn;
170   // just in case
171   done();
172   solvent_->init();
173   charge_positions_ = solvent_->alloc_charge_positions();
174   normals_ = solvent_->alloc_normals();
175   efield_dot_normals_ = solvent_->alloc_efield_dot_normals();
176   charges_ = solvent_->alloc_charges();
177   charges_n_ = solvent_->alloc_charges();
178 
179   // get the positions of the charges
180   solvent_->charge_positions(charge_positions_);
181 
182   // get the surface normals
183   solvent_->normals(normals_);
184 
185   if (integrate_nelectron_) {
186       Ref<DenIntegrator> integrator = new RadialAngularIntegrator();
187       Ref<DenFunctional> functional
188           = new NElInShapeFunctional(solvent_->surface()->volume_object(),
189                                      solvent_->surface()->isovalue());
190       integrator->init(wfn_);
191       integrator->integrate(functional);
192       integrator->done();
193       ExEnv::out0() << indent
194            << scprintf("N(e) in isosurf = %12.8f", integrator->value())
195            << endl;
196     }
197 
198   edisprep_ = solvent_->disprep();
199 
200   tim_exit("init");
201   tim_exit("solvent");
202 }
203 
204 // This adds J + X to h, where J and X are the matrices defined
205 // by Canni and Tomasi, J Comp Chem, 16(12), 1457, 1995.
206 // The resulting SCF free energy expression is
207 //    G = 1/2TrP[h' + F'] + Une + Unn + Vnn
208 //        -1/2(Uee+Uen+Une+Unn)
209 // which in the Canni-Tomasi notation is
210 //      = 1/2TrP[h+1/2(X+J+Y+G)] + Vnn + 1/2Unn
211 // which is identical to the Canni-Tomasi energy expression.
212 // My Fock matrix is
213 //       F' = h + J + X + G
214 // while the Canni-Tomasi Fock matrix is F' = h + 1/2(J+Y) + X + G.
215 // However, since J = Y formally, (assuming no numerical errors
216 // and all charge is enclosed, Canni-Tomasi use F' = h + J + X + G
217 // to get better numerical results.
218 //
219 //   If the y_equals_j option is true, the energy expression used
220 // here is G = 1/2TrP[h+1/2(X+2J+G)] + Vnn + 1/2Unn, however, THIS
221 // IS NOT RECOMMENDED.
222 void
accum(const RefSymmSCMatrix & h)223 BEMSolventH::accum(const RefSymmSCMatrix& h)
224 {
225   tim_enter("solvent");
226   tim_enter("accum");
227   int i,j;
228 
229   //// compute the polarization charges
230 
231   // compute the e-field at each point and dot with normals
232   tim_enter("efield");
233   int ncharge = solvent_->ncharge();
234   Ref<EfieldDotVectorData> efdn_dat = new EfieldDotVectorData;
235   Ref<OneBodyInt> efdn = wfn_->integral()->efield_dot_vector(efdn_dat);
236   Ref<SCElementOp> efdn_op = new OneBodyIntOp(efdn);
237   RefSymmSCMatrix ao_density = wfn_->ao_density()->copy();
238   RefSymmSCMatrix efdn_mat(ao_density->dim(), ao_density->kit());
239   // for the scalar products, scale the density's off-diagonals by two
240   ao_density->scale(2.0);
241   ao_density->scale_diagonal(0.5);
242   Ref<SCElementScalarProduct> sp = new SCElementScalarProduct;
243   Ref<SCElementOp2> generic_sp(sp.pointer());
244   for (i=0; i<ncharge; i++) {
245       efdn_dat->set_position(charge_positions_[i]);
246       efdn_dat->set_vector(normals_[i]);
247       efdn->reinitialize();
248       efdn_mat->assign(0.0);
249       efdn_mat->element_op(efdn_op);
250       sp->init();
251       efdn_mat->element_op(generic_sp, ao_density);
252       efield_dot_normals_[i] = sp->result();
253     }
254   RefSCDimension aodim = ao_density.dim();
255   Ref<SCMatrixKit> aokit = ao_density.kit();
256   ao_density = 0;
257   efdn_mat = 0;
258   tim_exit("efield");
259 
260   // compute a new set of charges
261   tim_enter("charges");
262   // electron contrib
263   solvent_->compute_charges(efield_dot_normals_, charges_);
264   double qeenc = solvent_->computed_enclosed_charge();
265   // nuclear contrib
266   for (i=0; i<ncharge; i++) {
267       double nuc_efield[3];
268       wfn_->molecule()->nuclear_efield(charge_positions_[i], nuc_efield);
269       double tmp = 0.0;
270       for (j=0; j<3; j++) {
271           tmp += nuc_efield[j] * normals_[i][j];
272         }
273       efield_dot_normals_[i] = tmp;
274     }
275   solvent_->compute_charges(efield_dot_normals_, charges_n_);
276   double qnenc = solvent_->computed_enclosed_charge();
277   tim_exit("charges");
278 
279   // normalize the charges
280   // e and n are independently normalized since the nature of the
281   // errors in e and n are different: n error is just numerical and
282   // e error is numerical plus diffuseness of electron distribution
283   if (normalize_q_) {
284       tim_enter("norm");
285       // electron contrib
286       solvent_->normalize_charge(-wfn_->nelectron(), charges_);
287       // nuclear contrib
288       solvent_->normalize_charge(wfn_->molecule()->nuclear_charge(),
289                                  charges_n_);
290       tim_exit("norm");
291     }
292   // sum the nuclear and electron contrib
293   for (i=0; i<ncharge; i++) charges_[i] += charges_n_[i];
294 
295   //// compute scalar contributions
296   double A = solvent_->area();
297 
298   // the cavitation energy
299   ecavitation_ = A * gamma_;
300 
301   // compute the nuclear-surface interaction energy
302   tim_enter("n-s");
303   enucsurf_
304       = solvent_->nuclear_interaction_energy(charge_positions_, charges_);
305   tim_exit("n-s");
306 
307   double enqn = 0.0, enqe = 0.0;
308   if (y_equals_j_ || separate_surf_charges_) {
309       tim_enter("n-qn");
310       enqn = solvent_->nuclear_interaction_energy(charge_positions_,
311                                                   charges_n_);
312       enqe = enucsurf_ - enqn;
313       tim_exit("n-qn");
314     }
315 
316   //// compute one body contributions
317 
318   // compute the electron-surface interaction matrix elements
319   tim_enter("e-s");
320   Ref<PointChargeData> pc_dat = new PointChargeData(ncharge,
321                                                   charge_positions_, charges_);
322   Ref<OneBodyInt> pc = wfn_->integral()->point_charge(pc_dat);
323   Ref<SCElementOp> pc_op = new OneBodyIntOp(pc);
324 
325   // compute matrix elements in the ao basis
326   RefSymmSCMatrix h_ao(aodim, aokit);
327   h_ao.assign(0.0);
328   h_ao.element_op(pc_op);
329   // transform to the so basis and add to h
330   RefSymmSCMatrix h_so = wfn_->integral()->petite_list()->to_SO_basis(h_ao);
331   if (onebody_) h->accumulate(h_so);
332   // compute the contribution to the energy
333   sp->init();
334   RefSymmSCMatrix so_density = wfn_->density()->copy();
335   // for the scalar products, scale the density's off-diagonals by two
336   so_density->scale(2.0);
337   so_density->scale_diagonal(0.5);
338   h_so->element_op(generic_sp, so_density);
339   eelecsurf_ = sp->result();
340   tim_exit("e-s");
341 
342   double eeqn = 0.0, eeqe = 0.0;
343   if (y_equals_j_ || separate_surf_charges_) {
344       tim_enter("e-qn");
345       pc_dat = new PointChargeData(ncharge, charge_positions_, charges_n_);
346       pc = wfn_->integral()->point_charge(pc_dat);
347       pc_op = new OneBodyIntOp(pc);
348 
349       // compute matrix elements in the ao basis
350       h_ao.assign(0.0);
351       h_ao.element_op(pc_op);
352       // transform to the so basis
353       h_so = wfn_->integral()->petite_list()->to_SO_basis(h_ao);
354       // compute the contribution to the energy
355       sp->init();
356       h_so->element_op(generic_sp, so_density);
357       eeqn = sp->result();
358       eeqe = eelecsurf_ - eeqn;
359       tim_exit("e-qn");
360     }
361 
362   if (y_equals_j_) {
363       // Remove the y term (enqe) and add the j term (eeqn).  Formally,
364       // they are equal, but they are not because some e-density is outside
365       // the surface and because of the numerical approximations.
366       enucsurf_ += eeqn - enqe;
367     }
368 
369   // compute the surface-surface interaction energy
370   esurfsurf_ = -0.5*(eelecsurf_+enucsurf_);
371   // (this can also be computed as below, but is much more expensive)
372   //tim_enter("s-s");
373   //double esurfsurf_;
374   //esurfsurf_ = solvent_->self_interaction_energy(charge_positions_, charges_);
375   //tim_exit("s-s");
376 
377   escalar_ = enucsurf_ + esurfsurf_ + ecavitation_ + edisprep_;
378   // NOTE: SCF currently only adds h_so to the Fock matrix
379   // so a term is missing in the energy.  This term is added here
380   // and when SCF is fixed, should no longer be included.
381   if (onebody_) escalar_ += 0.5 * eelecsurf_;
382 
383   if (!onebody_) escalar_ += eelecsurf_;
384 
385   ExEnv::out0() << incindent;
386   ExEnv::out0() << indent
387        << "Solvent: "
388        << scprintf("q(e-enc)=%12.10f q(n-enc)=%12.10f", qeenc, qnenc)
389        << endl;
390   ExEnv::out0() << incindent;
391   if (separate_surf_charges_) {
392       ExEnv::out0() << indent
393            << scprintf("E(n-qn)=%10.8f ", enqn)
394            << scprintf("E(n-qe)=%10.8f", enqe)
395            << endl;
396       ExEnv::out0() << indent
397            << scprintf("E(e-qn)=%10.8f ", eeqn)
398            << scprintf("E(e-qe)=%10.8f", eeqe)
399            << endl;
400       //ExEnv::out0() << indent
401       //     << scprintf("DG = %12.8f ", 0.5*627.51*(enqn+enqe+eeqn+eeqe))
402       //     << scprintf("DG(Y=J) = %12.8f", 0.5*627.51*(enqn+2*eeqn+eeqe))
403       //     << endl;
404     }
405   ExEnv::out0() << indent
406        << scprintf("E(c)=%10.8f ", ecavitation_)
407        << scprintf("E(disp-rep)=%10.8f", edisprep_)
408        << endl;
409   ExEnv::out0() << indent
410        << scprintf("E(n-s)=%10.8f ", enucsurf_)
411        << scprintf("E(e-s)=%10.8f ", eelecsurf_)
412        << scprintf("E(s-s)=%10.8f ", esurfsurf_)
413        << endl;
414   ExEnv::out0() << decindent;
415   ExEnv::out0() << decindent;
416 
417   tim_exit("accum");
418   tim_exit("solvent");
419 }
420 
421 void
done()422 BEMSolventH::done()
423 {
424   solvent_->free_normals(normals_);
425   normals_ = 0;
426   solvent_->free_efield_dot_normals(efield_dot_normals_);
427   efield_dot_normals_ = 0;
428   solvent_->free_charges(charges_);
429   solvent_->free_charges(charges_n_);
430   charges_ = 0;
431   charges_n_ = 0;
432   solvent_->free_charge_positions(charge_positions_);
433   charge_positions_ = 0;
434   solvent_->done();
435 }
436 
437 void
print_summary()438 BEMSolventH::print_summary()
439 {
440   Ref<Units> unit = new Units("kcal/mol");
441   ExEnv::out0() << endl;
442   ExEnv::out0() << "Summary of solvation calculation:" << endl;
443   ExEnv::out0() << "_______________________________________________" << endl;
444   ExEnv::out0() << endl;
445   ExEnv::out0().setf(ios::scientific,ios::floatfield); // use scientific format
446   ExEnv::out0().precision(5);
447   ExEnv::out0() << indent << "E(nuc-surf):              "
448        << setw(12) << setfill(' ')
449        << enucsurf_*unit->from_atomic_units() << " kcal/mol" << endl;
450   ExEnv::out0() << indent << "E(elec-surf):             "
451        << setw(12) << setfill(' ')
452        << eelecsurf_*unit->from_atomic_units() << " kcal/mol" << endl;
453   ExEnv::out0() << indent << "E(surf-surf):             "
454        << setw(12) << setfill(' ')
455        << esurfsurf_*unit->from_atomic_units() << " kcal/mol" << endl;
456   ExEnv::out0() << indent << "Electrostatic energy:     "
457        << setw(12) << setfill(' ')
458        << (enucsurf_+eelecsurf_+esurfsurf_)*unit->from_atomic_units()
459        << " kcal/mol" << endl;
460   ExEnv::out0() << "_______________________________________________" << endl;
461   ExEnv::out0() << endl;
462   ExEnv::out0() << indent << "E(cav):                   "
463        << setw(12) << setfill(' ')
464        << ecavitation_*unit->from_atomic_units() << " kcal/mol" << endl;
465   ExEnv::out0() << indent << "E(disp):                  "
466        << setw(12) << setfill(' ')
467        << solvent_->disp()*unit->from_atomic_units() << " kcal/mol" << endl;
468   ExEnv::out0() << indent << "E(rep):                   "
469        << setw(12) << setfill(' ')
470        << solvent_->rep()*unit->from_atomic_units() << " kcal/mol" << endl;
471   ExEnv::out0() << indent << "Non-electrostatic energy: "
472        << setw(12) << setfill(' ')
473        << (ecavitation_+solvent_->disp()+solvent_->rep())
474           *unit->from_atomic_units() << " kcal/mol" << endl;
475   ExEnv::out0() << "_______________________________________________" << endl;
476 
477 }
478 
479 double
e()480 BEMSolventH::e()
481 {
482   return escalar_;
483 }
484 
485 /////////////////////////////////////////////////////////////////////////////
486 
487 }
488 
489 // Local Variables:
490 // mode: c++
491 // c-file-style: "CLJ"
492 // End:
493