SIRIUS 7.5.0
Electronic structure library and applications
xc.cpp
Go to the documentation of this file.
1// Copyright (c) 2013-2017 Anton Kozhevnikov, Thomas Schulthess
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without modification, are permitted provided that
5// the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
8// following disclaimer.
9// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
10// and the following disclaimer in the documentation and/or other materials provided with the distribution.
11//
12// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
13// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
14// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
15// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
16// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
17// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
18// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
20/** \file xc.cpp
21 *
22 * \brief Generate XC potential.
23 */
24
25#include <vector>
26
27#include "potential.hpp"
28#include "core/typedefs.hpp"
29#include "core/omp.hpp"
30#include "core/profiler.hpp"
31#include "xc_functional.hpp"
32
33namespace sirius {
34
35template <bool add_pseudo_core__>
37{
38 PROFILE("sirius::Potential::xc_rg_nonmagnetic");
39
40 bool const use_2nd_deriv{false};
41
42 auto gvp = ctx_.gvec_fft_sptr();
43
44 bool is_gga = is_gradient_correction();
45
46 int num_points = ctx_.spfft<double>().local_slice_size();
47
48 Smooth_periodic_function<double> rho(ctx_.spfft<double>(), gvp);
49
50 /* we can use this comm for parallelization */
51 //auto& comm = ctx_.gvec().comm_ortho_fft();
52 /* split real-space points between available ranks */
53 //splindex<block> spl_np(num_points, comm.size(), comm.rank());
54
55 /* check for negative values */
56 double rhomin{0};
57 for (int ir = 0; ir < num_points; ir++) {
58
59 //int ir = spl_np[irloc];
60 double d = density__.rho().rg().value(ir);
61 if (add_pseudo_core__) {
62 d += density__.rho_pseudo_core().value(ir);
63 }
64 d *= (1 + add_delta_rho_xc_);
65
66 rhomin = std::min(rhomin, d);
67 rho.value(ir) = std::max(d, 0.0);
68 }
69 mpi::Communicator(ctx_.spfft<double>().communicator()).allreduce<double, mpi::op_t::min>(&rhomin, 1);
70 /* even a small negative density is a sign of something bing wrong; don't remove this check */
71 if (rhomin < 0.0 && ctx_.comm().rank() == 0) {
72 std::stringstream s;
73 s << "Interstitial charge density has negative values" << std::endl
74 << "most negatve value : " << rhomin;
75 RTE_WARNING(s);
76 }
77
78 if (env::print_hash()) {
79 auto h = rho.hash_f_rg();
80 print_hash("rho", h, ctx_.out());
81 }
82
83 if (env::print_checksum()) {
84 auto cs = density__.rho().rg().checksum_rg();
85 print_checksum("rho_rg", cs, ctx_.out());
86 }
87
90 Smooth_periodic_function<double> grad_rho_grad_rho;
91
92 Smooth_periodic_function<double> div_vsigma_grad_rho;
93
94 if (is_gga) {
95 /* use fft_transfrom of the base class (Smooth_periodic_function) */
96 rho.fft_transform(-1);
97
98 /* generate pw coeffs of the gradient */
99 grad_rho = gradient(rho);
100 /* generate pw coeffs of the laplacian */
101 if (use_2nd_deriv) {
102 lapl_rho = laplacian(rho);
103 /* Laplacian in real space */
104 lapl_rho.fft_transform(1);
105 }
106
107 /* gradient in real space */
108 for (int x: {0, 1, 2}) {
109 grad_rho[x].fft_transform(1);
110 }
111
112 /* product of gradients */
113 grad_rho_grad_rho = dot(grad_rho, grad_rho);
114
115 if (env::print_hash()) {
116 //auto h1 = lapl_rho.hash_f_rg();
117 auto h2 = grad_rho_grad_rho.hash_f_rg();
118 //utils::print_hash("lapl_rho", h1);
119 print_hash("grad_rho_grad_rho", h2, ctx_.out());
120 }
121 }
122
124 if (is_gga) {
125 vsigma = Smooth_periodic_function<double>(ctx_.spfft<double>(), ctx_.gvec_fft_sptr());
126 vsigma_[0]->zero();
127 }
128
129 sddk::mdarray<double, 1> exc(num_points, sddk::memory_t::host, "exc_tmp");
130 sddk::mdarray<double, 1> vxc(num_points, sddk::memory_t::host, "vxc_tmp");
131
132 /* loop over XC functionals */
133 for (auto& ixc: xc_func_) {
134 PROFILE_START("sirius::Potential::xc_rg_nonmagnetic|libxc");
135 if (ixc.is_vdw()) {
136#if defined(SIRIUS_USE_VDWXC)
137 /* all ranks should make a call because VdW uses FFT internaly */
138 if (num_points) {
139 /* Van der Walls correction */
140 ixc.get_vdw(&rho.value(0), &grad_rho_grad_rho.value(0), vxc.at(sddk::memory_t::host), &vsigma.value(0),
141 exc.at(sddk::memory_t::host));
142 } else {
143 ixc.get_vdw(nullptr, nullptr, nullptr, nullptr, nullptr);
144 }
145#else
146 RTE_THROW("You should not be there since SIRIUS is not compiled with libVDWXC support\n");
147#endif
148 } else {
149 if (num_points) {
150 #pragma omp parallel
151 {
152 /* split local size between threads */
153 splindex_block <>spl_t(num_points, n_blocks(omp_get_num_threads()), block_id(omp_get_thread_num()));
154 /* if this is an LDA functional */
155 if (ixc.is_lda()) {
156 ixc.get_lda(spl_t.local_size(), &rho.value(spl_t.global_offset()),
157 vxc.at(sddk::memory_t::host, spl_t.global_offset()),
158 exc.at(sddk::memory_t::host, spl_t.global_offset()));
159 }
160 /* if this is a GGA functional */
161 if (ixc.is_gga()) {
162 ixc.get_gga(spl_t.local_size(), &rho.value(spl_t.global_offset()),
163 &grad_rho_grad_rho.value(spl_t.global_offset()),
164 vxc.at(sddk::memory_t::host, spl_t.global_offset()),
165 &vsigma.value(spl_t.global_offset()),
166 exc.at(sddk::memory_t::host, spl_t.global_offset()));
167 }
168 } // omp parallel region
169 ///* this is the same expression between gga and vdw corrections.
170 // * The functionals are different that's all */
171 //for (int i = 0; i < spl_np_t.local_size(); i++) {
172 // /* add Exc contribution */
173 // exc_tmp(spl_np_t[i]) += exc_t[i];
174
175 // /* directly add to Vxc available contributions */
176 // //if (use_2nd_deriv) {
177 // // vxc_tmp(spl_np_t[i]) += (vrho_t[i] - 2 * vsigma_t[i] * lapl_rho.f_rg(spl_np_t[i]));
178 // //} else {
179 // // vxc_tmp(spl_np_t[i]) += vrho_t[i];
180 // //}
181 // vxc_tmp(spl_np_t[i]) += vrho_t[i];
182
183 // /* save the sigma derivative */
184 // vsigma_tmp(spl_np_t[i]) += vsigma_t[i];
185 } // num_points != 0
186 }
187 PROFILE_STOP("sirius::Potential::xc_rg_nonmagnetic|libxc");
188 if (ixc.is_gga()) { /* generic for gga and vdw */
189 #pragma omp parallel for
190 for (int ir = 0; ir < num_points; ir++) {
191 /* save for future reuse in XC stress calculation */
192 vsigma_[0]->value(ir) += vsigma.value(ir);
193 }
194
195 if (use_2nd_deriv) {
196 /* forward transform vsigma to plane-wave domain */
197 vsigma.fft_transform(-1);
198
199 /* gradient of vsigma in plane-wave domain */
200 auto grad_vsigma = gradient(vsigma);
201
202 /* backward transform gradient from pw to real space */
203 for (int x: {0, 1, 2}) {
204 grad_vsigma[x].fft_transform(1);
205 }
206
207 /* compute scalar product of two gradients */
208 auto grad_vsigma_grad_rho = dot(grad_vsigma, grad_rho);
209
210 /* add remaining term to Vxc */
211 #pragma omp parallel for
212 for (int ir = 0; ir < num_points; ir++) {
213 vxc(ir) -= 2 * (vsigma.value(ir) * lapl_rho.value(ir) + grad_vsigma_grad_rho.value(ir));
214 }
215 } else {
216 Smooth_periodic_vector_function<double> vsigma_grad_rho(ctx_.spfft<double>(), gvp);
217
218 for (int x: {0, 1, 2}) {
219 for (int ir = 0; ir < num_points; ir++) {
220 vsigma_grad_rho[x].value(ir) = grad_rho[x].value(ir) * vsigma.value(ir);
221 }
222 /* transform to plane wave domain */
223 vsigma_grad_rho[x].fft_transform(-1);
224 }
225 div_vsigma_grad_rho = divergence(vsigma_grad_rho);
226 /* transform to real space domain */
227 div_vsigma_grad_rho.fft_transform(1);
228 for (int ir = 0; ir < num_points; ir++) {
229 vxc(ir) -= 2 * div_vsigma_grad_rho.value(ir);
230 }
231 }
232 }
233 #pragma omp parallel for
234 for (int ir = 0; ir < num_points; ir++) {
235 xc_energy_density_->rg().value(ir) += exc(ir);
236 xc_potential_->rg().value(ir) += vxc(ir);
237 }
238 } // for loop over xc functionals
239
240 if (env::print_checksum()) {
241 auto cs = xc_potential_->rg().checksum_rg();
242 print_checksum("exc", cs, ctx_.out());
243 }
244}
245
246template <bool add_pseudo_core__>
247void Potential::xc_rg_magnetic(Density const& density__)
248{
249 PROFILE("sirius::Potential::xc_rg_magnetic");
250
251 bool is_gga = is_gradient_correction();
252
253 int num_points = ctx_.spfft<double>().local_slice_size();
254
255 auto result = get_rho_up_dn<add_pseudo_core__>(density__, add_delta_rho_xc_, add_delta_mag_xc_);
256
257 auto& rho_up = *result[0];
258 auto& rho_dn = *result[1];
259
260 if (env::print_hash()) {
261 auto h1 = rho_up.hash_f_rg();
262 auto h2 = rho_dn.hash_f_rg();
263 print_hash("rho_up", h1, ctx_.out());
264 print_hash("rho_dn", h2, ctx_.out());
265 }
266
269 Smooth_periodic_function<double> grad_rho_up_grad_rho_up;
270 Smooth_periodic_function<double> grad_rho_up_grad_rho_dn;
271 Smooth_periodic_function<double> grad_rho_dn_grad_rho_dn;
272
273 if (is_gga) {
274 PROFILE("sirius::Potential::xc_rg_magnetic|grad1");
275 /* get plane-wave coefficients of densities */
276 rho_up.fft_transform(-1);
277 rho_dn.fft_transform(-1);
278
279 /* generate pw coeffs of the gradient and laplacian */
280 grad_rho_up = gradient(rho_up);
281 grad_rho_dn = gradient(rho_dn);
282
283 /* gradient in real space */
284 for (int x: {0, 1, 2}) {
285 grad_rho_up[x].fft_transform(1);
286 grad_rho_dn[x].fft_transform(1);
287 }
288
289 /* product of gradients */
290 grad_rho_up_grad_rho_up = dot(grad_rho_up, grad_rho_up);
291 grad_rho_up_grad_rho_dn = dot(grad_rho_up, grad_rho_dn);
292 grad_rho_dn_grad_rho_dn = dot(grad_rho_dn, grad_rho_dn);
293
294 if (env::print_hash()) {
295 auto h3 = grad_rho_up_grad_rho_up.hash_f_rg();
296 auto h4 = grad_rho_up_grad_rho_dn.hash_f_rg();
297 auto h5 = grad_rho_dn_grad_rho_dn.hash_f_rg();
298
299 print_hash("grad_rho_up_grad_rho_up", h3, ctx_.out());
300 print_hash("grad_rho_up_grad_rho_dn", h4, ctx_.out());
301 print_hash("grad_rho_dn_grad_rho_dn", h5, ctx_.out());
302 }
303 }
304
305 /* vsigma_uu: dϵ/dσ↑↑ */
307 /* vsigma_ud: dϵ/dσ↑↓ */
309 /* vsigma_dd: dϵ/dσ↓↓ */
311
312 if (is_gga) {
313 vsigma_uu = Smooth_periodic_function<double>(ctx_.spfft<double>(), ctx_.gvec_fft_sptr());
314 vsigma_ud = Smooth_periodic_function<double>(ctx_.spfft<double>(), ctx_.gvec_fft_sptr());
315 vsigma_dd = Smooth_periodic_function<double>(ctx_.spfft<double>(), ctx_.gvec_fft_sptr());
316 for (int i = 0; i < 3; i++) {
317 vsigma_[i]->zero();
318 }
319 }
320
321 sddk::mdarray<double, 1> exc(num_points, sddk::memory_t::host, "exc_tmp");
322 sddk::mdarray<double, 1> vxc_up(num_points, sddk::memory_t::host, "vxc_up_tmp");
323 sddk::mdarray<double, 1> vxc_dn(num_points, sddk::memory_t::host, "vxc_dn_dmp");
324
325 /* loop over XC functionals */
326 for (auto& ixc: xc_func_) {
327 PROFILE_START("sirius::Potential::xc_rg_magnetic|libxc");
328 if (ixc.is_vdw()) {
329#if defined(SIRIUS_USE_VDWXC)
330 /* all ranks should make a call because VdW uses FFT internaly */
331 if (num_points) {
332 ixc.get_vdw(&rho_up.value(0), &rho_dn.value(0), &grad_rho_up_grad_rho_up.value(0),
333 &grad_rho_dn_grad_rho_dn.value(0), vxc_up.at(sddk::memory_t::host), vxc_dn.at(sddk::memory_t::host),
334 &vsigma_uu.value(0), &vsigma_dd.value(0), exc.at(sddk::memory_t::host));
335 } else {
336 ixc.get_vdw(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
337 }
338#else
339 RTE_THROW("You should not be there since sirius is not compiled with libVDWXC\n");
340#endif
341 } else {
342 if (num_points) {
343 #pragma omp parallel
344 {
345 /* split local size between threads */
346 splindex_block<> spl_t(num_points, n_blocks(omp_get_num_threads()), block_id(omp_get_thread_num()));
347 /* if this is an LDA functional */
348 if (ixc.is_lda()) {
349 ixc.get_lda(spl_t.local_size(), &rho_up.value(spl_t.global_offset()),
350 &rho_dn.value(spl_t.global_offset()),
351 vxc_up.at(sddk::memory_t::host, spl_t.global_offset()),
352 vxc_dn.at(sddk::memory_t::host, spl_t.global_offset()),
353 exc.at(sddk::memory_t::host, spl_t.global_offset()));
354 }
355 /* if this is a GGA functional */
356 if (ixc.is_gga()) {
357 ixc.get_gga(spl_t.local_size(), &rho_up.value(spl_t.global_offset()),
358 &rho_dn.value(spl_t.global_offset()),
359 &grad_rho_up_grad_rho_up.value(spl_t.global_offset()),
360 &grad_rho_up_grad_rho_dn.value(spl_t.global_offset()),
361 &grad_rho_dn_grad_rho_dn.value(spl_t.global_offset()),
362 vxc_up.at(sddk::memory_t::host, spl_t.global_offset()),
363 vxc_dn.at(sddk::memory_t::host, spl_t.global_offset()),
364 &vsigma_uu.value(spl_t.global_offset()),
365 &vsigma_ud.value(spl_t.global_offset()),
366 &vsigma_dd.value(spl_t.global_offset()),
367 exc.at(sddk::memory_t::host, spl_t.global_offset()));
368 }
369 } // omp parallel region
370 } // num_points != 0
371 }
372 PROFILE_STOP("sirius::Potential::xc_rg_magnetic|libxc");
373 if (ixc.is_gga()) {
374 #pragma omp parallel for
375 for (int ir = 0; ir < num_points; ir++) {
376 /* save for future reuse in XC stress calculation */
377 vsigma_[0]->value(ir) += vsigma_uu.value(ir);
378 vsigma_[1]->value(ir) += vsigma_ud.value(ir);
379 vsigma_[2]->value(ir) += vsigma_dd.value(ir);
380 }
381
382 Smooth_periodic_vector_function<double> up_gradrho_vsigma(ctx_.spfft<double>(), ctx_.gvec_fft_sptr());
383 Smooth_periodic_vector_function<double> dn_gradrho_vsigma(ctx_.spfft<double>(), ctx_.gvec_fft_sptr());
384 for (int x: {0, 1, 2}) {
385 for(int ir = 0; ir < num_points; ir++) {
386 up_gradrho_vsigma[x].value(ir) = 2 * grad_rho_up[x].value(ir) * vsigma_uu.value(ir) + grad_rho_dn[x].value(ir) * vsigma_ud.value(ir);
387 dn_gradrho_vsigma[x].value(ir) = 2 * grad_rho_dn[x].value(ir) * vsigma_dd.value(ir) + grad_rho_up[x].value(ir) * vsigma_ud.value(ir);
388 }
389 /* transform to plane wave domain */
390 up_gradrho_vsigma[x].fft_transform(-1);
391 dn_gradrho_vsigma[x].fft_transform(-1);
392 }
393
394 auto div_up_gradrho_vsigma = divergence(up_gradrho_vsigma);
395 div_up_gradrho_vsigma.fft_transform(1);
396 auto div_dn_gradrho_vsigma = divergence(dn_gradrho_vsigma);
397 div_dn_gradrho_vsigma.fft_transform(1);
398
399 /* add remaining term to Vxc */
400 #pragma omp parallel for
401 for (int ir = 0; ir < num_points; ir++) {
402 vxc_up(ir) -= div_up_gradrho_vsigma.value(ir);
403 vxc_dn(ir) -= div_dn_gradrho_vsigma.value(ir);
404 }
405 }
406
407 #pragma omp parallel for
408 for (int irloc = 0; irloc < num_points; irloc++) {
409 /* add XC energy density */
410 xc_energy_density_->rg().value(irloc) += exc(irloc);
411 /* add XC potential */
412 xc_potential_->rg().value(irloc) += 0.5 * (vxc_up(irloc) + vxc_dn(irloc));
413
414 double bxc = 0.5 * (vxc_up(irloc) - vxc_dn(irloc));
415
416 /* get the sign between mag and B */
417 auto s = sign((rho_up.value(irloc) - rho_dn.value(irloc)) * bxc);
418
420 for (int j = 0; j < ctx_.num_mag_dims(); j++) {
421 m[j] = density__.mag(j).rg().value(irloc);
422 }
423 auto m_len = m.length();
424
425 if (m_len > 1e-8) {
426 for (int j = 0; j < ctx_.num_mag_dims(); j++) {
427 effective_magnetic_field(j).rg().value(irloc) += std::abs(bxc) * s * m[j] / m_len;
428 }
429 }
430 }
431 } // for loop over XC functionals
432}
433
434template <bool add_pseudo_core__>
435void Potential::xc(Density const& density__)
436{
437 PROFILE("sirius::Potential::xc");
438
439 /* zero all fields */
440 xc_potential_->zero();
441 xc_energy_density_->zero();
442 for (int i = 0; i < ctx_.num_mag_dims(); i++) {
443 effective_magnetic_field(i).zero();
444 }
445 /* quick return */
446 if (xc_func_.size() == 0) {
447 return;
448 }
449
450 if (ctx_.full_potential()) {
451 xc_mt(density__);
452 }
453
454 if (ctx_.num_spins() == 1) {
455 xc_rg_nonmagnetic<add_pseudo_core__>(density__);
456 } else {
457 xc_rg_magnetic<add_pseudo_core__>(density__);
458 }
459
460 if (env::print_hash()) {
461 auto h = xc_energy_density_->rg().hash_f_rg();
462 print_hash("Exc", h, ctx_.out());
463 }
464}
465
466// explicit instantiation
467template void Potential::xc_rg_nonmagnetic<true>(Density const&);
468template void Potential::xc_rg_nonmagnetic<false>(Density const&);
469template void Potential::xc_rg_magnetic<true>(Density const&);
470template void Potential::xc_rg_magnetic<false>(Density const&);
471template void Potential::xc<true>(Density const&);
472template void Potential::xc<false>(Density const&);
473
474} // namespace sirius
Generate charge density and magnetization from occupied spinor wave-functions.
Definition: density.hpp:214
auto const & rho() const
Return const reference to charge density (scalar functions).
Definition: density.hpp:416
void xc_rg_magnetic(Density const &density__)
Generate magnetic XC potential on the regular real-space grid.
Definition: xc.cpp:247
std::unique_ptr< Periodic_function< double > > xc_energy_density_
XC energy per unit particle.
Definition: potential.hpp:61
double add_delta_mag_xc_
Add extra charge to the density.
Definition: potential.hpp:143
void xc_mt(Density const &density__)
Generate XC potential in the muffin-tins.
Definition: xc_mt.cpp:314
void xc(Density const &rho__)
Generate XC potential and energy density.
Definition: xc.cpp:435
std::unique_ptr< Periodic_function< double > > xc_potential_
XC potential.
Definition: potential.hpp:58
std::array< std::unique_ptr< Smooth_periodic_function< double > >, 3 > vsigma_
Derivative .
Definition: potential.hpp:72
void xc_rg_nonmagnetic(Density const &density__)
Generate non-magnetic XC potential on the regular real-space grid.
Definition: xc.cpp:36
double add_delta_rho_xc_
Add extra charge to the density.
Definition: potential.hpp:139
auto gvec_fft_sptr() const
Return shared pointer to Gvec_fft object.
std::ostream & out() const
Return output stream.
mpi::Communicator const & comm() const
Total communicator of the simulation.
int num_spins() const
Number of spin components.
int num_mag_dims() const
Number of dimensions in the magnetization vector.
Representation of a smooth (Fourier-transformable) periodic function.
Vector of the smooth periodic functions.
MPI communicator wrapper.
void allreduce(T *buffer__, int count__) const
Perform the in-place (the output buffer is used as the input buffer) all-to-all reduction.
int rank() const
Rank of MPI process inside communicator.
double length() const
Return vector length (L2 norm).
Definition: r3.hpp:126
value_type local_size(block_id block_id__) const
Return local size of the split index for a given block.
Definition: splindex.hpp:302
Namespace of the SIRIUS library.
Definition: sirius.f90:5
Smooth_periodic_vector_function< T > gradient(Smooth_periodic_function< T > &f__)
Gradient of the function in the plane-wave domain.
strong_type< int, struct __block_id_tag > block_id
ID of the block.
Definition: splindex.hpp:108
Smooth_periodic_function< T > laplacian(Smooth_periodic_function< T > &f__)
Laplacian of the function in the plane-wave domain.
Smooth_periodic_function< T > divergence(Smooth_periodic_vector_function< T > &g__)
Divergence of the vecor function.
int sign(T val)
Sign of the variable.
Definition: math_tools.hpp:52
strong_type< int, struct __n_blocks_tag > n_blocks
Number of blocks to which the global index is split.
Definition: splindex.hpp:105
Add or substitute OMP functions.
Contains declaration and partial implementation of sirius::Potential class.
A time-based profiler.
Contains typedefs, enums and simple descriptors.
Contains implementation of sirius::XC_functional class.