1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2014 The Regents of the University of California
4 //
5 // This file is part of Qbox
6 //
7 // Qbox is distributed under the terms of the GNU General Public License
8 // as published by the Free Software Foundation, either version 2 of
9 // the License, or (at your option) any later version.
10 // See the file COPYING in the root directory of this distribution
11 // or <http://www.gnu.org/licenses/>.
12 //
13 ////////////////////////////////////////////////////////////////////////////////
14 //
15 // ElectricEnthalpy.cpp
16 //
17 ////////////////////////////////////////////////////////////////////////////////
18
19 #include <iostream>
20 #include <iomanip>
21 #include <complex>
22 #include <cassert>
23 #include <cmath>
24 #include <algorithm> // fill
25
26 #include "Timer.h"
27 #include "Context.h"
28 #include "Matrix.h"
29 #include "Sample.h"
30 #include "D3vector.h"
31 #include "ElectricEnthalpy.h"
32 #include "MLWFTransform.h"
33 #include "Wavefunction.h"
34 #include "D3vector.h"
35 #include "Basis.h"
36 #include "SlaterDet.h"
37 #include "ChargeDensity.h"
38 #include "FourierTransform.h"
39 #include "UnitCell.h"
40 using namespace std;
41
42 ///////////////////////////////////////////////////////////////////////////////
vsst(double x) const43 double ElectricEnthalpy::vsst(double x) const
44 {
45 // smooth sawtooth periodic potential function
46 // x in [-1/2, 1/2]
47 // The slope of vsst is 1 at x=0
48 //
49 // The function vsst approximates the identity function x->x
50 // in the interval [-1/2+xcut, 1/2-xcut]
51 const double xcut = 0.05;
52 const double xcut2 = xcut*xcut;
53 // The function vsst is well represented by a
54 // discrete Fourier transform of length np = 2*ng
55 // Some aliasing error will arise if np < 2*ng
56 // The error is determined by the product xcut*ng
57 const int ng = 32;
58 double v = 0.0;
59 for ( int ig = 1; ig < ng; ig++ )
60 {
61 const double g = 2 * M_PI * ig;
62 const double arg = -0.25 * g * g * xcut2;
63 // next line: factor sgn to shift origin by 0.5
64 const int sgn = 1 - 2*(ig%2);
65 const double c = -2.0 * sgn * exp ( arg ) / g;
66 v += c * sin(x*g);
67 }
68 return v;
69 }
70
71 ///////////////////////////////////////////////////////////////////////////////
ElectricEnthalpy(Sample & s)72 ElectricEnthalpy::ElectricEnthalpy(Sample& s): s_(s), wf_(s.wf),
73 sd_(*(s.wf.sd(0,0))), ctxt_(s.wf.sd(0,0)->context()),
74 basis_(s.wf.sd(0,0)->basis())
75 {
76 onpe0_ = MPIdata::onpe0();
77 e_field_ = s.ctrl.e_field;
78 finite_field_ = norm2(e_field_) != 0.0;
79 compute_quadrupole_ = false;
80
81 if ( s.ctrl.polarization == "OFF" )
82 pol_type_ = off;
83 else if ( s.ctrl.polarization == "BERRY" )
84 pol_type_ = berry;
85 else if ( s.ctrl.polarization == "MLWF" )
86 pol_type_ = mlwf;
87 else if ( s.ctrl.polarization == "MLWF_REF" )
88 pol_type_ = mlwf_ref;
89 else if ( s.ctrl.polarization == "MLWF_REF_Q" )
90 {
91 pol_type_ = mlwf_ref;
92 compute_quadrupole_ = true;
93 }
94 else
95 {
96 cerr << "ElectricEnthalpy: invalid polarization option" << endl;
97 ctxt_.abort(1);
98 }
99
100 // do not allocate further objects if polarization is off
101 if ( pol_type_ == off ) return;
102
103 assert(wf_.nkp()==1);
104 assert(wf_.nspin()==1);
105
106 dwf_ = new Wavefunction(s.wf);
107 mlwft_ = new MLWFTransform(sd_);
108 mlwft_->set_tol(1.e-10);
109
110 smat_[0] = smat_[1] = smat_[2] = 0;
111 rwf_[0] = rwf_[1] = rwf_[2] = 0;
112 int nst = sd_.nst();
113
114 if ( pol_type_ == mlwf_ref || pol_type_ == mlwf_ref_q )
115 {
116 // allocate real space wf arrays for MLWF refinement
117 for ( int idir = 0; idir < 3; idir++ )
118 rwf_[idir] = new Wavefunction(wf_);
119 correction_.resize(nst);
120 }
121 else if ( pol_type_ == berry )
122 {
123 // allocate complex Berry phase matrix
124 int n = sd_.c().n();
125 int nb = sd_.c().nb();
126 for ( int idir = 0; idir < 3; idir++ )
127 smat_[idir] = new ComplexMatrix(ctxt_,n,n,nb,nb);
128 }
129
130 if ( (pol_type_ != off) && onpe0_ )
131 {
132 cout.setf(ios::fixed,ios::floatfield);
133 cout.setf(ios::right,ios::adjustfield);
134 cout.precision(8);
135 cout << "<e_field> " << e_field_ << " </e_field>" << endl;
136 }
137
138 mlwfc_.resize(nst);
139 mlwfs_.resize(nst);
140 quad_.resize(nst);
141
142 tmap["mlwf_update"].reset();
143 tmap["mlwf_trans"].reset();
144 tmap["correction"].reset();
145 tmap["ft"].reset();
146 tmap["real"].reset();
147 tmap["ft"].reset();
148 tmap["dsum"].reset();
149 tmap["real"].reset();
150 }
151
152 ///////////////////////////////////////////////////////////////////////////////
~ElectricEnthalpy(void)153 ElectricEnthalpy::~ElectricEnthalpy(void)
154 {
155 if ( pol_type_ == off ) return;
156
157 delete dwf_;
158 delete mlwft_;
159 for ( int idir = 0; idir < 3; idir++ )
160 {
161 delete rwf_[idir];
162 delete smat_[idir];
163 }
164
165 for ( TimerMap::iterator i = tmap.begin(); i != tmap.end(); i++ )
166 {
167 double time = i->second.real();
168 double tmin, tmax;
169 MPI_Reduce(&time,&tmin,1,MPI_DOUBLE,MPI_MIN,0,MPIdata::comm());
170 MPI_Reduce(&time,&tmax,1,MPI_DOUBLE,MPI_MAX,0,MPIdata::comm());
171 if ( MPIdata::onpe0() && (tmax > 0.0) )
172 {
173 string s = "name=\"" + (*i).first + "\"";
174 cout << "<timing " << left << setw(22) << s
175 << " min=\"" << setprecision(3) << tmin << "\""
176 << " max=\"" << setprecision(3) << tmax << "\"/>"
177 << endl;
178 }
179 }
180 }
181
182 ///////////////////////////////////////////////////////////////////////////////
update(void)183 void ElectricEnthalpy::update(void)
184 {
185 if ( pol_type_ == off ) return;
186
187 const UnitCell& cell = sd_.basis().cell();
188 // compute cos and sin matrices
189 tmap["mlwf_update"].start();
190 mlwft_->update();
191 tmap["mlwf_update"].stop();
192 vector<SlaterDet*> sdcos(3), sdsin(3);
193 sdcos[0] = mlwft_->sdcosx();
194 sdcos[1] = mlwft_->sdcosy();
195 sdcos[2] = mlwft_->sdcosz();
196 sdsin[0] = mlwft_->sdsinx();
197 sdsin[1] = mlwft_->sdsiny();
198 sdsin[2] = mlwft_->sdsinz();
199
200 dipole_ion_ = s_.atoms.dipole();
201 dipole_el_ = D3vector(0,0,0);
202
203 if ( pol_type_ == mlwf || pol_type_ == mlwf_ref || pol_type_ == mlwf_ref_q )
204 {
205 tmap["mlwf_trans"].start();
206 mlwft_->compute_transform();
207 mlwft_->apply_transform(sd_);
208 tmap["mlwf_trans"].stop();
209 for ( int i = 0; i < sd_.nst(); i++ )
210 {
211 mlwfc_[i] = mlwft_->center(i);
212 mlwfs_[i] = mlwft_->spread(i);
213 }
214
215 if ( pol_type_ == mlwf_ref || pol_type_ == mlwf_ref_q )
216 {
217 tmap["correction"].start();
218 compute_correction();
219 tmap["correction"].stop();
220 }
221
222 for ( int i = 0; i < sd_.nst(); i++ )
223 {
224 dipole_el_ -= 2.0 * mlwfc_[i];
225 if ( pol_type_ == mlwf_ref || pol_type_ == mlwf_ref_q )
226 dipole_el_ -= 2.0 * correction_[i];
227 }
228
229 // compute gradient
230 if ( finite_field_ )
231 {
232 dwf_->clear();
233 for ( int idir = 0; idir < 3; idir++ )
234 {
235 if ( e_field_[idir] != 0.0 )
236 {
237 // MLWF part
238 if ( pol_type_ == mlwf )
239 {
240 const double nst = sd_.nst();
241 std::vector<double> adiag_inv_real(nst,0),adiag_inv_imag(nst,0);
242 for ( int ist = 0; ist < nst; ist ++ )
243 {
244 const std::complex<double>
245 adiag( mlwft_->adiag(idir*2,ist),mlwft_->adiag(idir*2+1,ist) );
246
247 adiag_inv_real[ist] = real( std::complex<double>(1,0) / adiag );
248 adiag_inv_imag[ist] = imag( std::complex<double>(1,0) / adiag );
249
250 }
251
252 DoubleMatrix ccos(sdcos[idir]->c());
253 DoubleMatrix csin(sdsin[idir]->c());
254 DoubleMatrix cp(dwf_->sd(0,0)->c());
255
256 int nloc = cp.nloc();
257 int mloc = cp.mloc();
258 int ione = 1;
259
260 const double fac = cell.a_norm(idir)
261 * e_field_[idir] / ( 2.0 * M_PI );
262
263 for (int in = 0; in < nloc; in++)
264 {
265 int ist = cp.jglobal(in);
266 double fac1 = adiag_inv_real[ist] * fac;
267 double fac2 = adiag_inv_imag[ist] * fac;
268
269 double *ptr1 = &cp[in*mloc],
270 *ptrcos = &ccos[in*mloc],
271 *ptrsin = &csin[in*mloc];
272
273 daxpy(&mloc, &fac2, ptrcos, &ione, ptr1, &ione);
274 daxpy(&mloc, &fac1, ptrsin, &ione, ptr1, &ione);
275
276 }
277 }
278 else if ( pol_type_ == mlwf_ref || pol_type_ == mlwf_ref_q )
279 {
280 // MLWF_REF part: real-space correction
281 DoubleMatrix cc(rwf_[idir]->sd(0,0)->c());
282 DoubleMatrix cp(dwf_->sd(0,0)->c());
283
284 int size = cc.size();
285 double alpha = e_field_[idir];
286 int ione = 1;
287 daxpy (&size, &alpha, cc.valptr(), &ione, cp.valptr(), &ione);
288 } // if pol_type_
289 } // if e_field_[idir]
290 } // for idir
291 } // if finite_field_
292 }
293 else if ( pol_type_ == berry )
294 {
295 // proxy matrix
296 DoubleMatrix gradp(dwf_->sd(0,0)->c());
297 if ( finite_field_ )
298 dwf_->clear();
299
300 for ( int idir = 0; idir < 3; idir++ )
301 {
302 const double fac = cell.a_norm(idir)/( 2.0*M_PI );
303 complex<double>* val = smat_[idir]->valptr();
304
305 const double* re = mlwft_->a(idir*2)->cvalptr();
306 const double* im = mlwft_->a(idir*2+1)->cvalptr();
307 for ( int i = 0; i < smat_[idir]->size(); i++ )
308 val[i] = complex<double>(re[i],im[i]);
309
310 // compute determinant of S
311 ComplexMatrix& sm = *smat_[idir];
312 const Context& ctxt = sm.context();
313
314 // perform LU decomposition of S
315 valarray<int> ipiv;
316 sm.lu(ipiv);
317
318 int n = sm.n();
319 int nb = sm.nb();
320 // note: m == n, mb == nb
321
322 // compute determinant from diagonal values of U (stored in diag of S)
323 // get full diagonal of the matrix in array diag
324 valarray<complex<double> > diag(n);
325 for ( int ii = 0; ii < n; ii++ )
326 {
327 int iii = sm.l(ii) * nb + sm.x(ii);
328 int jjj = sm.m(ii) * nb + sm.y(ii);
329 if ( sm.pr(ii) == ctxt.myrow() &&
330 sm.pc(ii) == ctxt.mycol() )
331 diag[ii] = val[iii+sm.mloc()*jjj];
332 }
333 ctxt.dsum(2*n,1,(double*)&diag[0],2*n);
334
335 // sum the argument of diagonal elements
336 double sumarg = 0.0;
337 for ( int ii = 0; ii < n; ii++ )
338 sumarg += arg(diag[ii]);
339
340 // compute determinant
341 complex<double> det = 1.0;
342 for ( int ii = 0; ii < n; ii++ )
343 det *= diag[ii];
344
345 const int sign = sm.signature(ipiv);
346 if ( sign == -1 )
347 sumarg += M_PI;
348
349 // assume occupation number of 2.0
350 dipole_el_[idir] = - 2.0 * fac * sumarg;
351
352 if ( finite_field_ )
353 {
354 // compute inverse of smat
355 sm.inverse_from_lu(ipiv);
356
357 // real and img part of S^{-1}
358 DoubleMatrix s_real(ctxt_,n,n,nb,nb);
359 DoubleMatrix s_img(ctxt_,n,n,nb,nb);
360 DoubleMatrix sp(sm);
361
362 int size = s_real.size();
363 int ione = 1, itwo = 2;
364
365 // copy real and imaginary parts of s to s_real and s_img
366 dcopy(&size,sp.valptr(),&itwo,s_real.valptr(),&ione);
367 dcopy(&size,sp.valptr()+1,&itwo,s_img.valptr(),&ione);
368
369 // proxy Matrix for cosx|psi> and sinx|psi>
370 DoubleMatrix cosp(sdcos[idir]->c());
371 DoubleMatrix sinp(sdsin[idir]->c());
372
373 // alpha = E_i * L_i / 2pi
374 double alpha = fac * e_field_[idir];
375
376 gradp.gemm('n','n',alpha,cosp,s_img,1.0);
377 gradp.gemm('n','n',alpha,sinp,s_real,1.0);
378 }
379 } // for idir
380 }
381
382 dipole_total_ = dipole_ion_ + dipole_el_;
383 cell.fold_in_ws(dipole_ion_);
384 cell.fold_in_ws(dipole_el_);
385 cell.fold_in_ws(dipole_total_);
386 }
387
388 ///////////////////////////////////////////////////////////////////////////////
enthalpy(Wavefunction & dwf,bool compute_hpsi)389 double ElectricEnthalpy::enthalpy(Wavefunction& dwf, bool compute_hpsi)
390 {
391 // return zero if polarization is off, or field is zero
392 if ( pol_type_ == off || !finite_field_ )
393 return 0.0;
394
395 enthalpy_ = - e_field_ * dipole_total_;
396 if ( compute_hpsi )
397 {
398 // assert gamma-only and no spin
399 assert(dwf.nkp()==1 && dwf.nspin()==1);
400 dwf.sd(0,0)->c() += dwf_->sd(0,0)->c();
401 }
402 return enthalpy_;
403 }
404
405 ///////////////////////////////////////////////////////////////////////////////
406 // Correction scheme by M. Stengel and N. Spaldin,
407 // Phys. Rev. B 73, 075121 (2006)
408 // Calculate correction in real space and derivatives of the correction
409 ///////////////////////////////////////////////////////////////////////////////
compute_correction(void)410 void ElectricEnthalpy::compute_correction(void)
411 {
412 int np0v = basis_.np(0);
413 int np1v = basis_.np(1);
414 int np2v = basis_.np(2);
415 const ComplexMatrix& c = sd_.c();
416 DoubleMatrix cp(c);
417
418 FourierTransform ft(basis_,np0v,np1v,np2v);
419
420 int np012v = ft.np012();
421 int np012loc = ft.np012loc();
422 int nst = sd_.nst();
423 int nloc = c.nloc();
424 int mloc = c.mloc();
425
426 // store (x-x0)|psi> in rwfs
427 rwf_[0]->clear();
428 rwf_[1]->clear();
429 rwf_[2]->clear();
430
431 ComplexMatrix& cx = rwf_[0]->sd(0,0)->c();
432 ComplexMatrix& cy = rwf_[1]->sd(0,0)->c();
433 ComplexMatrix& cz = rwf_[2]->sd(0,0)->c();
434
435 DoubleMatrix cpx(cx);
436 DoubleMatrix cpy(cy);
437 DoubleMatrix cpz(cz);
438
439 // calculate refinements
440 // ref is scaled by np012v
441 vector<double> ref(nst*3);
442 if ( compute_quadrupole_ ) ref.resize(nst*9);
443
444 // cell size;
445 const UnitCell& cell = sd_.basis().cell();
446 const double ax = cell.amat(0);
447 const double ay = cell.amat(4);
448 const double az = cell.amat(8);
449
450 // half cell size;
451 const double ax2 = ax / 2.0;
452 const double ay2 = ay / 2.0;
453 const double az2 = az / 2.0;
454
455 // grid size;
456 const double dx = ax / np0v;
457 const double dy = ay / np1v;
458 const double dz = az / np2v;
459
460 for ( int i = 0; i < nst; i++ )
461 correction_[i] = D3vector(0,0,0);
462
463 int niter = 1;
464 // check if override from the debug variable
465 // use: set debug mlwf_ref_niter <value>
466 const map<string,string>& debug_map = s_.ctrl.debug;
467
468 map<string,string>::const_iterator imap =
469 debug_map.find("MLWF_REF_NITER");
470 if ( imap != debug_map.end() )
471 {
472 const string val = imap->second;
473 istringstream is(val);
474 is >> niter;
475 if ( MPIdata::onpe0() )
476 cout << " override mlwf_ref_niter value: niter = " << niter << endl;
477 assert(niter > 0);
478 }
479 for ( int iter = 0; iter < niter; iter++ )
480 {
481 fill(ref.begin(),ref.end(),0.0);
482
483 // wavefunctions in real space
484 vector<complex<double> > wftmp(np012loc);
485 vector<complex<double> > xwftmp(np012loc);
486 vector<complex<double> > ywftmp(np012loc);
487 vector<complex<double> > zwftmp(np012loc);
488
489 for ( int in = 0; in < nloc; in++ )
490 {
491 int n = c.jglobal(in);
492 double* pref;
493 if ( compute_quadrupole_ )
494 pref = &ref[9*n];
495 else
496 pref = &ref[3*n];
497
498 // real space wavefunction in wftmp
499 tmap["ft"].start();
500 ft.backward(c.cvalptr(mloc*in),&wftmp[0]);
501 tmap["ft"].stop();
502
503 tmap["real"].start();
504 double x0 = mlwfc_[n][0] + correction_[n][0];
505 double y0 = mlwfc_[n][1] + correction_[n][1];
506 double z0 = mlwfc_[n][2] + correction_[n][2];
507
508 // compute shifted sawtooth potentials vx, vy, vz
509 vector<double> vx(ft.np0()), vy(ft.np1()), vz(ft.np2());
510 for ( int i = 0; i < vx.size(); i++ )
511 {
512 double x = i * dx - x0;
513 if ( x > ax2 ) x -= ax;
514 if ( x < -ax2 ) x += ax;
515 #ifdef NO_VSST
516 vx[i] = x;
517 #else
518 vx[i] = ax * vsst(x/ax);
519 #endif
520 }
521 for ( int j = 0; j < vy.size(); j++ )
522 {
523 double y = j * dy - y0;
524 if ( y > ay2 ) y -= ay;
525 if ( y < -ay2 ) y += ay;
526 #ifdef NO_VSST
527 vy[j] = y;
528 #else
529 vy[j] = ay * vsst(y/ay);
530 #endif
531 }
532 for ( int k = 0; k < vz.size(); k++ )
533 {
534 double z = k * dz - z0;
535 if ( z > az2 ) z -= az;
536 if ( z < -az2 ) z += az;
537 #ifdef NO_VSST
538 vz[k] = z;
539 #else
540 vz[k] = az * vsst(z/az);
541 #endif
542 }
543
544 for ( int i = 0; i < np012loc; i++ )
545 {
546 int ix = ft.i(i);
547 int iy = ft.j(i);
548 int iz = ft.k(i);
549
550 const double wft = real(wftmp[i]);
551 const double xwft = wft * vx[ix];
552 const double ywft = wft * vy[iy];
553 const double zwft = wft * vz[iz];
554
555 pref[0] += wft * xwft;
556 pref[1] += wft * ywft;
557 pref[2] += wft * zwft;
558
559 xwftmp[i] = xwft;
560 ywftmp[i] = ywft;
561 zwftmp[i] = zwft;
562
563 if ( compute_quadrupole_ )
564 {
565 pref[3] += xwft * xwft;
566 pref[4] += ywft * ywft;
567 pref[5] += zwft * zwft;
568 pref[6] += xwft * ywft;
569 pref[7] += ywft * zwft;
570 pref[8] += zwft * xwft;
571 }
572 } // for i
573 tmap["real"].stop();
574
575 // ft to get xwf in reciprocal space at the last iteration
576 if ( iter == niter - 1 )
577 {
578 tmap["ft"].start();
579 if ( e_field_[0] != 0.0 )
580 ft.forward(&xwftmp[0],cx.valptr(mloc*in));
581 if ( e_field_[1] != 0.0 )
582 ft.forward(&ywftmp[0],cy.valptr(mloc*in));
583 if ( e_field_[2] != 0.0 )
584 ft.forward(&zwftmp[0],cz.valptr(mloc*in));
585 tmap["ft"].stop();
586 } // if
587 } //for in
588
589 ctxt_.barrier();
590 tmap["dsum"].start();
591 if ( compute_quadrupole_ )
592 ctxt_.dsum(9*nst,1,&ref[0],9*nst);
593 else
594 ctxt_.dsum(3*nst,1,&ref[0],3*nst);
595 tmap["dsum"].stop();
596
597 tmap["real"].start();
598 if ( compute_quadrupole_ )
599 {
600 for ( int ist = 0; ist < nst; ist++ )
601 {
602 D3vector& pcor = correction_[ist];
603 D3tensor& pquad = quad_[ist];
604
605 pcor[0] += ref[ist*9]/np012v;
606 pcor[1] += ref[ist*9+1]/np012v;
607 pcor[2] += ref[ist*9+2]/np012v;
608 pquad.setdiag ( 0, ref[ist*9+3]/np012v - pcor[0] * pcor[0] );
609 pquad.setdiag ( 1, ref[ist*9+4]/np012v - pcor[1] * pcor[1] );
610 pquad.setdiag ( 2, ref[ist*9+5]/np012v - pcor[2] * pcor[2] );
611 pquad.setoffdiag ( 0, ref[ist*9+6]/np012v - pcor[0] * pcor[1] );
612 pquad.setoffdiag ( 1, ref[ist*9+7]/np012v - pcor[1] * pcor[2] );
613 pquad.setoffdiag ( 2, ref[ist*9+8]/np012v - pcor[2] * pcor[0] );
614 }
615 }
616 else
617 {
618 for ( int ist = 0; ist < nst; ist++ )
619 {
620 D3vector& pcor = correction_[ist];
621 pcor[0] += ref[ist*3]/np012v;
622 pcor[1] += ref[ist*3+1]/np012v;
623 pcor[2] += ref[ist*3+2]/np012v;
624 }
625 }
626 tmap["real"].stop();
627 } // for iter
628 }
629
630 ////////////////////////////////////////////////////////////////////////////////
print(ostream & os) const631 void ElectricEnthalpy::print(ostream& os) const
632 {
633 if ( pol_type_ == off ) return;
634
635 os << fixed << right << setprecision(8);
636 // print MLWF centers if pol_type_ == MLWF or MLWF_REF or MLWF_REF_Q
637 if ( pol_type_ == mlwf || pol_type_ == mlwf_ref || pol_type_ == mlwf_ref_q )
638 {
639 int nst = sd_.nst();
640 os << " <mlwf_set size=\"" << nst << "\">" << endl;
641 for ( int i = 0; i < nst; i++ )
642 {
643 os << " <mlwf center=\"" << setprecision(8)
644 << setw(12) << mlwfc_[i].x << " "
645 << setw(12) << mlwfc_[i].y << " "
646 << setw(12) << mlwfc_[i].z << " \"\n"
647 << " spread=\" " << mlwfs_[i] << " \"/>" << endl;
648 if ( pol_type_ == mlwf_ref )
649 {
650 os << " <mlwf_ref center=\"" << setprecision(8)
651 << setw(12) << mlwfc_[i].x + correction_[i].x << " "
652 << setw(12) << mlwfc_[i].y + correction_[i].y << " "
653 << setw(12) << mlwfc_[i].z + correction_[i].z << " \"";
654 if ( compute_quadrupole_ )
655 {
656 // add spread attribute
657 os << " \n spread=\" " << sqrt(quad_[i].trace()) << " \"";
658 }
659 os << "/>" << endl;
660
661 if ( compute_quadrupole_ )
662 os << " <quad>"
663 << setw(12) << quad_[i][0] << " "
664 << setw(12) << quad_[i][4] << " "
665 << setw(12) << quad_[i][8] << " "
666 << setw(12) << quad_[i][1] << " "
667 << setw(12) << quad_[i][2] << " "
668 << setw(12) << quad_[i][5]
669 << " </quad>" << endl;
670 }
671 }
672 os << " </mlwf_set>" << endl;
673 }
674
675 // print dipole
676 os << setprecision(10) << fixed << right;
677 os << " <dipole>\n";
678 os << " <dipole_ion> "
679 << setw(14) << dipole_ion_.x << " "
680 << setw(14) << dipole_ion_.y << " "
681 << setw(14) << dipole_ion_.z << " </dipole_ion>\n";
682 os << " <dipole_el> "
683 << setw(14) << dipole_el_.x << " "
684 << setw(14) << dipole_el_.y << " "
685 << setw(14) << dipole_el_.z << " </dipole_el>\n";
686 os << " <dipole_total> "
687 << setw(14) << dipole_total_.x << " "
688 << setw(14) << dipole_total_.y << " "
689 << setw(14) << dipole_total_.z << " </dipole_total>\n";
690 os << " </dipole>\n";
691
692 if ( compute_quadrupole_ )
693 {
694 D3tensor q_mlwfc;
695 D3tensor q_mlwfs;
696 for ( int ist = 0; ist < sd_.nst(); ist++ )
697 {
698 D3vector ctr = mlwfc_[ist];
699 for (int j=0; j<3; j++)
700 {
701 for (int k = 0; k < 3; k++)
702 q_mlwfc[j*3+k] -= 2.0 * ctr[j] * ctr[k];
703 }
704 q_mlwfs -= quad_[ist] * 2.0;
705 } // for ist
706
707 D3tensor q_ion = s_.atoms.quadrupole();
708 D3tensor q_mlwf = q_mlwfc + q_mlwfs;
709 //total primitive quadrupoles
710 D3tensor q_total = q_ion + q_mlwf;
711 //traceless quadrupole
712 D3tensor q_traceless = q_total;
713 q_traceless.traceless();
714
715 os << " <quadrupole> " << endl;
716 os << " <quadrupole_ion> " << endl
717 << q_ion
718 << " </quadrupole_ion>" << endl;
719 os << " <quadrupole_el> " << endl
720 << q_mlwf
721 << " </quadrupole_el>" << endl;
722 os << " <quadrupole_total> " << endl
723 << q_total
724 << " </quadrupole_total>" << endl;
725 os << " <traceless_quadrupole> " << endl
726 << q_traceless
727 << " </traceless_quadrupole>" << endl;
728 char uplo = 'u';
729 D3vector eigval;
730 D3tensor eigvec;
731 q_traceless.syev(uplo, eigval, eigvec);
732 os << " <traceless_quadrupole_eigval> " << endl
733 << " " << eigval << endl
734 << " </traceless_quadrupole_eigval>" << endl;
735 os << " <traceless_quadrupole_eigvec> " << endl
736 << eigvec
737 << " </traceless_quadrupole_eigvec>" << endl;
738 os << " </quadrupole> " << endl;
739 }
740
741 }
742
743 ////////////////////////////////////////////////////////////////////////////////
set_e_field(D3vector e_field_val)744 void ElectricEnthalpy::set_e_field(D3vector e_field_val)
745 {
746 e_field_ = e_field_val;
747 finite_field_ = norm2(e_field_) != 0.0;
748 }
749
750 ////////////////////////////////////////////////////////////////////////////////
operator <<(ostream & os,const ElectricEnthalpy & e)751 ostream& operator<< ( ostream& os, const ElectricEnthalpy& e )
752 {
753 e.print(os);
754 return os;
755 }
756