votca 2026-dev
Loading...
Searching...
No Matches
vxc_potential.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2026 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20// Third party includes
21#include <algorithm>
22#include <boost/format.hpp>
23#include <cmath>
24#include <exception>
25#include <iomanip>
26#include <iostream>
27#include <stdexcept>
28#include <vector>
29
30// VOTCA includes
32
33// Local VOTCA includes
34#include "votca/xtp/aobasis.h"
37#include "votca/xtp/vxc_grid.h"
39
40namespace votca {
41namespace xtp {
42
43namespace {
44// Range-separated hybrids (HSE, CAM-B3LYP, ...) need erf-attenuated exact
45// exchange, which neither the DFT nor GW parts implement: the code would use
46// cam_alpha as a global exchange fraction and run a different functional
47// without notice. Refuse them.
48void RejectRangeSeparated(const xc_func_type& func, const std::string& name) {
49 if (func.cam_beta != 0.0 || func.cam_omega != 0.0) {
50 throw std::runtime_error(
51 "Functional " + name +
52 " is a range-separated hybrid; range-separated exact exchange is not "
53 "implemented in xtp (DFT and GW). Use a global hybrid or a semilocal "
54 "functional.");
55 }
56}
57} // namespace
58
59template <class Grid>
61 if (setXC_) {
62 xc_func_end(&xfunc);
63 xc_func_end(&xfunc_pol_);
64 if (use_separate_) {
65 xc_func_end(&cfunc);
66 xc_func_end(&cfunc_pol_);
67 }
68 }
69}
70
71template <class Grid>
72double Vxc_Potential<Grid>::getExactExchange(const std::string& functional) {
73 double exactexchange = 0.0;
75
76 std::vector<std::string> functional_names =
77 tools::Tokenizer(functional, " ").ToVector();
78
79 if (functional_names.size() > 2) {
80 throw std::runtime_error("Too many functional names");
81 } else if (functional_names.empty()) {
82 throw std::runtime_error("Specify at least one functional");
83 }
84
85 for (const std::string& functional_name : functional_names) {
86 int func_id = map.getID(functional_name);
87 if (func_id < 0) {
88 exactexchange = 0.0;
89 break;
90 }
91
92 xc_func_type func;
93 if (xc_func_init(&func, func_id, XC_UNPOLARIZED) != 0) {
94 throw std::runtime_error(
95 (boost::format("Functional %s not found\n") % functional_name).str());
96 }
97
98 try {
99 RejectRangeSeparated(func, functional_name);
100 } catch (...) {
101 xc_func_end(&func);
102 throw;
103 }
104
105 if (exactexchange > 0 && func.cam_alpha > 0) {
106 xc_func_end(&func);
107 throw std::runtime_error(
108 "You have specified two functionals with exact exchange");
109 }
110
111 exactexchange += func.cam_alpha;
112 xc_func_end(&func);
113 }
114
115 return exactexchange;
116}
117
118template <class Grid>
119void Vxc_Potential<Grid>::setXCfunctional(const std::string& functional) {
120 Vxc_Functionals map;
121 std::vector<std::string> strs =
122 tools::Tokenizer(functional, " ,\n\t").ToVector();
123
124 xfunc_id = 0;
125 use_separate_ = false;
126 cfunc_id = 0;
127
128 if (strs.size() == 1) {
129 xfunc_id = map.getID(strs[0]);
130 } else if (strs.size() == 2) {
131 xfunc_id = map.getID(strs[0]);
132 cfunc_id = map.getID(strs[1]);
133 use_separate_ = true;
134 } else {
135 throw std::runtime_error(
136 "LIBXC. Please specify one combined or an exchange and a correlation "
137 "functionals");
138 }
139
140 // Keep the stored handles UNPOLARIZED for the closed-shell/restricted code
141 // path. The UKS path creates temporary polarized handles inside
142 // EvaluateXCSpin().
143 if (xc_func_init(&xfunc, xfunc_id, XC_UNPOLARIZED) != 0) {
144 throw std::runtime_error(
145 (boost::format("Functional %s not found\n") % strs[0]).str());
146 }
147
148 try {
149 RejectRangeSeparated(xfunc, strs[0]);
150 } catch (...) {
151 xc_func_end(&xfunc);
152 throw;
153 }
154
155 if (xfunc.info->kind != 2 && !use_separate_) {
156 throw std::runtime_error(
157 "Your functional misses either correlation or exchange, please specify "
158 "another functional, separated by whitespace");
159 }
160
161 if (use_separate_) {
162 if (xc_func_init(&cfunc, cfunc_id, XC_UNPOLARIZED) != 0) {
163 xc_func_end(&xfunc);
164 throw std::runtime_error(
165 (boost::format("Functional %s not found\n") % strs[1]).str());
166 }
167
168 try {
169 RejectRangeSeparated(cfunc, strs[1]);
170 } catch (...) {
171 xc_func_end(&xfunc);
172 xc_func_end(&cfunc);
173 throw;
174 }
175
176 if ((xfunc.info->kind + cfunc.info->kind) != 1) {
177 xc_func_end(&xfunc);
178 xc_func_end(&cfunc);
179 throw std::runtime_error(
180 "Your functionals are not one exchange and one correlation");
181 }
182 }
183
184 // Polarized twins for the UKS path.
185 if (xc_func_init(&xfunc_pol_, xfunc_id, XC_POLARIZED) != 0) {
186 throw std::runtime_error(
187 "Failed to initialize polarized exchange XC "
188 "functional.");
189 }
190 if (use_separate_ && xc_func_init(&cfunc_pol_, cfunc_id, XC_POLARIZED) != 0) {
191 xc_func_end(&xfunc_pol_);
192 throw std::runtime_error(
193 "Failed to initialize polarized correlation XC "
194 "functional.");
195 }
196
197 auto is_gga = [](const xc_func_type& f) {
198 return f.info->family == XC_FAMILY_GGA ||
199 f.info->family == XC_FAMILY_HYB_GGA;
200 };
201 is_gga_ = is_gga(xfunc) || (use_separate_ && is_gga(cfunc));
202
203 setXC_ = true;
204 return;
205}
206
207namespace {
208// One libxc call for n points with any supported family.
209void CallLibxc(const xc_func_type& f, Index n, const double* rho,
210 const double* sigma, double* exc, double* vrho, double* vsigma) {
211 switch (f.info->family) {
212 case XC_FAMILY_LDA:
213 xc_lda_exc_vxc(&f, std::size_t(n), rho, exc, vrho);
214 break;
215 case XC_FAMILY_GGA:
216 case XC_FAMILY_HYB_GGA:
217 xc_gga_exc_vxc(&f, std::size_t(n), rho, sigma, exc, vrho, vsigma);
218 break;
219 default:
220 throw std::runtime_error("Unsupported XC functional family.");
221 }
222}
223} // namespace
224
225template <class Grid>
227 const double* sigma, double* exc,
228 double* vrho, double* vsigma) const {
229 std::fill(vsigma, vsigma + n, 0.0);
230 CallLibxc(xfunc, n, rho, sigma, exc, vrho, vsigma);
231 if (use_separate_) {
232 std::vector<double> e(n), vr(n), vs(n, 0.0);
233 CallLibxc(cfunc, n, rho, sigma, e.data(), vr.data(), vs.data());
234 for (Index i = 0; i < n; ++i) {
235 exc[i] += e[i];
236 vrho[i] += vr[i];
237 vsigma[i] += vs[i];
238 }
239 }
240}
241
242template <class Grid>
244 const double* sigma, double* exc,
245 double* vrho,
246 double* vsigma) const {
247 std::fill(vsigma, vsigma + 3 * n, 0.0);
248 CallLibxc(xfunc_pol_, n, rho, sigma, exc, vrho, vsigma);
249 if (use_separate_) {
250 std::vector<double> e(n), vr(2 * n), vs(3 * n, 0.0);
251 CallLibxc(cfunc_pol_, n, rho, sigma, e.data(), vr.data(), vs.data());
252 for (Index i = 0; i < n; ++i) {
253 exc[i] += e[i];
254 }
255 for (Index i = 0; i < 2 * n; ++i) {
256 vrho[i] += vr[i];
257 }
258 for (Index i = 0; i < 3 * n; ++i) {
259 vsigma[i] += vs[i];
260 }
261 }
262}
263
264template <class Grid>
266 double rho, double sigma) const {
267 typename Vxc_Potential<Grid>::XC_entry result;
268 EvaluateXCBatch(1, &rho, &sigma, &result.f_xc, &result.df_drho,
269 &result.df_dsigma);
270 return result;
271}
272
273template <class Grid>
275 double rho_a, double rho_b, double sigma_aa, double sigma_ab,
276 double sigma_bb) const {
278 const double rho[2] = {rho_a, rho_b};
279 const double sigma[3] = {sigma_aa, sigma_ab, sigma_bb};
280 double vrho[2];
281 double vsigma[3];
282 EvaluateXCSpinBatch(1, rho, sigma, &result.f_xc, vrho, vsigma);
283 result.vrho_a = vrho[0];
284 result.vrho_b = vrho[1];
285 result.vsigma_aa = vsigma[0];
286 result.vsigma_ab = vsigma[1];
287 result.vsigma_bb = vsigma[2];
288 return result;
289}
290
291// Points are processed in blocks inside each box. With Phi the box's AO
292// values (functions x points) and dPhi their gradients, the work that scales
293// with the square of the box's basis size is two matrix products per block:
294// Y = D Phi (density and its gradient at every point)
295// Vxc += A Phi^T, A_p = w_p (vrho_p/2 phi_p + 2 vsigma_p dPhi_p grad rho_p)
296// and libxc is called once per block. Per point, only O(functions) work
297// remains. Same formulas as the former point-by-point loop.
298template <class Grid>
300 const Eigen::MatrixXd& density_matrix) const {
301 assert(density_matrix.isApprox(density_matrix.transpose()) &&
302 "Density matrix has to be symmetric!");
303
304 Mat_p_Energy vxc = Mat_p_Energy(density_matrix.rows(), density_matrix.cols());
305
306#pragma omp parallel for schedule(guided) reduction(+ : vxc)
307 for (Index i = 0; i < grid_.getBoxesSize(); ++i) {
308 const GridBox& box = grid_[i];
309 if (!box.Matrixsize()) {
310 continue;
311 }
312
313 // two because we have to use the density matrix and its transpose
314 const Eigen::MatrixXd DMAT_here = 2 * box.ReadFromBigMatrix(density_matrix);
315
316 double cutoff =
317 1.e-40 / double(density_matrix.rows()) / double(density_matrix.rows());
318 if (DMAT_here.cwiseAbs2().maxCoeff() < cutoff) {
319 continue;
320 }
321
322 const Index nf = box.Matrixsize();
323 const Index block = std::min(kPointBlock, box.size());
324 Eigen::MatrixXd Vxc_here = Eigen::MatrixXd::Zero(nf, nf);
325 Eigen::MatrixXd phi(nf, block);
326 Eigen::MatrixXd dphi(nf, 3 * block);
327 Eigen::MatrixXd y(nf, block);
328 Eigen::MatrixXd a(nf, block);
329 std::vector<Index> kept;
330 std::vector<double> rho, sigma, exc, vrho, vsigma;
331 std::vector<Eigen::Vector3d> grad;
332 double EXC_box = 0.0;
333
334 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
335 const std::vector<double>& weights = box.getGridWeights();
336
337 for (Index start = 0; start < box.size(); start += block) {
338 const Index nb = std::min(block, box.size() - start);
339 for (Index p = 0; p < nb; ++p) {
340 box.CalcAOValues(points[start + p], phi, dphi, p);
341 }
342 y.leftCols(nb).noalias() = DMAT_here * phi.leftCols(nb);
343
344 kept.clear();
345 rho.clear();
346 sigma.clear();
347 grad.clear();
348 for (Index p = 0; p < nb; ++p) {
349 const double r = 0.5 * phi.col(p).dot(y.col(p));
350 if (r * weights[start + p] < 1.e-20) {
351 continue;
352 }
353 const Eigen::Vector3d g =
354 dphi.middleCols(3 * p, 3).transpose() * y.col(p);
355 kept.push_back(p);
356 rho.push_back(r);
357 sigma.push_back(g.squaredNorm());
358 grad.push_back(g);
359 }
360 const Index nk = Index(kept.size());
361 if (nk == 0) {
362 continue;
363 }
364 exc.resize(nk);
365 vrho.resize(nk);
366 vsigma.resize(nk);
367 EvaluateXCBatch(nk, rho.data(), sigma.data(), exc.data(), vrho.data(),
368 vsigma.data());
369
370 // Kept points are compacted to the front: column k receives point
371 // kept[k] >= k, which has been read by then.
372 for (Index k = 0; k < nk; ++k) {
373 const Index p = kept[k];
374 const double w = weights[start + p];
375 EXC_box += w * rho[k] * exc[k];
376 a.col(k) = (w * 0.5 * vrho[k]) * phi.col(p);
377 if (is_gga_) {
378 a.col(k).noalias() +=
379 (w * 2.0 * vsigma[k]) * (dphi.middleCols(3 * p, 3) * grad[k]);
380 }
381 if (k != p) {
382 phi.col(k) = phi.col(p);
383 }
384 }
385 Vxc_here.noalias() += a.leftCols(nk) * phi.leftCols(nk).transpose();
386 }
387
388 box.AddtoBigMatrix(vxc.matrix(), Vxc_here);
389 vxc.energy() += EXC_box;
390 }
391
392 return Mat_p_Energy(vxc.energy(), vxc.matrix() + vxc.matrix().transpose());
393}
394
395namespace {
396// first and second derivatives of one functional (unpolarized)
397void CallLibxcFXC(const xc_func_type& f, Index n, const double* rho,
398 const double* sigma, double* vrho, double* vsigma,
399 double* v2rho2, double* v2rhosigma, double* v2sigma2) {
400 if (!(f.info->flags & XC_FLAGS_HAVE_FXC)) {
401 throw std::runtime_error(std::string("Functional ") + f.info->name +
402 " has no second derivatives in libxc");
403 }
404 switch (f.info->family) {
405 case XC_FAMILY_LDA:
406 xc_lda_vxc_fxc(&f, std::size_t(n), rho, vrho, v2rho2);
407 break;
408 case XC_FAMILY_GGA:
409 case XC_FAMILY_HYB_GGA:
410 xc_gga_vxc_fxc(&f, std::size_t(n), rho, sigma, vrho, vsigma, v2rho2,
411 v2rhosigma, v2sigma2);
412 break;
413 default:
414 throw std::runtime_error("Unsupported XC functional family.");
415 }
416}
417} // namespace
418
419template <class Grid>
421 const double* sigma, double* vsigma,
422 double* v2rho2, double* v2rhosigma,
423 double* v2sigma2) const {
424 std::vector<double> vrho(n);
425 std::fill(vsigma, vsigma + n, 0.0);
426 std::fill(v2rhosigma, v2rhosigma + n, 0.0);
427 std::fill(v2sigma2, v2sigma2 + n, 0.0);
428 CallLibxcFXC(xfunc, n, rho, sigma, vrho.data(), vsigma, v2rho2, v2rhosigma,
429 v2sigma2);
430 if (use_separate_) {
431 std::vector<double> vr(n), vs(n, 0.0), rr(n), rs(n, 0.0), ss(n, 0.0);
432 CallLibxcFXC(cfunc, n, rho, sigma, vr.data(), vs.data(), rr.data(),
433 rs.data(), ss.data());
434 for (Index i = 0; i < n; ++i) {
435 vsigma[i] += vs[i];
436 v2rho2[i] += rr[i];
437 v2rhosigma[i] += rs[i];
438 v2sigma2[i] += ss[i];
439 }
440 }
441}
442
443template <class Grid>
445 const double* sigma,
446 double* vsigma, double* v2rho2,
447 double* v2rhosigma,
448 double* v2sigma2) const {
449 std::vector<double> vrho(2 * n);
450 std::fill(vsigma, vsigma + 3 * n, 0.0);
451 std::fill(v2rhosigma, v2rhosigma + 6 * n, 0.0);
452 std::fill(v2sigma2, v2sigma2 + 6 * n, 0.0);
453 CallLibxcFXC(xfunc_pol_, n, rho, sigma, vrho.data(), vsigma, v2rho2,
454 v2rhosigma, v2sigma2);
455 if (use_separate_) {
456 std::vector<double> vr(2 * n), vs(3 * n, 0.0), rr(3 * n), rs(6 * n, 0.0),
457 ss(6 * n, 0.0);
458 CallLibxcFXC(cfunc_pol_, n, rho, sigma, vr.data(), vs.data(), rr.data(),
459 rs.data(), ss.data());
460 for (Index i = 0; i < 3 * n; ++i) {
461 vsigma[i] += vs[i];
462 v2rho2[i] += rr[i];
463 }
464 for (Index i = 0; i < 6 * n; ++i) {
465 v2rhosigma[i] += rs[i];
466 v2sigma2[i] += ss[i];
467 }
468 }
469}
470
471// First-order change of IntegrateVXCSpin. Per point, with g_s = grad rho_s,
472// sigma = (g_a.g_a, g_a.g_b, g_b.g_b) and the first-order changes drho_s,
473// dg_s = grad drho_s:
474// dsigma = (2 g_a.dg_a, g_a.dg_b + dg_a.g_b, 2 g_b.dg_b)
475// dv_rho_s = sum_t v2rho2_st drho_t + sum_c v2rhosigma_sc dsigma_c
476// dv_sigma_c = sum_t v2rhosigma_tc drho_t + sum_d v2sigma2_cd dsigma_d
477// dV_a = int dv_rho_a chi chi + (2 dv_aa g_a + 2 v_aa dg_a + dv_ab g_b
478// + v_ab dg_b).grad(chi chi)
479// and dV_b likewise (a <-> b, aa <-> bb); assembled as in IntegrateVXCSpin.
480template <class Grid>
481std::vector<std::array<Eigen::MatrixXd, 2>>
483 const Eigen::MatrixXd& dmat_alpha, const Eigen::MatrixXd& dmat_beta,
484 const std::vector<std::array<Eigen::MatrixXd, 2>>& response) const {
485 const Index n_ao = dmat_alpha.rows();
486 const Index n_resp = Index(response.size());
487 std::vector<std::array<Eigen::MatrixXd, 2>> result(
488 static_cast<std::size_t>(n_resp),
489 {Eigen::MatrixXd::Zero(n_ao, n_ao), Eigen::MatrixXd::Zero(n_ao, n_ao)});
490 if (n_resp == 0) {
491 return result;
492 }
493 auto wrong_size = [n_ao](const std::array<Eigen::MatrixXd, 2>& r) {
494 return r[0].rows() != n_ao || r[0].cols() != n_ao || r[1].rows() != n_ao ||
495 r[1].cols() != n_ao;
496 };
497 if (std::any_of(response.begin(), response.end(), wrong_size)) {
498 throw std::runtime_error(
499 "IntegrateFXCSpin: response matrix does not match the density "
500 "matrix");
501 }
502 // fail before the grid loop for functionals without fxc
503 {
504 const double rho[2] = {0.1, 0.05};
505 const double sigma[3] = {0.01, 0.005, 0.004};
506 double vs[3], rr[3], rs[6], ss[6];
507 EvaluateFXCSpinBatch(1, rho, sigma, vs, rr, rs, ss);
508 }
509
510#pragma omp parallel for schedule(guided)
511 for (Index i = 0; i < grid_.getBoxesSize(); ++i) {
512 const GridBox& box = grid_[i];
513 if (!box.Matrixsize()) {
514 continue;
515 }
516 const Eigen::MatrixXd DMa = box.ReadFromBigMatrix(dmat_alpha);
517 const Eigen::MatrixXd DMb = box.ReadFromBigMatrix(dmat_beta);
518 std::vector<Eigen::MatrixXd> dDa(static_cast<std::size_t>(n_resp));
519 std::vector<Eigen::MatrixXd> dDb(static_cast<std::size_t>(n_resp));
520 for (Index k = 0; k < n_resp; ++k) {
521 dDa[std::size_t(k)] = box.ReadFromBigMatrix(response[std::size_t(k)][0]);
522 dDb[std::size_t(k)] = box.ReadFromBigMatrix(response[std::size_t(k)][1]);
523 }
524 const Index nf = box.Matrixsize();
525 const Index block = std::min(kPointBlock, box.size());
526 std::vector<Eigen::MatrixXd> fxc_a(static_cast<std::size_t>(n_resp),
527 Eigen::MatrixXd::Zero(nf, nf));
528 std::vector<Eigen::MatrixXd> fxc_b(static_cast<std::size_t>(n_resp),
529 Eigen::MatrixXd::Zero(nf, nf));
530 Eigen::MatrixXd phi(nf, block);
531 Eigen::MatrixXd dphi(nf, 3 * block);
532 Eigen::MatrixXd ya(nf, block);
533 Eigen::MatrixXd yb(nf, block);
534 Eigen::MatrixXd yka(nf, block);
535 Eigen::MatrixXd ykb(nf, block);
536 Eigen::MatrixXd aa(nf, block);
537 Eigen::MatrixXd ab(nf, block);
538 std::vector<Index> kept;
539 std::vector<double> rho, sigma, vsigma, v2rho2, v2rhosigma, v2sigma2;
540 std::vector<Eigen::Vector3d> grad_a, grad_b;
541 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
542 const std::vector<double>& weights = box.getGridWeights();
543
544 for (Index start = 0; start < box.size(); start += block) {
545 const Index nb = std::min(block, box.size() - start);
546 for (Index p = 0; p < nb; ++p) {
547 box.CalcAOValues(points[start + p], phi, dphi, p);
548 }
549 ya.leftCols(nb).noalias() = DMa * phi.leftCols(nb);
550 yb.leftCols(nb).noalias() = DMb * phi.leftCols(nb);
551 kept.clear();
552 rho.clear();
553 sigma.clear();
554 grad_a.clear();
555 grad_b.clear();
556 for (Index p = 0; p < nb; ++p) {
557 const double rho_a = phi.col(p).dot(ya.col(p));
558 const double rho_b = phi.col(p).dot(yb.col(p));
559 if ((rho_a + rho_b) * weights[start + p] < 1.e-20) {
560 continue;
561 }
562 const Eigen::Vector3d ga =
563 2.0 * (dphi.middleCols(3 * p, 3).transpose() * ya.col(p));
564 const Eigen::Vector3d gb =
565 2.0 * (dphi.middleCols(3 * p, 3).transpose() * yb.col(p));
566 kept.push_back(p);
567 rho.push_back(rho_a);
568 rho.push_back(rho_b);
569 sigma.push_back(ga.dot(ga));
570 sigma.push_back(ga.dot(gb));
571 sigma.push_back(gb.dot(gb));
572 grad_a.push_back(ga);
573 grad_b.push_back(gb);
574 }
575 const Index nk = Index(kept.size());
576 if (nk == 0) {
577 continue;
578 }
579 vsigma.resize(3 * nk);
580 v2rho2.resize(3 * nk);
581 v2rhosigma.resize(6 * nk);
582 v2sigma2.resize(6 * nk);
583 EvaluateFXCSpinBatch(nk, rho.data(), sigma.data(), vsigma.data(),
584 v2rho2.data(), v2rhosigma.data(), v2sigma2.data());
585 Eigen::MatrixXd phik(nf, nk);
586 for (Index q = 0; q < nk; ++q) {
587 phik.col(q) = phi.col(kept[q]);
588 }
589
590 for (Index k = 0; k < n_resp; ++k) {
591 yka.leftCols(nb).noalias() = dDa[std::size_t(k)] * phi.leftCols(nb);
592 ykb.leftCols(nb).noalias() = dDb[std::size_t(k)] * phi.leftCols(nb);
593 for (Index q = 0; q < nk; ++q) {
594 const Index p = kept[q];
595 const double w = weights[start + p];
596 const double dra = phi.col(p).dot(yka.col(p));
597 const double drb = phi.col(p).dot(ykb.col(p));
598 const double* r2 = &v2rho2[3 * q];
599 if (!is_gga_) {
600 const double dva = r2[0] * dra + r2[1] * drb;
601 const double dvb = r2[1] * dra + r2[2] * drb;
602 aa.col(q) = (w * 0.5 * dva) * phi.col(p);
603 ab.col(q) = (w * 0.5 * dvb) * phi.col(p);
604 continue;
605 }
606 const auto d = dphi.middleCols(3 * p, 3);
607 const Eigen::Vector3d dga = 2.0 * (d.transpose() * yka.col(p));
608 const Eigen::Vector3d dgb = 2.0 * (d.transpose() * ykb.col(p));
609 const Eigen::Vector3d& ga = grad_a[q];
610 const Eigen::Vector3d& gb = grad_b[q];
611 const double ds[3] = {2.0 * ga.dot(dga), ga.dot(dgb) + dga.dot(gb),
612 2.0 * gb.dot(dgb)};
613 const double* rs = &v2rhosigma[6 * q];
614 const double* s2 = &v2sigma2[6 * q];
615 const double* vs = &vsigma[3 * q];
616 // v2sigma2 as a symmetric 3x3 matrix
617 const double S[3][3] = {{s2[0], s2[1], s2[2]},
618 {s2[1], s2[3], s2[4]},
619 {s2[2], s2[4], s2[5]}};
620 const double dva = r2[0] * dra + r2[1] * drb + rs[0] * ds[0] +
621 rs[1] * ds[1] + rs[2] * ds[2];
622 const double dvb = r2[1] * dra + r2[2] * drb + rs[3] * ds[0] +
623 rs[4] * ds[1] + rs[5] * ds[2];
624 double dvs[3];
625 for (int c = 0; c < 3; ++c) {
626 dvs[c] = rs[c] * dra + rs[3 + c] * drb + S[c][0] * ds[0] +
627 S[c][1] * ds[1] + S[c][2] * ds[2];
628 }
629 const Eigen::Vector3d vec_a =
630 2.0 * dvs[0] * ga + 2.0 * vs[0] * dga + dvs[1] * gb + vs[1] * dgb;
631 const Eigen::Vector3d vec_b =
632 2.0 * dvs[2] * gb + 2.0 * vs[2] * dgb + dvs[1] * ga + vs[1] * dga;
633 aa.col(q) = (w * 0.5 * dva) * phi.col(p) + w * (d * vec_a);
634 ab.col(q) = (w * 0.5 * dvb) * phi.col(p) + w * (d * vec_b);
635 }
636 fxc_a[std::size_t(k)].noalias() += aa.leftCols(nk) * phik.transpose();
637 fxc_b[std::size_t(k)].noalias() += ab.leftCols(nk) * phik.transpose();
638 }
639 }
640#pragma omp critical
641 {
642 for (Index k = 0; k < n_resp; ++k) {
643 box.AddtoBigMatrix(result[std::size_t(k)][0], fxc_a[std::size_t(k)]);
644 box.AddtoBigMatrix(result[std::size_t(k)][1], fxc_b[std::size_t(k)]);
645 }
646 }
647 }
648 for (std::array<Eigen::MatrixXd, 2>& m : result) {
649 m[0] = (m[0] + m[0].transpose()).eval();
650 m[1] = (m[1] + m[1].transpose()).eval();
651 }
652 return result;
653}
654
655// First-order change of IntegrateVXC. With sigma = |grad rho|^2, at a point
656// drho, dg = grad drho, dsigma = 2 g.dg
657// dv_rho = v2rho2 drho + v2rhosigma dsigma
658// dv_sigma = v2rhosigma drho + v2sigma2 dsigma
659// dV = int dv_rho chi chi + 2 (dv_sigma g + vsigma dg).grad(chi chi)
660// assembled like IntegrateVXC: a = w (dv_rho/2 phi + 2 dphi.(...)),
661// dV = a phi^T + transpose. The points IntegrateVXC skips are skipped.
662template <class Grid>
663std::vector<Eigen::MatrixXd> Vxc_Potential<Grid>::IntegrateFXC(
664 const Eigen::MatrixXd& density_matrix,
665 const std::vector<Eigen::MatrixXd>& response_matrices) const {
666 const Index n_ao = density_matrix.rows();
667 const Index n_resp = Index(response_matrices.size());
668 std::vector<Eigen::MatrixXd> result(static_cast<std::size_t>(n_resp),
669 Eigen::MatrixXd::Zero(n_ao, n_ao));
670 if (n_resp == 0) {
671 return result;
672 }
673 if (std::any_of(response_matrices.begin(), response_matrices.end(),
674 [n_ao](const Eigen::MatrixXd& m) {
675 return m.rows() != n_ao || m.cols() != n_ao;
676 })) {
677 throw std::runtime_error(
678 "IntegrateFXC: response matrix does not match the density matrix");
679 }
680 // fail before the grid loop for functionals without fxc
681 {
682 const double rho = 0.1;
683 const double sigma = 0.01;
684 double vs, rr, rs, ss;
685 EvaluateFXCBatch(1, &rho, &sigma, &vs, &rr, &rs, &ss);
686 }
687
688#pragma omp parallel for schedule(guided)
689 for (Index i = 0; i < grid_.getBoxesSize(); ++i) {
690 const GridBox& box = grid_[i];
691 if (!box.Matrixsize()) {
692 continue;
693 }
694 const Eigen::MatrixXd DMAT_here = 2 * box.ReadFromBigMatrix(density_matrix);
695 std::vector<Eigen::MatrixXd> dmat_k(static_cast<std::size_t>(n_resp));
696 for (Index k = 0; k < n_resp; ++k) {
697 dmat_k[std::size_t(k)] =
698 2 * box.ReadFromBigMatrix(response_matrices[std::size_t(k)]);
699 }
700 const Index nf = box.Matrixsize();
701 const Index block = std::min(kPointBlock, box.size());
702 std::vector<Eigen::MatrixXd> fxc_here(static_cast<std::size_t>(n_resp),
703 Eigen::MatrixXd::Zero(nf, nf));
704 Eigen::MatrixXd phi(nf, block);
705 Eigen::MatrixXd dphi(nf, 3 * block);
706 Eigen::MatrixXd y(nf, block);
707 Eigen::MatrixXd yk(nf, block);
708 Eigen::MatrixXd a(nf, block);
709 std::vector<Index> kept;
710 std::vector<double> rho, sigma, vsigma, v2rho2, v2rhosigma, v2sigma2;
711 std::vector<Eigen::Vector3d> grad;
712 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
713 const std::vector<double>& weights = box.getGridWeights();
714
715 for (Index start = 0; start < box.size(); start += block) {
716 const Index nb = std::min(block, box.size() - start);
717 for (Index p = 0; p < nb; ++p) {
718 box.CalcAOValues(points[start + p], phi, dphi, p);
719 }
720 y.leftCols(nb).noalias() = DMAT_here * phi.leftCols(nb);
721 kept.clear();
722 rho.clear();
723 sigma.clear();
724 grad.clear();
725 for (Index p = 0; p < nb; ++p) {
726 const double r = 0.5 * phi.col(p).dot(y.col(p));
727 if (r * weights[start + p] < 1.e-20) {
728 continue;
729 }
730 const Eigen::Vector3d g =
731 dphi.middleCols(3 * p, 3).transpose() * y.col(p);
732 kept.push_back(p);
733 rho.push_back(r);
734 sigma.push_back(g.squaredNorm());
735 grad.push_back(g);
736 }
737 const Index nk = Index(kept.size());
738 if (nk == 0) {
739 continue;
740 }
741 vsigma.resize(nk);
742 v2rho2.resize(nk);
743 v2rhosigma.resize(nk);
744 v2sigma2.resize(nk);
745 EvaluateFXCBatch(nk, rho.data(), sigma.data(), vsigma.data(),
746 v2rho2.data(), v2rhosigma.data(), v2sigma2.data());
747 Eigen::MatrixXd phik(nf, nk);
748 for (Index q = 0; q < nk; ++q) {
749 phik.col(q) = phi.col(kept[q]);
750 }
751
752 for (Index k = 0; k < n_resp; ++k) {
753 yk.leftCols(nb).noalias() = dmat_k[std::size_t(k)] * phi.leftCols(nb);
754 for (Index q = 0; q < nk; ++q) {
755 const Index p = kept[q];
756 const double w = weights[start + p];
757 const double drho = 0.5 * phi.col(p).dot(yk.col(p));
758 if (is_gga_) {
759 const Eigen::Vector3d dg =
760 dphi.middleCols(3 * p, 3).transpose() * yk.col(p);
761 const double dsigma = 2.0 * grad[q].dot(dg);
762 const double dv_rho = v2rho2[q] * drho + v2rhosigma[q] * dsigma;
763 const double dv_sigma = v2rhosigma[q] * drho + v2sigma2[q] * dsigma;
764 const Eigen::Vector3d vec =
765 2.0 * (dv_sigma * grad[q] + vsigma[q] * dg);
766 a.col(q) = (w * 0.5 * dv_rho) * phi.col(p) +
767 w * (dphi.middleCols(3 * p, 3) * vec);
768 } else {
769 a.col(q) = (w * 0.5 * v2rho2[q] * drho) * phi.col(p);
770 }
771 }
772 fxc_here[std::size_t(k)].noalias() += a.leftCols(nk) * phik.transpose();
773 }
774 }
775#pragma omp critical
776 {
777 for (Index k = 0; k < n_resp; ++k) {
778 box.AddtoBigMatrix(result[std::size_t(k)], fxc_here[std::size_t(k)]);
779 }
780 }
781 }
782 for (Eigen::MatrixXd& m : result) {
783 m = (m + m.transpose()).eval();
784 }
785 return result;
786}
787
788// ===========================================================================
789// XC gradient at fixed grid weights: basis-function (Pulay) and grid-point
790// translation terms, LDA and GGA. The grid-weight (SSW partition) term is
791// in GridWeightGradient.
792//
793// E_xc = sum_p weight_p * e_xc(rho_p, sigma_p) (e_xc = rho*f_xc)
794//
795// dE_xc/dR_A = sum_p weight_p [v_rho d(rho_p)/dR_A + v_sigma d(sigma_p)/dR_A]
796//
797// with v_rho = xc.df_drho, v_sigma = xc.df_dsigma (LibXC vrho/vsigma, the
798// full derivatives of e_xc).
799//
800// Basis term: for chi_mu(r) = phi_mu(r - R_A), d(chi_mu)/dR_A =
801// -grad_r chi_mu for mu on A and zero otherwise, so with
802// temp = ao.values^T * DMAT_here (DMAT_here = 2P)
803// d(rho_p)/dR_A|_basis = -sum_{mu in A} temp(mu) (grad_r chi_mu)(r_p).
804// ===========================================================================
805template <class Grid>
807 const Eigen::MatrixXd& density_matrix, const AOBasis& dftbasis) const {
808 assert(density_matrix.isApprox(density_matrix.transpose()) &&
809 "Density matrix has to be symmetric!");
810
811 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
812 Index nthreads = OPENMP::getMaxThreads();
813 std::vector<Eigen::MatrixXd> grad_thread(nthreads,
814 Eigen::MatrixXd::Zero(natoms, 3));
815
816 std::exception_ptr eptr_pulay = nullptr;
817#pragma omp parallel for schedule(guided)
818 for (Index i = 0; i < grid_.getBoxesSize(); ++i) {
819 try {
820 Index thread_id = OPENMP::getThreadId();
821 const GridBox& box = grid_[i];
822 if (!box.Matrixsize()) {
823 continue;
824 }
825
826 const Eigen::MatrixXd DMAT_here =
827 2 * box.ReadFromBigMatrix(density_matrix);
828
829 double cutoff = 1.e-40 / double(density_matrix.rows()) /
830 double(density_matrix.rows());
831 if (DMAT_here.cwiseAbs2().maxCoeff() < cutoff) {
832 continue;
833 }
834
835 // box-local AO index -> atom index
836 std::vector<Index> local_idx_to_atom(box.Matrixsize());
837 const std::vector<const AOShell*>& shells = box.getShells();
838 const std::vector<GridboxRange>& ao_ranges = box.getAOranges();
839 for (size_t s = 0; s < shells.size(); ++s) {
840 Index atom = shells[s]->getAtomIndex();
841 for (Index k = 0; k < ao_ranges[s].size; ++k) {
842 local_idx_to_atom[ao_ranges[s].start + k] = atom;
843 }
844 }
845
846 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
847 const std::vector<double>& weights = box.getGridWeights();
848 // atom each grid point moves with, for the translation terms
849 const std::vector<Index>& owner_atoms = box.getOwnerAtoms();
850
851 for (Index p = 0; p < box.size(); ++p) {
853
854 Eigen::VectorXd temp = ao.values.transpose() * DMAT_here;
855 double rho = 0.5 * temp.dot(ao.values);
856 const double weight = weights[p];
857
858 if (rho * weight < 1.e-20) {
859 continue;
860 }
861
862 const Eigen::Vector3d rho_grad = temp.transpose() * ao.derivatives;
864 EvaluateXC(rho, rho_grad.squaredNorm());
865
866 // ===========================================================
867 // GGA sigma terms (need the AO Hessians, EvalAOspaceHessian).
868 // They vanish for LDA, where xc.df_dsigma stays 0.
869 //
870 // sigma_p = |grad_r(rho_p)|^2 = rho_grad . rho_grad
871 //
872 // Gmat(mu,l) = sum_nu DMAT_mu,nu grad_nu,l (DMAT_here*ao.derivatives)
873 // s_vec(mu) = sum_l Gmat(mu,l) rho_grad_l (Gmat*rho_grad)
874 //
875 // Basis term (mu in A):
876 // dsigma_p/dR_A|_basis = -2 sum_{mu in A} [grad_mu s_vec(mu)
877 // + temp_mu (Hessian_mu . rho_grad)]
878 //
879 // Translation term (A == owner(p) only), with the density Hessian:
880 // Hessian_rho = sum_mu temp_mu*Hessian_mu
881 // + symmetrized(ao.derivatives^T * Gmat)
882 // dsigma_p/dR_owner|_translation = 2 * (Hessian_rho * rho_grad)
883 //
884 // Both are contracted with weight_p * v_sigma.
885 Eigen::MatrixX3d Gmat = DMAT_here * ao.derivatives; // (Matrixsize x 3)
886 Eigen::VectorXd s_vec = Gmat * rho_grad; // (Matrixsize)
887
888 Eigen::Matrix3d Hessian_rho = Eigen::Matrix3d::Zero();
889 for (Index mu = 0; mu < box.Matrixsize(); ++mu) {
890 Hessian_rho += temp(mu) * ao.hessians[mu];
891 }
892 Eigen::Matrix3d M = ao.derivatives.transpose() * Gmat;
893 Hessian_rho += 0.5 * (M + M.transpose());
894
895 Index owner_of_point_sigma = owner_atoms[p];
896 Eigen::Vector3d dsigma_translation = 2.0 * (Hessian_rho * rho_grad);
897 grad_thread[thread_id].row(owner_of_point_sigma) +=
898 (weight * xc.df_dsigma * dsigma_translation).transpose();
899 // ===========================================================
900
901 // ===========================================================
902 // Grid-point translation term. Points move rigidly with their
903 // owner atom, r_p = R_owner + const, so
904 // d(rho_p)/dR_A|_translation = grad_r(rho_p) if A == owner(p),
905 // 0 otherwise,
906 // dE_xc/dR_A|_translation = sum_{p: owner(p)==A}
907 // weight_p v_rho(rho_p) grad_r(rho_p).
908 Index owner_of_point = owner_atoms[p];
909 grad_thread[thread_id].row(owner_of_point) +=
910 (weight * xc.df_drho * rho_grad).transpose();
911 // ===========================================================
912
913 // Basis terms, per local AO index mu, added to the atom of mu (the
914 // shells of a box are not necessarily grouped by atom).
915 for (Index mu = 0; mu < box.Matrixsize(); ++mu) {
916 Index atom = local_idx_to_atom[mu];
917 Eigen::Vector3d contribution = -weight * xc.df_drho * temp(mu) *
918 ao.derivatives.row(mu).transpose();
919 // GGA sigma basis term, see above
920 Eigen::Vector3d dsigma_basis_mu =
921 -2.0 * (ao.derivatives.row(mu).transpose() * s_vec(mu) +
922 temp(mu) * (ao.hessians[mu] * rho_grad));
923 contribution += weight * xc.df_dsigma * dsigma_basis_mu;
924 grad_thread[thread_id].row(atom) += contribution.transpose();
925 }
926 }
927 } catch (...) {
928#pragma omp critical
929 {
930 if (!eptr_pulay) {
931 eptr_pulay = std::current_exception();
932 }
933 }
934 }
935 }
936 if (eptr_pulay) {
937 std::rethrow_exception(eptr_pulay);
938 }
939
940 Eigen::MatrixXd grad = Eigen::MatrixXd::Zero(natoms, 3);
941 for (Index t = 0; t < nthreads; ++t) {
942 grad += grad_thread[t];
943 }
944 return grad;
945}
946
947// Blocked like IntegrateVXC, per spin channel. UKS density matrices are not
948// pre-doubled: rho_s = phi^T D_s phi, grad rho_s = 2 dPhi^T D_s phi.
949template <class Grid>
951 const Eigen::MatrixXd& dmat_alpha, const Eigen::MatrixXd& dmat_beta) const {
952 assert(dmat_alpha.isApprox(dmat_alpha.transpose()) &&
953 "Alpha density matrix has to be symmetric!");
954 assert(dmat_beta.isApprox(dmat_beta.transpose()) &&
955 "Beta density matrix has to be symmetric!");
956
957 typename Vxc_Potential<Grid>::SpinResult result;
958 result.vxc_alpha =
959 Eigen::MatrixXd::Zero(dmat_alpha.rows(), dmat_alpha.cols());
960 result.vxc_beta = Eigen::MatrixXd::Zero(dmat_beta.rows(), dmat_beta.cols());
961
962#pragma omp parallel
963 {
964 Eigen::MatrixXd vxc_alpha_private =
965 Eigen::MatrixXd::Zero(dmat_alpha.rows(), dmat_alpha.cols());
966 Eigen::MatrixXd vxc_beta_private =
967 Eigen::MatrixXd::Zero(dmat_beta.rows(), dmat_beta.cols());
968 double exc_private = 0.0;
969
970 std::vector<Index> kept;
971 std::vector<double> rho, sigma, exc, vrho, vsigma;
972 std::vector<Eigen::Vector3d> grad_a, grad_b;
973
974#pragma omp for schedule(guided)
975 for (Index i = 0; i < grid_.getBoxesSize(); ++i) {
976 const GridBox& box = grid_[i];
977 if (!box.Matrixsize()) {
978 continue;
979 }
980
981 const Eigen::MatrixXd DMa = box.ReadFromBigMatrix(dmat_alpha);
982 const Eigen::MatrixXd DMb = box.ReadFromBigMatrix(dmat_beta);
983
984 double cutoff =
985 1.e-40 / double(dmat_alpha.rows()) / double(dmat_alpha.rows());
986 if (std::max(DMa.cwiseAbs2().maxCoeff(), DMb.cwiseAbs2().maxCoeff()) <
987 cutoff) {
988 continue;
989 }
990
991 const Index nf = box.Matrixsize();
992 const Index block = std::min(kPointBlock, box.size());
993 Eigen::MatrixXd Vxc_a_here = Eigen::MatrixXd::Zero(nf, nf);
994 Eigen::MatrixXd Vxc_b_here = Eigen::MatrixXd::Zero(nf, nf);
995 Eigen::MatrixXd phi(nf, block);
996 Eigen::MatrixXd dphi(nf, 3 * block);
997 Eigen::MatrixXd ya(nf, block);
998 Eigen::MatrixXd yb(nf, block);
999 Eigen::MatrixXd aa(nf, block);
1000 Eigen::MatrixXd ab(nf, block);
1001
1002 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
1003 const std::vector<double>& weights = box.getGridWeights();
1004
1005 for (Index start = 0; start < box.size(); start += block) {
1006 const Index nb = std::min(block, box.size() - start);
1007 for (Index p = 0; p < nb; ++p) {
1008 box.CalcAOValues(points[start + p], phi, dphi, p);
1009 }
1010 ya.leftCols(nb).noalias() = DMa * phi.leftCols(nb);
1011 yb.leftCols(nb).noalias() = DMb * phi.leftCols(nb);
1012
1013 kept.clear();
1014 rho.clear();
1015 sigma.clear();
1016 grad_a.clear();
1017 grad_b.clear();
1018 for (Index p = 0; p < nb; ++p) {
1019 const double rho_a = phi.col(p).dot(ya.col(p));
1020 const double rho_b = phi.col(p).dot(yb.col(p));
1021 if ((rho_a + rho_b) * weights[start + p] < 1.e-20) {
1022 continue;
1023 }
1024 const Eigen::Vector3d ga =
1025 2.0 * (dphi.middleCols(3 * p, 3).transpose() * ya.col(p));
1026 const Eigen::Vector3d gb =
1027 2.0 * (dphi.middleCols(3 * p, 3).transpose() * yb.col(p));
1028 kept.push_back(p);
1029 rho.push_back(rho_a);
1030 rho.push_back(rho_b);
1031 sigma.push_back(ga.dot(ga));
1032 sigma.push_back(ga.dot(gb));
1033 sigma.push_back(gb.dot(gb));
1034 grad_a.push_back(ga);
1035 grad_b.push_back(gb);
1036 }
1037 const Index nk = Index(kept.size());
1038 if (nk == 0) {
1039 continue;
1040 }
1041 exc.resize(nk);
1042 vrho.resize(2 * nk);
1043 vsigma.resize(3 * nk);
1044 EvaluateXCSpinBatch(nk, rho.data(), sigma.data(), exc.data(),
1045 vrho.data(), vsigma.data());
1046
1047 for (Index k = 0; k < nk; ++k) {
1048 const Index p = kept[k];
1049 const double w = weights[start + p];
1050 exc_private += w * (rho[2 * k] + rho[2 * k + 1]) * exc[k];
1051 // 0.5 because the result is symmetrized by adding the transpose
1052 aa.col(k) = (w * 0.5 * vrho[2 * k]) * phi.col(p);
1053 ab.col(k) = (w * 0.5 * vrho[2 * k + 1]) * phi.col(p);
1054 if (is_gga_) {
1055 const auto d = dphi.middleCols(3 * p, 3);
1056 const Eigen::VectorXd g_a = d * grad_a[k];
1057 const Eigen::VectorXd g_b = d * grad_b[k];
1058 const double vs_aa = vsigma[3 * k];
1059 const double vs_ab = vsigma[3 * k + 1];
1060 const double vs_bb = vsigma[3 * k + 2];
1061 aa.col(k).noalias() += w * (2.0 * vs_aa * g_a + vs_ab * g_b);
1062 ab.col(k).noalias() += w * (vs_ab * g_a + 2.0 * vs_bb * g_b);
1063 }
1064 if (k != p) {
1065 phi.col(k) = phi.col(p);
1066 }
1067 }
1068 Vxc_a_here.noalias() += aa.leftCols(nk) * phi.leftCols(nk).transpose();
1069 Vxc_b_here.noalias() += ab.leftCols(nk) * phi.leftCols(nk).transpose();
1070 }
1071
1072 box.AddtoBigMatrix(vxc_alpha_private, Vxc_a_here);
1073 box.AddtoBigMatrix(vxc_beta_private, Vxc_b_here);
1074 }
1075
1076#pragma omp critical
1077 {
1078 result.vxc_alpha += vxc_alpha_private + vxc_alpha_private.transpose();
1079 result.vxc_beta += vxc_beta_private + vxc_beta_private.transpose();
1080 result.energy += exc_private;
1081 }
1082 }
1083
1084 return result;
1085}
1086
1087namespace {
1088// SSW switching function and its derivative, restated from the private
1089// Vxc_Grid::erf1c (0.5*erfc(|x/(1-x^2)|*alpha), alpha = 1/0.30); must be kept
1090// consistent with it.
1091constexpr double kSSWAlpha = 1.0 / 0.30;
1092constexpr double kSSWCutoff = 0.725;
1093
1094constexpr double kSqrtPi = 1.7724538509055160273; // M_PI is not standard
1095
1096double SSWValue(double mu) {
1097 double val = 0.5 * std::erfc(std::abs(mu / (1.0 - mu * mu)) * kSSWAlpha);
1098 if (mu > 0.0) {
1099 val = 1.0 - val;
1100 }
1101 return val;
1102}
1103
1104// d(SSWValue)/d(mu), closed form.
1105double SSWDerivative(double mu) {
1106 double h = std::abs(mu) / (1.0 - mu * mu);
1107 double sign_mu = (mu > 0.0) ? 1.0 : ((mu < 0.0) ? -1.0 : 0.0);
1108 double one_minus_mu2 = 1.0 - mu * mu;
1109 double hprime = sign_mu * (1.0 + mu * mu) / (one_minus_mu2 * one_minus_mu2);
1110 double d_erf1c = -(kSSWAlpha / kSqrtPi) *
1111 std::exp(-(kSSWAlpha * h) * (kSSWAlpha * h)) * hprime;
1112 return (mu > 0.0) ? -d_erf1c : d_erf1c;
1113}
1114} // namespace
1115
1116template <class Grid>
1118 const Eigen::MatrixXd& density_matrix, const QMMolecule& atoms) const {
1119 Index natoms = atoms.size();
1120 Eigen::MatrixXd Rij = grid_.CalcInverseAtomDist(atoms);
1121
1122 Index nthreads = OPENMP::getMaxThreads();
1123 std::vector<Eigen::MatrixXd> grad_thread(nthreads,
1124 Eigen::MatrixXd::Zero(natoms, 3));
1125
1126 std::exception_ptr eptr_weight = nullptr;
1127#pragma omp parallel for schedule(guided)
1128 for (Index i = 0; i < grid_.getBoxesSize(); ++i) {
1129 try {
1130 Index thread_id = OPENMP::getThreadId();
1131 const GridBox& box = grid_[i];
1132 if (!box.Matrixsize()) {
1133 continue;
1134 }
1135
1136 const Eigen::MatrixXd DMAT_here =
1137 2 * box.ReadFromBigMatrix(density_matrix);
1138 double cutoff = 1.e-40 / double(density_matrix.rows()) /
1139 double(density_matrix.rows());
1140 if (DMAT_here.cwiseAbs2().maxCoeff() < cutoff) {
1141 continue;
1142 }
1143
1144 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
1145 const std::vector<double>& weights = box.getGridWeights();
1146 const std::vector<Index>& owner_atoms = box.getOwnerAtoms();
1147
1148 for (Index pidx = 0; pidx < box.size(); ++pidx) {
1149 AOShell::AOValues ao = box.CalcAOValues(points[pidx]);
1150 Eigen::VectorXd temp = ao.values.transpose() * DMAT_here;
1151 double rho = 0.5 * temp.dot(ao.values);
1152 double weight = weights[pidx];
1153 if (rho * weight < 1.e-20) {
1154 continue;
1155 }
1156 const Eigen::Vector3d rho_grad = temp.transpose() * ao.derivatives;
1157 typename Vxc_Potential<Grid>::XC_entry xc =
1158 EvaluateXC(rho, rho_grad.squaredNorm());
1159
1160 Index owner = owner_atoms[pidx];
1161 if (owner < 0) {
1162 // Owner atoms are set by GridSetup; skipping the point would give
1163 // an incomplete gradient.
1164 throw std::runtime_error(
1165 "GridWeightGradient: grid point has no owner atom set; the "
1166 "grid must be built with Vxc_Grid::GridSetup.");
1167 }
1168
1169 // rq(k) = distance from THIS point to atom k; needed for every
1170 // atom, not just the owner, since the partition weight depends on
1171 // distances to all atoms.
1172 const Eigen::Vector3d& point = points[pidx];
1173 Eigen::VectorXd rq(natoms);
1174 for (Index k = 0; k < natoms; ++k) {
1175 rq(k) = (point - atoms[k].getPos()).norm();
1176 }
1177
1178 // Build p[], mu_table, sk_table, hard-cutoff flags for every pair
1179 // -- same structure as Vxc_Grid::SSWpartition, but retaining the
1180 // per-pair intermediate values needed for the derivative (the
1181 // energy-level code discards these immediately after use).
1182 Eigen::VectorXd p = Eigen::VectorXd::Ones(natoms);
1183 Eigen::MatrixXd mu_table = Eigen::MatrixXd::Zero(natoms, natoms);
1184 Eigen::MatrixXd sk_table = Eigen::MatrixXd::Zero(natoms, natoms);
1185 // hard(j,i): 0 = smooth (sk_table valid), 1 = mu>cutoff (p[i]=0
1186 // hard), -1 = mu<-cutoff (p[j]=0 hard). Only upper triangle (j<i)
1187 // populated, matching the loop structure below.
1188 Eigen::MatrixXi hard = Eigen::MatrixXi::Zero(natoms, natoms);
1189 for (Index ii = 1; ii < natoms; ++ii) {
1190 for (Index jj = 0; jj < ii; ++jj) {
1191 double mu = (rq(ii) - rq(jj)) * Rij(jj, ii);
1192 mu_table(jj, ii) = mu;
1193 if (mu > kSSWCutoff) {
1194 p(ii) = 0.0;
1195 hard(jj, ii) = 1;
1196 } else if (mu < -kSSWCutoff) {
1197 p(jj) = 0.0;
1198 hard(jj, ii) = -1;
1199 } else {
1200 double sk = SSWValue(mu);
1201 sk_table(jj, ii) = sk;
1202 p(jj) *= sk;
1203 p(ii) *= (1.0 - sk);
1204 }
1205 }
1206 }
1207 double wsum = p.sum();
1208 double w_owner = p(owner) / wsum;
1209
1210 // d(rq(k))/dR_A: zero unless A is the point's owner or A == k, then
1211 // +-unit vector; zero if owner == k == A (point and owner move
1212 // together).
1213 auto d_rq_dR = [&](Index k, Index A) -> Eigen::Vector3d {
1214 if (A == owner && A == k) {
1215 return Eigen::Vector3d::Zero();
1216 } else if (A == owner) {
1217 return (point - atoms[k].getPos()) / rq(k);
1218 } else if (A == k) {
1219 return -(point - atoms[k].getPos()) / rq(k);
1220 }
1221 return Eigen::Vector3d::Zero();
1222 };
1223 auto d_Rab_dR = [&](Index a, Index b, Index A) -> Eigen::Vector3d {
1224 Eigen::Vector3d rvec = atoms[a].getPos() - atoms[b].getPos();
1225 double Rab = rvec.norm();
1226 if (A == a) {
1227 return rvec / Rab;
1228 } else if (A == b) {
1229 return -rvec / Rab;
1230 }
1231 return Eigen::Vector3d::Zero();
1232 };
1233 // a<b convention throughout, matching mu_table(a,b) with a<b.
1234 auto dmu_dR = [&](Index a, Index b, Index A) -> Eigen::Vector3d {
1235 Eigen::Vector3d d_rq_b = d_rq_dR(b, A);
1236 Eigen::Vector3d d_rq_a = d_rq_dR(a, A);
1237 double Rab = 1.0 / Rij(a, b);
1238 Eigen::Vector3d dRab = d_Rab_dR(a, b, A);
1239 double mu = mu_table(a, b);
1240 return (d_rq_b - d_rq_a) / Rab - (mu / Rab) * dRab;
1241 };
1242 auto dp_dR = [&](Index k, Index A) -> Eigen::Vector3d {
1243 // The log-derivative terms below divide by sk or (1-sk), which
1244 // gives 0/0 as p(k) -> 0 near SSW saturation; points with
1245 // negligible p(k) are treated as zero. The threshold is not
1246 // tuned.
1247 constexpr double kNegligibleP = 1.e-8;
1248 if (p(k) < kNegligibleP) {
1249 return Eigen::Vector3d::Zero();
1250 }
1251 Eigen::Vector3d total = Eigen::Vector3d::Zero();
1252 for (Index b = k + 1; b < natoms; ++b) {
1253 if (hard(k, b) != 0) {
1254 continue;
1255 }
1256 double skv = sk_table(k, b);
1257 if (skv < kNegligibleP) {
1258 continue; // avoid dividing by a near-zero skv
1259 }
1260 total += (SSWDerivative(mu_table(k, b)) / skv) * dmu_dR(k, b, A);
1261 }
1262 for (Index a = 0; a < k; ++a) {
1263 if (hard(a, k) != 0) {
1264 continue;
1265 }
1266 double skv = sk_table(a, k);
1267 double one_minus_skv = 1.0 - skv;
1268 if (one_minus_skv < kNegligibleP) {
1269 continue; // same for the (1-sk) denominator
1270 }
1271 total += (-SSWDerivative(mu_table(a, k)) / one_minus_skv) *
1272 dmu_dR(a, k, A);
1273 }
1274 return p(k) * total;
1275 };
1276
1277 // Skip points with negligible w_owner (then weight_p = C_p*w_owner
1278 // is negligible too, C_p being bounded) to avoid dividing by it.
1279 constexpr double kNegligibleWOwner = 1.e-8;
1280 if (w_owner < kNegligibleWOwner) {
1281 continue;
1282 }
1283
1284 // The stored weight is weight_p = C_p * w_owner(p), with C_p the
1285 // geometry-independent radial*angular quadrature weight and w_owner
1286 // the SSW partition weight. The energy C_p*w_owner*rho_p*f_xc_p is
1287 // differentiated through w_owner only (dw below), so the prefactor
1288 // needs C_p = weight/w_owner.
1289 double C_p = weight / w_owner;
1290
1291 double prefactor = C_p * rho * xc.f_xc;
1292
1293 for (Index A = 0; A < natoms; ++A) {
1294 Eigen::Vector3d dp_owner = dp_dR(owner, A);
1295 Eigen::Vector3d dwsum = Eigen::Vector3d::Zero();
1296 for (Index k = 0; k < natoms; ++k) {
1297 dwsum += dp_dR(k, A);
1298 }
1299 Eigen::Vector3d dw = dp_owner / wsum - w_owner * dwsum / wsum;
1300 Eigen::Vector3d contribution = prefactor * dw;
1301 grad_thread[thread_id].row(A) += contribution.transpose();
1302 }
1303 }
1304 } catch (...) {
1305#pragma omp critical
1306 {
1307 if (!eptr_weight) {
1308 eptr_weight = std::current_exception();
1309 }
1310 }
1311 }
1312 }
1313 if (eptr_weight) {
1314 std::rethrow_exception(eptr_weight);
1315 }
1316
1317 Eigen::MatrixXd grad = Eigen::MatrixXd::Zero(natoms, 3);
1318 for (Index t = 0; t < nthreads; ++t) {
1319 grad += grad_thread[t];
1320 }
1321 return grad;
1322}
1323
1324template <class Grid>
1326 const Eigen::MatrixXd& dmat_alpha, const Eigen::MatrixXd& dmat_beta,
1327 const AOBasis& dftbasis) const {
1328 // UKS version of PulayGradient (LDA and GGA). The three sigma terms
1329 // (sigma_aa, sigma_ab, sigma_bb) reduce to the restricted per-spin
1330 // expressions (Gmat_s, Hessian_rho_s), applied once per spin with the
1331 // effective gradient vectors
1332 // V_a = 2*vsigma_aa*rho_a_grad + vsigma_ab*rho_b_grad
1333 // V_b = 2*vsigma_bb*rho_b_grad + vsigma_ab*rho_a_grad.
1334 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
1335 Index nthreads = OPENMP::getMaxThreads();
1336 std::vector<Eigen::MatrixXd> grad_thread(nthreads,
1337 Eigen::MatrixXd::Zero(natoms, 3));
1338
1339 std::exception_ptr eptr_pulay_uks = nullptr;
1340#pragma omp parallel for schedule(guided)
1341 for (Index i = 0; i < grid_.getBoxesSize(); ++i) {
1342 try {
1343 Index thread_id = OPENMP::getThreadId();
1344 const GridBox& box = grid_[i];
1345 if (!box.Matrixsize()) {
1346 continue;
1347 }
1348
1349 const Eigen::MatrixXd DMa = box.ReadFromBigMatrix(dmat_alpha);
1350 const Eigen::MatrixXd DMb = box.ReadFromBigMatrix(dmat_beta);
1351
1352 // Box-local AO index -> atom index -- MUST use getAOranges() (start/
1353 // size per shell), not a naive sequential push_back per shell: a
1354 // single box's significant shells are not guaranteed to be grouped
1355 // contiguously by atom. Matches the exact construction already
1356 // validated in the restricted PulayGradient.
1357 std::vector<Index> local_idx_to_atom(box.Matrixsize());
1358 const std::vector<const AOShell*>& shells = box.getShells();
1359 const std::vector<GridboxRange>& ao_ranges = box.getAOranges();
1360 for (size_t s = 0; s < shells.size(); ++s) {
1361 Index atom = shells[s]->getAtomIndex();
1362 for (Index k = 0; k < ao_ranges[s].size; ++k) {
1363 local_idx_to_atom[ao_ranges[s].start + k] = atom;
1364 }
1365 }
1366 const std::vector<Index>& owner_atoms = box.getOwnerAtoms();
1367
1368 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
1369 const std::vector<double>& weights = box.getGridWeights();
1370
1371 for (Index p = 0; p < box.size(); ++p) {
1373
1374 Eigen::VectorXd temp_a = DMa * ao.values;
1375 Eigen::VectorXd temp_b = DMb * ao.values;
1376 const double rho_a = ao.values.dot(temp_a);
1377 const double rho_b = ao.values.dot(temp_b);
1378 const double rho = rho_a + rho_b;
1379 const double weight = weights[p];
1380
1381 if (rho * weight < 1.e-20) {
1382 continue;
1383 }
1384
1385 const Eigen::Vector3d rho_a_grad =
1386 2.0 * (ao.derivatives.transpose() * temp_a);
1387 const Eigen::Vector3d rho_b_grad =
1388 2.0 * (ao.derivatives.transpose() * temp_b);
1389 const double sigma_aa = rho_a_grad.dot(rho_a_grad);
1390 const double sigma_ab = rho_a_grad.dot(rho_b_grad);
1391 const double sigma_bb = rho_b_grad.dot(rho_b_grad);
1392
1394 EvaluateXCSpin(rho_a, rho_b, sigma_aa, sigma_ab, sigma_bb);
1395
1396 // translation term (A == owner(p) only), rho part
1397 Index owner_of_point = owner_atoms[p];
1398 grad_thread[thread_id].row(owner_of_point) +=
1399 (weight * (xc.vrho_a * rho_a_grad + xc.vrho_b * rho_b_grad))
1400 .transpose();
1401
1402 // GGA sigma terms: basis and translation terms as in the restricted
1403 // case, with Gmat_s/Hessian_rho_s per spin (from the spin density
1404 // matrices instead of DMAT_here), contracted with V_a/V_b.
1405 Eigen::MatrixX3d Gmat_a = 2.0 * (DMa * ao.derivatives);
1406 Eigen::MatrixX3d Gmat_b = 2.0 * (DMb * ao.derivatives);
1407 Eigen::Vector3d V_a =
1408 2.0 * xc.vsigma_aa * rho_a_grad + xc.vsigma_ab * rho_b_grad;
1409 Eigen::Vector3d V_b =
1410 2.0 * xc.vsigma_bb * rho_b_grad + xc.vsigma_ab * rho_a_grad;
1411
1412 // Translation-type sigma contribution: Hessian_rho_s * V_s,
1413 // summed over both spins.
1414 Eigen::Matrix3d Hessian_rho_a = Eigen::Matrix3d::Zero();
1415 Eigen::Matrix3d Hessian_rho_b = Eigen::Matrix3d::Zero();
1416 for (Index mu = 0; mu < box.Matrixsize(); ++mu) {
1417 Hessian_rho_a += 2.0 * temp_a(mu) * ao.hessians[mu];
1418 Hessian_rho_b += 2.0 * temp_b(mu) * ao.hessians[mu];
1419 }
1420 Eigen::Matrix3d Ma = ao.derivatives.transpose() * Gmat_a;
1421 Eigen::Matrix3d Mb = ao.derivatives.transpose() * Gmat_b;
1422 Hessian_rho_a += 0.5 * (Ma + Ma.transpose());
1423 Hessian_rho_b += 0.5 * (Mb + Mb.transpose());
1424
1425 grad_thread[thread_id].row(owner_of_point) +=
1426 (weight * (Hessian_rho_a * V_a + Hessian_rho_b * V_b)).transpose();
1427
1428 // Basis-type term (LDA + sigma), accumulated per local AO index
1429 // mu, scattered to the correct atom via local_idx_to_atom.
1430 Eigen::VectorXd Gmat_a_dot_Va = Gmat_a * V_a;
1431 Eigen::VectorXd Gmat_b_dot_Vb = Gmat_b * V_b;
1432 for (Index mu = 0; mu < box.Matrixsize(); ++mu) {
1433 Index atom = local_idx_to_atom[mu];
1434 Eigen::Vector3d lda_contribution =
1435 -2.0 * weight *
1436 (xc.vrho_a * temp_a(mu) + xc.vrho_b * temp_b(mu)) *
1437 ao.derivatives.row(mu).transpose();
1438 Eigen::Vector3d sigma_contribution =
1439 -weight *
1440 (ao.derivatives.row(mu).transpose() * Gmat_a_dot_Va(mu) +
1441 2.0 * temp_a(mu) * (ao.hessians[mu] * V_a) +
1442 ao.derivatives.row(mu).transpose() * Gmat_b_dot_Vb(mu) +
1443 2.0 * temp_b(mu) * (ao.hessians[mu] * V_b));
1444 grad_thread[thread_id].row(atom) +=
1445 (lda_contribution + sigma_contribution).transpose();
1446 }
1447 }
1448 } catch (...) {
1449#pragma omp critical
1450 {
1451 if (!eptr_pulay_uks) {
1452 eptr_pulay_uks = std::current_exception();
1453 }
1454 }
1455 }
1456 }
1457 if (eptr_pulay_uks) {
1458 std::rethrow_exception(eptr_pulay_uks);
1459 }
1460
1461 Eigen::MatrixXd grad = Eigen::MatrixXd::Zero(natoms, 3);
1462 for (Index t = 0; t < nthreads; ++t) {
1463 grad += grad_thread[t];
1464 }
1465 return grad;
1466}
1467
1468template <class Grid>
1470 const Eigen::MatrixXd& dmat_alpha, const Eigen::MatrixXd& dmat_beta,
1471 const QMMolecule& atoms) const {
1472 // UKS version of GridWeightGradient (LDA and GGA). The SSW weight
1473 // derivative (d_rq_dR, d_Rab_dR, dmu_dR, dp_dR, C_p/w_owner) is purely
1474 // geometric and identical; only the prefactor differs: rho_a, rho_b
1475 // from the spin density matrices (not pre-doubled, as in
1476 // IntegrateVXCSpin) and f_xc from EvaluateXCSpin.
1477 Index natoms = atoms.size();
1478 Eigen::MatrixXd Rij = grid_.CalcInverseAtomDist(atoms);
1479
1480 Index nthreads = OPENMP::getMaxThreads();
1481 std::vector<Eigen::MatrixXd> grad_thread(nthreads,
1482 Eigen::MatrixXd::Zero(natoms, 3));
1483
1484 std::exception_ptr eptr_weight_uks = nullptr;
1485#pragma omp parallel for schedule(guided)
1486 for (Index i = 0; i < grid_.getBoxesSize(); ++i) {
1487 try {
1488 Index thread_id = OPENMP::getThreadId();
1489 const GridBox& box = grid_[i];
1490 if (!box.Matrixsize()) {
1491 continue;
1492 }
1493
1494 const Eigen::MatrixXd DMa = box.ReadFromBigMatrix(dmat_alpha);
1495 const Eigen::MatrixXd DMb = box.ReadFromBigMatrix(dmat_beta);
1496 double cutoff =
1497 1.e-40 / double(dmat_alpha.rows()) / double(dmat_alpha.rows());
1498 if (std::max(DMa.cwiseAbs2().maxCoeff(), DMb.cwiseAbs2().maxCoeff()) <
1499 cutoff) {
1500 continue;
1501 }
1502
1503 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
1504 const std::vector<double>& weights = box.getGridWeights();
1505 const std::vector<Index>& owner_atoms = box.getOwnerAtoms();
1506
1507 for (Index pidx = 0; pidx < box.size(); ++pidx) {
1508 AOShell::AOValues ao = box.CalcAOValues(points[pidx]);
1509 Eigen::VectorXd temp_a = DMa * ao.values;
1510 Eigen::VectorXd temp_b = DMb * ao.values;
1511 const double rho_a = ao.values.dot(temp_a);
1512 const double rho_b = ao.values.dot(temp_b);
1513 const double rho = rho_a + rho_b;
1514 double weight = weights[pidx];
1515 if (rho * weight < 1.e-20) {
1516 continue;
1517 }
1518 const Eigen::Vector3d rho_a_grad =
1519 2.0 * (ao.derivatives.transpose() * temp_a);
1520 const Eigen::Vector3d rho_b_grad =
1521 2.0 * (ao.derivatives.transpose() * temp_b);
1522 const double sigma_aa = rho_a_grad.dot(rho_a_grad);
1523 const double sigma_ab = rho_a_grad.dot(rho_b_grad);
1524 const double sigma_bb = rho_b_grad.dot(rho_b_grad);
1526 EvaluateXCSpin(rho_a, rho_b, sigma_aa, sigma_ab, sigma_bb);
1527
1528 Index owner = owner_atoms[pidx];
1529 if (owner < 0) {
1530 throw std::runtime_error(
1531 "GridWeightGradientUKS: grid point has no owner atom set; the "
1532 "grid must be built with Vxc_Grid::GridSetup.");
1533 }
1534
1535 const Eigen::Vector3d& point = points[pidx];
1536 Eigen::VectorXd rq(natoms);
1537 for (Index k = 0; k < natoms; ++k) {
1538 rq(k) = (point - atoms[k].getPos()).norm();
1539 }
1540
1541 Eigen::VectorXd p = Eigen::VectorXd::Ones(natoms);
1542 Eigen::MatrixXd mu_table = Eigen::MatrixXd::Zero(natoms, natoms);
1543 Eigen::MatrixXd sk_table = Eigen::MatrixXd::Zero(natoms, natoms);
1544 Eigen::MatrixXi hard = Eigen::MatrixXi::Zero(natoms, natoms);
1545 for (Index ii = 1; ii < natoms; ++ii) {
1546 for (Index jj = 0; jj < ii; ++jj) {
1547 double mu = (rq(ii) - rq(jj)) * Rij(jj, ii);
1548 mu_table(jj, ii) = mu;
1549 if (mu > kSSWCutoff) {
1550 p(ii) = 0.0;
1551 hard(jj, ii) = 1;
1552 } else if (mu < -kSSWCutoff) {
1553 p(jj) = 0.0;
1554 hard(jj, ii) = -1;
1555 } else {
1556 double sk = SSWValue(mu);
1557 sk_table(jj, ii) = sk;
1558 p(jj) *= sk;
1559 p(ii) *= (1.0 - sk);
1560 }
1561 }
1562 }
1563 double wsum = p.sum();
1564 double w_owner = p(owner) / wsum;
1565
1566 auto d_rq_dR = [&](Index k, Index A) -> Eigen::Vector3d {
1567 if (A == owner && A == k) {
1568 return Eigen::Vector3d::Zero();
1569 } else if (A == owner) {
1570 return (point - atoms[k].getPos()) / rq(k);
1571 } else if (A == k) {
1572 return -(point - atoms[k].getPos()) / rq(k);
1573 }
1574 return Eigen::Vector3d::Zero();
1575 };
1576 auto d_Rab_dR = [&](Index a, Index b, Index A) -> Eigen::Vector3d {
1577 Eigen::Vector3d rvec = atoms[a].getPos() - atoms[b].getPos();
1578 double Rab = rvec.norm();
1579 if (A == a) {
1580 return rvec / Rab;
1581 } else if (A == b) {
1582 return -rvec / Rab;
1583 }
1584 return Eigen::Vector3d::Zero();
1585 };
1586 auto dmu_dR = [&](Index a, Index b, Index A) -> Eigen::Vector3d {
1587 Eigen::Vector3d d_rq_b = d_rq_dR(b, A);
1588 Eigen::Vector3d d_rq_a = d_rq_dR(a, A);
1589 double Rab = 1.0 / Rij(a, b);
1590 Eigen::Vector3d dRab = d_Rab_dR(a, b, A);
1591 double mu = mu_table(a, b);
1592 return (d_rq_b - d_rq_a) / Rab - (mu / Rab) * dRab;
1593 };
1594 auto dp_dR = [&](Index k, Index A) -> Eigen::Vector3d {
1595 constexpr double kNegligibleP = 1.e-8;
1596 if (p(k) < kNegligibleP) {
1597 return Eigen::Vector3d::Zero();
1598 }
1599 Eigen::Vector3d total = Eigen::Vector3d::Zero();
1600 for (Index b = k + 1; b < natoms; ++b) {
1601 if (hard(k, b) != 0) {
1602 continue;
1603 }
1604 double skv = sk_table(k, b);
1605 if (skv < kNegligibleP) {
1606 continue;
1607 }
1608 total += (SSWDerivative(mu_table(k, b)) / skv) * dmu_dR(k, b, A);
1609 }
1610 for (Index a = 0; a < k; ++a) {
1611 if (hard(a, k) != 0) {
1612 continue;
1613 }
1614 double skv = sk_table(a, k);
1615 double one_minus_skv = 1.0 - skv;
1616 if (one_minus_skv < kNegligibleP) {
1617 continue;
1618 }
1619 total += (-SSWDerivative(mu_table(a, k)) / one_minus_skv) *
1620 dmu_dR(a, k, A);
1621 }
1622 return p(k) * total;
1623 };
1624
1625 constexpr double kNegligibleWOwner = 1.e-8;
1626 if (w_owner < kNegligibleWOwner) {
1627 continue;
1628 }
1629 double C_p = weight / w_owner;
1630 double prefactor = C_p * rho * xc.f_xc;
1631
1632 for (Index A = 0; A < natoms; ++A) {
1633 Eigen::Vector3d dp_owner = dp_dR(owner, A);
1634 Eigen::Vector3d dwsum = Eigen::Vector3d::Zero();
1635 for (Index k = 0; k < natoms; ++k) {
1636 dwsum += dp_dR(k, A);
1637 }
1638 Eigen::Vector3d dw = dp_owner / wsum - w_owner * dwsum / wsum;
1639 Eigen::Vector3d contribution = prefactor * dw;
1640 grad_thread[thread_id].row(A) += contribution.transpose();
1641 }
1642 }
1643 } catch (...) {
1644#pragma omp critical
1645 {
1646 if (!eptr_weight_uks) {
1647 eptr_weight_uks = std::current_exception();
1648 }
1649 }
1650 }
1651 }
1652 if (eptr_weight_uks) {
1653 std::rethrow_exception(eptr_weight_uks);
1654 }
1655
1656 Eigen::MatrixXd grad = Eigen::MatrixXd::Zero(natoms, 3);
1657 for (Index t = 0; t < nthreads; ++t) {
1658 grad += grad_thread[t];
1659 }
1660 return grad;
1661}
1662
1663template class Vxc_Potential<Vxc_Grid>;
1664
1665} // namespace xtp
1666} // namespace votca
break string into words
Definition tokenizer.h:72
std::vector< T > ToVector()
store all words in a vector of type T, does type conversion.
Definition tokenizer.h:109
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
const std::vector< Index > & getFuncPerAtom() const
Definition aobasis.h:72
const Eigen::Vector3d & getPos() const
const std::vector< Eigen::Vector3d > & getGridPoints() const
Definition gridbox.h:55
Eigen::MatrixXd ReadFromBigMatrix(const Eigen::MatrixXd &bigmatrix) const
Definition gridbox.cc:97
void AddtoBigMatrix(Eigen::MatrixXd &bigmatrix, const Eigen::MatrixXd &smallmatrix) const
Definition gridbox.cc:84
const std::vector< const AOShell * > & getShells() const
Definition gridbox.h:65
const std::vector< GridboxRange > & getAOranges() const
Definition gridbox.h:69
const std::vector< double > & getGridWeights() const
Definition gridbox.h:57
const std::vector< Index > & getOwnerAtoms() const
Definition gridbox.h:63
AOShell::AOValuesHessian CalcAOValuesHessian(const Eigen::Vector3d &point) const
Definition gridbox.cc:68
AOShell::AOValues CalcAOValues(const Eigen::Vector3d &point) const
Definition gridbox.cc:44
Index size() const
Definition gridbox.h:71
Index Matrixsize() const
Definition gridbox.h:75
Eigen::MatrixXd & matrix()
Definition eigen.h:97
double & energy()
Definition eigen.h:98
conversion of functional string into integer
static constexpr Index kPointBlock
void EvaluateXCSpinBatch(Index n, const double *rho, const double *sigma, double *exc, double *vrho, double *vsigma) const
Eigen::MatrixXd GridWeightGradient(const Eigen::MatrixXd &density_matrix, const QMMolecule &atoms) const
Eigen::MatrixXd PulayGradient(const Eigen::MatrixXd &density_matrix, const AOBasis &dftbasis) const
void EvaluateFXCSpinBatch(Index n, const double *rho, const double *sigma, double *vsigma, double *v2rho2, double *v2rhosigma, double *v2sigma2) const
XC_entry_spin EvaluateXCSpin(double rho_a, double rho_b, double sigma_aa, double sigma_ab, double sigma_bb) const
void EvaluateXCBatch(Index n, const double *rho, const double *sigma, double *exc, double *vrho, double *vsigma) const
Eigen::MatrixXd PulayGradientUKS(const Eigen::MatrixXd &dmat_alpha, const Eigen::MatrixXd &dmat_beta, const AOBasis &dftbasis) const
XC_entry EvaluateXC(double rho, double sigma) const
void EvaluateFXCBatch(Index n, const double *rho, const double *sigma, double *vsigma, double *v2rho2, double *v2rhosigma, double *v2sigma2) const
Mat_p_Energy IntegrateVXC(const Eigen::MatrixXd &density_matrix) const
void setXCfunctional(const std::string &functional)
std::vector< std::array< Eigen::MatrixXd, 2 > > IntegrateFXCSpin(const Eigen::MatrixXd &dmat_alpha, const Eigen::MatrixXd &dmat_beta, const std::vector< std::array< Eigen::MatrixXd, 2 > > &response) const
static double getExactExchange(const std::string &functional)
std::vector< Eigen::MatrixXd > IntegrateFXC(const Eigen::MatrixXd &density_matrix, const std::vector< Eigen::MatrixXd > &response_matrices) const
Eigen::MatrixXd GridWeightGradientUKS(const Eigen::MatrixXd &dmat_alpha, const Eigen::MatrixXd &dmat_beta, const QMMolecule &atoms) const
SpinResult IntegrateVXCSpin(const Eigen::MatrixXd &dmat_alpha, const Eigen::MatrixXd &dmat_beta) const
Index getMaxThreads()
Definition eigen.h:145
Index getThreadId()
Definition eigen.h:160
Charge transport classes.
Definition ERIs.h:28
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
std::vector< Eigen::Matrix3d > hessians
Definition aoshell.h:180
Eigen::MatrixX3d derivatives
Definition aoshell.h:131
double vsigma_ab
double vrho_b
double vrho_a
double vsigma_aa
double vsigma_bb
double f_xc
double f_xc
double df_dsigma
double df_drho