votca 2026-dev
Loading...
Searching...
No Matches
hirshfeldpartition.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// Local VOTCA includes
22#include "votca/xtp/aoshell.h"
23#include "votca/xtp/basisset.h"
24#include "votca/xtp/eigen.h"
25#include "votca/xtp/gridbox.h"
26
27// Standard includes
28#include <cmath>
29#include <stdexcept>
30
31namespace votca {
32namespace xtp {
33
34std::vector<HirshfeldPartition::AtomicReference>
36 const QMMolecule& mol, const std::string& basisset_name,
37 const std::map<std::string, Eigen::MatrixXd>& reference_densities) {
38 BasisSet basisset;
39 basisset.Load(basisset_name);
40
41 std::vector<AtomicReference> atoms;
42 atoms.reserve(mol.size());
43 for (const QMAtom& real_atom : mol) {
44 auto it = reference_densities.find(real_atom.getElement());
45 if (it == reference_densities.end()) {
46 // Deliberately fail loudly rather than silently skip: a missing
47 // entry here means DFTEngine::ComputeHirshfeldReferenceDensities
48 // was not actually run for every element this molecule contains,
49 // which would otherwise show up only much later, as a silently
50 // wrong (missing-atom) weight denominator.
51 throw std::runtime_error(
52 "HirshfeldPartition::BuildAtomicReferences: no reference "
53 "density found for element '" +
54 real_atom.getElement() +
55 "' -- was DFTEngine::ComputeHirshfeldReferenceDensities "
56 "actually run for every element in this molecule?");
57 }
58
59 // Same construction RunAtomicDFT_unrestricted itself uses (a
60 // single-atom QMMolecule, then AOBasis::Fill on it) -- except at
61 // this REAL atom's own position, not the origin, so the resulting
62 // basis functions are already centered where they need to be for
63 // EvaluateAtomicDensity/EvaluateWeight to evaluate correctly.
64 QMMolecule single_atom("hirshfeld_reference_atom", 0);
65 single_atom.push_back(
66 QMAtom(0, real_atom.getElement(), real_atom.getPos()));
67
69 ref.basis.Fill(basisset, single_atom);
70 ref.density = it->second;
71 atoms.push_back(std::move(ref));
72 }
73 return atoms;
74}
75
77 const AOBasis& atom_basis, const Eigen::MatrixXd& reference_density,
78 const Eigen::Vector3d& point) {
79 Eigen::VectorXd ao_values = Eigen::VectorXd::Zero(atom_basis.AOBasisSize());
80 for (const AOShell& shell : atom_basis) {
81 AOShell::AOValues vals = shell.EvalAOspace(point);
82 ao_values.segment(shell.getStartIndex(), shell.getNumFunc()) = vals.values;
83 }
84 return ao_values.dot(reference_density * ao_values);
85}
86
88 const AOBasis& atom_basis, const Eigen::MatrixXd& reference_density,
89 const Eigen::Vector3d& point) {
90 Index n = atom_basis.AOBasisSize();
91 Eigen::VectorXd ao_values = Eigen::VectorXd::Zero(n);
92 Eigen::MatrixX3d ao_derivatives = Eigen::MatrixX3d::Zero(n, 3);
93 for (const AOShell& shell : atom_basis) {
94 AOShell::AOValues vals = shell.EvalAOspace(point);
95 ao_values.segment(shell.getStartIndex(), shell.getNumFunc()) = vals.values;
96 ao_derivatives.block(shell.getStartIndex(), 0, shell.getNumFunc(), 3) =
97 vals.derivatives;
98 }
99 // rho(r) = phi^T P phi, so grad_rho(r) = 2 * derivatives^T * (P * phi) --
100 // same quadratic-form pattern as EvaluateAtomicDensity itself, just
101 // carrying the extra factor of 2 and the derivative (rather than
102 // value) of the second phi in the product rule.
103 return 2.0 * ao_derivatives.transpose() * (reference_density * ao_values);
104}
105
107 const std::vector<AtomicReference>& atoms, Index target_atom_index,
108 const Eigen::Vector3d& point) {
109 double denominator = 0.0;
110 double numerator = 0.0;
111 for (Index j = 0; j < static_cast<Index>(atoms.size()); ++j) {
112 double rho_j =
113 EvaluateAtomicDensity(atoms[j].basis, atoms[j].density, point);
114 denominator += rho_j;
115 if (j == target_atom_index) {
116 numerator = rho_j;
117 }
118 }
119 // Same negligible-denominator guard pattern already used for the SSW
120 // grid weights in GridWeightGradient (kNegligibleWOwner there) --
121 // points far from every atom, where every rho_j(point) is
122 // negligible, would otherwise divide a near-zero numerator by a
123 // near-zero denominator.
124 constexpr double kNegligibleDenominator = 1.e-12;
125 if (denominator < kNegligibleDenominator) {
126 return 0.0;
127 }
128 return numerator / denominator;
129}
130
132 const std::vector<AtomicReference>& atoms, Index target_atom_index,
133 Index differentiate_atom_index, const Eigen::Vector3d& point) {
134 double denominator = 0.0;
135 for (Index j = 0; j < static_cast<Index>(atoms.size()); ++j) {
136 denominator +=
137 EvaluateAtomicDensity(atoms[j].basis, atoms[j].density, point);
138 }
139 // Same negligible-denominator guard as EvaluateWeight itself, for
140 // the identical reason -- points far from every atom, where the
141 // whole formula is 0/0 in the limit.
142 constexpr double kNegligibleDenominator = 1.e-12;
143 if (denominator < kNegligibleDenominator) {
144 return Eigen::Vector3d::Zero();
145 }
146
147 double w_target = EvaluateWeight(atoms, target_atom_index, point);
148 Eigen::Vector3d grad_rho_A = EvaluateAtomicDensityGradient(
149 atoms[differentiate_atom_index].basis,
150 atoms[differentiate_atom_index].density, point);
151 double indicator =
152 (differentiate_atom_index == target_atom_index) ? 1.0 : 0.0;
153 // d w_target/d R_A = [w_target(point) - 1_{A==target}] *
154 // grad_rho_A(point) / rho_tot(point)
155 // -- see this function's own header comment for the derivation.
156 return (w_target - indicator) * grad_rho_A / denominator;
157}
158
160 const std::vector<AtomicReference>& atoms, Index target_atom_index,
161 const Eigen::Vector3d& point) {
162 double denominator = 0.0;
163 Eigen::Vector3d grad_denominator = Eigen::Vector3d::Zero();
164 for (Index j = 0; j < static_cast<Index>(atoms.size()); ++j) {
165 denominator +=
166 EvaluateAtomicDensity(atoms[j].basis, atoms[j].density, point);
167 grad_denominator +=
168 EvaluateAtomicDensityGradient(atoms[j].basis, atoms[j].density, point);
169 }
170 constexpr double kNegligibleDenominator = 1.e-12;
171 if (denominator < kNegligibleDenominator) {
172 return Eigen::Vector3d::Zero();
173 }
174
175 double w_target = EvaluateWeight(atoms, target_atom_index, point);
176 Eigen::Vector3d grad_rho_target = EvaluateAtomicDensityGradient(
177 atoms[target_atom_index].basis, atoms[target_atom_index].density, point);
178 // grad_r w(r) = [grad_r rho_target(r) - w(r)*grad_r rho_tot(r)] /
179 // rho_tot(r) -- see this function's own header comment.
180 return (grad_rho_target - w_target * grad_denominator) / denominator;
181}
182
184 const std::vector<AtomicReference>& atoms, Index target_atom_index,
185 const AOBasis& full_dftbasis, const Vxc_Grid& grid) {
186 Index full_size = full_dftbasis.AOBasisSize();
187 Eigen::MatrixXd W = Eigen::MatrixXd::Zero(full_size, full_size);
188
189 // Deliberately omp critical around AddtoBigMatrix alone, rather than
190 // the per-thread full-Natoms-squared-sized accumulator pattern used
191 // for the (much smaller) Natoms x 3 force-contribution functions
192 // below -- W is a full AOBasisSize x AOBasisSize matrix, potentially
193 // large for bigger systems, so nthreads separate copies of it would
194 // be a real memory cost; box_contribution (the only thing entering
195 // the critical section) is small and box-local, so only that final,
196 // cheap accumulation step is serialized, not the expensive per-point
197 // EvaluateWeight/CalcAOValues work this loop spends most of its time
198 // on.
199 std::exception_ptr eptr_weightmatrix = nullptr;
200#pragma omp parallel for schedule(guided)
201 for (Index i = 0; i < grid.getBoxesSize(); ++i) {
202 try {
203 const GridBox& box = grid[i];
204 if (!box.Matrixsize()) {
205 continue;
206 }
207 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
208 const std::vector<double>& weights = box.getGridWeights();
209
210 Eigen::MatrixXd box_contribution =
211 Eigen::MatrixXd::Zero(box.Matrixsize(), box.Matrixsize());
212
213 for (Index p = 0; p < static_cast<Index>(points.size()); ++p) {
214 double w_i = EvaluateWeight(atoms, target_atom_index, points[p]);
215 if (w_i == 0.0) {
216 // Purely a performance guard (skip the AO evaluation entirely
217 // when it cannot contribute anything), not a correctness one --
218 // see this function's own header comment.
219 continue;
220 }
221 AOShell::AOValues ao = box.CalcAOValues(points[p]);
222 box_contribution +=
223 (weights[p] * w_i) * (ao.values * ao.values.transpose());
224 }
225
226#pragma omp critical
227 {
228 box.AddtoBigMatrix(W, box_contribution);
229 }
230 } catch (...) {
231#pragma omp critical
232 {
233 if (!eptr_weightmatrix) {
234 eptr_weightmatrix = std::current_exception();
235 }
236 }
237 }
238 }
239 if (eptr_weightmatrix) {
240 std::rethrow_exception(eptr_weightmatrix);
241 }
242
243 // The outer product ao.values * ao.values.transpose() is already
244 // exactly symmetric by construction, so this is a numerical no-op --
245 // guards only against tiny floating-point asymmetry accumulating
246 // across many grid points, matching the same defensive pattern used
247 // elsewhere in this branch for operator matrices built this way.
248 W = 0.5 * (W + W.transpose());
249 return W;
250}
251
252namespace {
253// Faithfully duplicated from vxc_potential.cc's own anonymous namespace
254// (SSWValue/SSWDerivative/kSSWCutoff there) -- those are genuinely
255// inaccessible from this file (confirmed: they are inside an
256// anonymous namespace in that translation unit, not exposed via any
257// header), and per the explicit decision this grew out of, this is a
258// deliberate duplication rather than a refactor of that existing,
259// already-validated file. Must stay byte-for-byte consistent with the
260// original if it is ever changed there.
261constexpr double kSSWAlpha = 1.0 / 0.30;
262constexpr double kSSWCutoff = 0.725;
263constexpr double kSqrtPi = 1.7724538509055160273;
264
265double SSWValue(double mu) {
266 double val = 0.5 * std::erfc(std::abs(mu / (1.0 - mu * mu)) * kSSWAlpha);
267 if (mu > 0.0) {
268 val = 1.0 - val;
269 }
270 return val;
271}
272
273double SSWDerivative(double mu) {
274 double h = std::abs(mu) / (1.0 - mu * mu);
275 double sign_mu = (mu > 0.0) ? 1.0 : ((mu < 0.0) ? -1.0 : 0.0);
276 double one_minus_mu2 = 1.0 - mu * mu;
277 double hprime = sign_mu * (1.0 + mu * mu) / (one_minus_mu2 * one_minus_mu2);
278 double d_erf1c = -(kSSWAlpha / kSqrtPi) *
279 std::exp(-(kSSWAlpha * h) * (kSSWAlpha * h)) * hprime;
280 return (mu > 0.0) ? -d_erf1c : d_erf1c;
281}
282} // namespace
283
285 const std::vector<AtomicReference>& atoms, Index target_atom_index,
286 const Eigen::MatrixXd& density_matrix, const QMMolecule& mol,
287 const Vxc_Grid& grid) {
288 Index natoms = mol.size();
289 Eigen::MatrixXd Rij = grid.CalcInverseAtomDist(mol);
290
291 Index nthreads = OPENMP::getMaxThreads();
292 std::vector<Eigen::MatrixXd> force_thread(nthreads,
293 Eigen::MatrixXd::Zero(natoms, 3));
294
295 std::exception_ptr eptr_gridweight = nullptr;
296#pragma omp parallel for schedule(guided)
297 for (Index i = 0; i < grid.getBoxesSize(); ++i) {
298 try {
299 Index thread_id = OPENMP::getThreadId();
300 const GridBox& box = grid[i];
301 if (!box.Matrixsize()) {
302 continue;
303 }
304
305 const Eigen::MatrixXd DMAT_here = box.ReadFromBigMatrix(density_matrix);
306 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
307 const std::vector<double>& weights = box.getGridWeights();
308 const std::vector<Index>& owner_atoms = box.getOwnerAtoms();
309
310 for (Index pidx = 0; pidx < box.size(); ++pidx) {
311 AOShell::AOValues ao = box.CalcAOValues(points[pidx]);
312 double rho_molecule = ao.values.dot(DMAT_here * ao.values);
313 double weight = weights[pidx];
314 if (std::abs(rho_molecule * weight) < 1.e-20) {
315 continue;
316 }
317
318 Index owner = owner_atoms[pidx];
319 if (owner < 0) {
320 throw std::runtime_error(
321 "GridWeightDerivativeContribution: grid point has no "
322 "owner_atom set -- was this grid built via GridSetup after "
323 "the owner-atom tracking change?");
324 }
325
326 const Eigen::Vector3d& point = points[pidx];
327 double w_c = EvaluateWeight(atoms, target_atom_index, point);
328
329 Eigen::VectorXd rq(natoms);
330 for (Index k = 0; k < natoms; ++k) {
331 rq(k) = (point - mol[k].getPos()).norm();
332 }
333
334 Eigen::VectorXd p = Eigen::VectorXd::Ones(natoms);
335 Eigen::MatrixXd mu_table = Eigen::MatrixXd::Zero(natoms, natoms);
336 Eigen::MatrixXd sk_table = Eigen::MatrixXd::Zero(natoms, natoms);
337 Eigen::MatrixXi hard = Eigen::MatrixXi::Zero(natoms, natoms);
338 for (Index ii = 1; ii < natoms; ++ii) {
339 for (Index jj = 0; jj < ii; ++jj) {
340 double mu = (rq(ii) - rq(jj)) * Rij(jj, ii);
341 mu_table(jj, ii) = mu;
342 if (mu > kSSWCutoff) {
343 p(ii) = 0.0;
344 hard(jj, ii) = 1;
345 } else if (mu < -kSSWCutoff) {
346 p(jj) = 0.0;
347 hard(jj, ii) = -1;
348 } else {
349 double sk = SSWValue(mu);
350 sk_table(jj, ii) = sk;
351 p(jj) *= sk;
352 p(ii) *= (1.0 - sk);
353 }
354 }
355 }
356 double wsum = p.sum();
357 double w_owner = p(owner) / wsum;
358 constexpr double kNegligibleWOwner = 1.e-8;
359 if (w_owner < kNegligibleWOwner) {
360 continue;
361 }
362 double C_p = weight / w_owner;
363
364 auto d_rq_dR = [&](Index k, Index A) -> Eigen::Vector3d {
365 if (A == owner && A == k) {
366 return Eigen::Vector3d::Zero();
367 } else if (A == owner) {
368 return (point - mol[k].getPos()) / rq(k);
369 } else if (A == k) {
370 return -(point - mol[k].getPos()) / rq(k);
371 }
372 return Eigen::Vector3d::Zero();
373 };
374 auto d_Rab_dR = [&](Index a, Index b, Index A) -> Eigen::Vector3d {
375 Eigen::Vector3d rvec = mol[a].getPos() - mol[b].getPos();
376 double Rab = rvec.norm();
377 if (A == a) {
378 return rvec / Rab;
379 } else if (A == b) {
380 return -rvec / Rab;
381 }
382 return Eigen::Vector3d::Zero();
383 };
384 auto dmu_dR = [&](Index a, Index b, Index A) -> Eigen::Vector3d {
385 Eigen::Vector3d d_rq_b = d_rq_dR(b, A);
386 Eigen::Vector3d d_rq_a = d_rq_dR(a, A);
387 double Rab = 1.0 / Rij(a, b);
388 Eigen::Vector3d dRab = d_Rab_dR(a, b, A);
389 double mu = mu_table(a, b);
390 return (d_rq_b - d_rq_a) / Rab - (mu / Rab) * dRab;
391 };
392 auto dp_dR = [&](Index k, Index A) -> Eigen::Vector3d {
393 constexpr double kNegligibleP = 1.e-8;
394 if (p(k) < kNegligibleP) {
395 return Eigen::Vector3d::Zero();
396 }
397 Eigen::Vector3d total = Eigen::Vector3d::Zero();
398 for (Index b = k + 1; b < natoms; ++b) {
399 if (hard(k, b) != 0) {
400 continue;
401 }
402 double skv = sk_table(k, b);
403 if (skv < kNegligibleP) {
404 continue;
405 }
406 total += (SSWDerivative(mu_table(k, b)) / skv) * dmu_dR(k, b, A);
407 }
408 for (Index a = 0; a < k; ++a) {
409 if (hard(a, k) != 0) {
410 continue;
411 }
412 double skv = sk_table(a, k);
413 double one_minus_skv = 1.0 - skv;
414 if (one_minus_skv < kNegligibleP) {
415 continue;
416 }
417 total += (-SSWDerivative(mu_table(a, k)) / one_minus_skv) *
418 dmu_dR(a, k, A);
419 }
420 return p(k) * total;
421 };
422
423 // Same prefactor role as GridWeightGradient's own C_p*rho*xc.f_xc,
424 // just with w_c(point) in place of xc.f_xc -- this point's
425 // contribution to Tr[D*W_c] is weight*w_c*rho_molecule =
426 // C_p*w_owner*w_c*rho_molecule, and differentiating w_owner alone
427 // (dw below) needs this same missing C_p factor multiplied back
428 // in, for the identical reason documented in GridWeightGradient's
429 // own comment on this.
430 double prefactor = C_p * w_c * rho_molecule;
431
432 for (Index A = 0; A < natoms; ++A) {
433 Eigen::Vector3d dp_owner = dp_dR(owner, A);
434 Eigen::Vector3d dwsum = Eigen::Vector3d::Zero();
435 for (Index k = 0; k < natoms; ++k) {
436 dwsum += dp_dR(k, A);
437 }
438 Eigen::Vector3d dw = dp_owner / wsum - w_owner * dwsum / wsum;
439 force_thread[thread_id].row(A) += (prefactor * dw).transpose();
440 }
441 }
442 } catch (...) {
443#pragma omp critical
444 {
445 if (!eptr_gridweight) {
446 eptr_gridweight = std::current_exception();
447 }
448 }
449 }
450 }
451 if (eptr_gridweight) {
452 std::rethrow_exception(eptr_gridweight);
453 }
454
455 Eigen::MatrixXd force_contribution = Eigen::MatrixXd::Zero(natoms, 3);
456 for (Index t = 0; t < nthreads; ++t) {
457 force_contribution += force_thread[t];
458 }
459 return force_contribution;
460}
461
463 const std::vector<AtomicReference>& atoms, Index target_atom_index,
464 const Eigen::MatrixXd& density_matrix, const AOBasis& full_dftbasis,
465 const Vxc_Grid& grid) {
466 Index natoms = static_cast<Index>(full_dftbasis.getFuncPerAtom().size());
467
468 Index nthreads = OPENMP::getMaxThreads();
469 std::vector<Eigen::MatrixXd> force_thread(nthreads,
470 Eigen::MatrixXd::Zero(natoms, 3));
471
472 std::exception_ptr eptr_pulaytranslation = nullptr;
473#pragma omp parallel for schedule(guided)
474 for (Index i = 0; i < grid.getBoxesSize(); ++i) {
475 try {
476 Index thread_id = OPENMP::getThreadId();
477 const GridBox& box = grid[i];
478 if (!box.Matrixsize()) {
479 continue;
480 }
481
482 // DMAT_here carries the same factor-of-2 convention as
483 // PulayGradient's own DMAT_here -- temp(mu) below is therefore
484 // already 2*(D*phi)_mu, matching that function's own contribution
485 // formula exactly (no separate factor of 2 needed at the point of
486 // use).
487 const Eigen::MatrixXd DMAT_here =
488 2 * box.ReadFromBigMatrix(density_matrix);
489
490 // Same box-local-AO-index -> atom-index bookkeeping as
491 // PulayGradient's own, identical reasoning: a box's significant
492 // shells are not guaranteed to be grouped contiguously by atom.
493 std::vector<Index> local_idx_to_atom(box.Matrixsize());
494 const std::vector<const AOShell*>& shells = box.getShells();
495 const std::vector<GridboxRange>& ao_ranges = box.getAOranges();
496 for (size_t s = 0; s < shells.size(); ++s) {
497 Index atom = shells[s]->getAtomIndex();
498 for (Index k = 0; k < ao_ranges[s].size; ++k) {
499 local_idx_to_atom[ao_ranges[s].start + k] = atom;
500 }
501 }
502
503 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
504 const std::vector<double>& weights = box.getGridWeights();
505 const std::vector<Index>& owner_atoms = box.getOwnerAtoms();
506
507 for (Index p = 0; p < box.size(); ++p) {
508 const Eigen::Vector3d& point = points[p];
509 AOShell::AOValues ao = box.CalcAOValues(point);
510 Eigen::VectorXd temp = ao.values.transpose() * DMAT_here;
511 double rho_molecule = 0.5 * temp.dot(ao.values);
512 double weight = weights[p];
513 if (std::abs(rho_molecule * weight) < 1.e-20) {
514 continue;
515 }
516
517 double w_c = EvaluateWeight(atoms, target_atom_index, point);
518
519 // --- Pulay (basis-function) term ---
520 // Same sign/accumulation convention as PulayGradient's own LDA
521 // term, confirmed directly: contribution = -weight * <potential>
522 // * temp(mu) * ao.derivatives.row(mu), with w_c(point) here in
523 // place of xc.df_drho there.
524 for (Index mu = 0; mu < box.Matrixsize(); ++mu) {
525 Index atom = local_idx_to_atom[mu];
526 Eigen::Vector3d contribution =
527 -weight * w_c * temp(mu) * ao.derivatives.row(mu).transpose();
528 force_thread[thread_id].row(atom) += contribution.transpose();
529 }
530
531 // --- Grid-point-translation term ---
532 // Only the point's OWNER atom is affected (r_p moves rigidly
533 // with its owner only) -- see EvaluatePointWeightGradient's own
534 // header comment for the derivation. Full product rule, since
535 // both w_c(r) and rho_molecule(r) vary here (unlike the analogous
536 // XC term, which has only one point-varying factor -- xc.df_drho
537 // is evaluated AT rho, not itself differentiated in that specific
538 // term).
539 Index owner = owner_atoms[p];
540 if (owner < 0) {
541 throw std::runtime_error(
542 "PulayAndTranslationContribution: grid point has no "
543 "owner_atom set -- was this grid built via GridSetup after "
544 "the owner-atom tracking change?");
545 }
546 Eigen::Vector3d rho_molecule_grad = temp.transpose() * ao.derivatives;
547 Eigen::Vector3d grad_w_c =
548 EvaluatePointWeightGradient(atoms, target_atom_index, point);
549 Eigen::Vector3d translation_term =
550 weight * (grad_w_c * rho_molecule + w_c * rho_molecule_grad);
551 force_thread[thread_id].row(owner) += translation_term.transpose();
552 }
553 } catch (...) {
554#pragma omp critical
555 {
556 if (!eptr_pulaytranslation) {
557 eptr_pulaytranslation = std::current_exception();
558 }
559 }
560 }
561 }
562 if (eptr_pulaytranslation) {
563 std::rethrow_exception(eptr_pulaytranslation);
564 }
565
566 Eigen::MatrixXd force_contribution = Eigen::MatrixXd::Zero(natoms, 3);
567 for (Index t = 0; t < nthreads; ++t) {
568 force_contribution += force_thread[t];
569 }
570 return force_contribution;
571}
572
574 const std::vector<AtomicReference>& atoms, Index target_atom_index,
575 const Eigen::MatrixXd& density_matrix, const AOBasis& full_dftbasis,
576 const Vxc_Grid& grid) {
577 Index natoms = static_cast<Index>(full_dftbasis.getFuncPerAtom().size());
578
579 Index nthreads = OPENMP::getMaxThreads();
580 std::vector<Eigen::MatrixXd> force_thread(nthreads,
581 Eigen::MatrixXd::Zero(natoms, 3));
582
583 std::exception_ptr eptr_weightfunction = nullptr;
584#pragma omp parallel for schedule(guided)
585 for (Index i = 0; i < grid.getBoxesSize(); ++i) {
586 try {
587 Index thread_id = OPENMP::getThreadId();
588 const GridBox& box = grid[i];
589 if (!box.Matrixsize()) {
590 continue;
591 }
592 const Eigen::MatrixXd DMAT_here = box.ReadFromBigMatrix(density_matrix);
593 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
594 const std::vector<double>& weights = box.getGridWeights();
595
596 for (Index p = 0; p < box.size(); ++p) {
597 const Eigen::Vector3d& point = points[p];
598 AOShell::AOValues ao = box.CalcAOValues(point);
599 double rho_molecule = ao.values.dot(DMAT_here * ao.values);
600 double weight = weights[p];
601 if (std::abs(rho_molecule * weight) < 1.e-20) {
602 continue;
603 }
604 double prefactor = weight * rho_molecule;
605 for (Index A = 0; A < natoms; ++A) {
606 Eigen::Vector3d dw_dR_A =
607 EvaluateWeightGradient(atoms, target_atom_index, A, point);
608 force_thread[thread_id].row(A) += (prefactor * dw_dR_A).transpose();
609 }
610 }
611 } catch (...) {
612#pragma omp critical
613 {
614 if (!eptr_weightfunction) {
615 eptr_weightfunction = std::current_exception();
616 }
617 }
618 }
619 }
620 if (eptr_weightfunction) {
621 std::rethrow_exception(eptr_weightfunction);
622 }
623
624 Eigen::MatrixXd force_contribution = Eigen::MatrixXd::Zero(natoms, 3);
625 for (Index t = 0; t < nthreads; ++t) {
626 force_contribution += force_thread[t];
627 }
628 return force_contribution;
629}
630
632 const std::vector<AtomicReference>& atoms, Index target_atom_index,
633 const Eigen::MatrixXd& density_matrix, const QMMolecule& mol,
634 const AOBasis& full_dftbasis, const Vxc_Grid& grid) {
635 // Linear sum of all four terms -- see this function's own header
636 // comment for why the product-rule decomposition means their sum is
637 // exactly the full derivative, with no additional cross-terms.
638 return GridWeightDerivativeContribution(atoms, target_atom_index,
639 density_matrix, mol, grid) +
641 atoms, target_atom_index, density_matrix, full_dftbasis, grid) +
642 PulayAndTranslationContribution(atoms, target_atom_index,
643 density_matrix, full_dftbasis, grid);
644}
645
646} // namespace xtp
647} // namespace votca
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index AOBasisSize() const
Definition aobasis.h:46
void Fill(const BasisSet &bs, const QMMolecule &atoms)
Definition aobasis.cc:85
const std::vector< Index > & getFuncPerAtom() const
Definition aobasis.h:72
void push_back(const T &atom)
const Eigen::Vector3d & getPos() const
void Load(const std::string &name)
Definition basisset.cc:149
const std::vector< Eigen::Vector3d > & getGridPoints() const
Definition gridbox.h:48
Eigen::MatrixXd ReadFromBigMatrix(const Eigen::MatrixXd &bigmatrix) const
Definition gridbox.cc:84
void AddtoBigMatrix(Eigen::MatrixXd &bigmatrix, const Eigen::MatrixXd &smallmatrix) const
Definition gridbox.cc:71
const std::vector< const AOShell * > & getShells() const
Definition gridbox.h:54
const std::vector< GridboxRange > & getAOranges() const
Definition gridbox.h:58
const std::vector< double > & getGridWeights() const
Definition gridbox.h:50
const std::vector< Index > & getOwnerAtoms() const
Definition gridbox.h:52
AOShell::AOValues CalcAOValues(const Eigen::Vector3d &point) const
Definition gridbox.cc:44
Index size() const
Definition gridbox.h:60
Index Matrixsize() const
Definition gridbox.h:64
static std::vector< AtomicReference > BuildAtomicReferences(const QMMolecule &mol, const std::string &basisset_name, const std::map< std::string, Eigen::MatrixXd > &reference_densities)
static double EvaluateWeight(const std::vector< AtomicReference > &atoms, Index target_atom_index, const Eigen::Vector3d &point)
static double EvaluateAtomicDensity(const AOBasis &atom_basis, const Eigen::MatrixXd &reference_density, const Eigen::Vector3d &point)
static Eigen::MatrixXd WeightFunctionDerivativeContribution(const std::vector< AtomicReference > &atoms, Index target_atom_index, const Eigen::MatrixXd &density_matrix, const AOBasis &full_dftbasis, const Vxc_Grid &grid)
static Eigen::Vector3d EvaluatePointWeightGradient(const std::vector< AtomicReference > &atoms, Index target_atom_index, const Eigen::Vector3d &point)
static Eigen::Vector3d EvaluateWeightGradient(const std::vector< AtomicReference > &atoms, Index target_atom_index, Index differentiate_atom_index, const Eigen::Vector3d &point)
static Eigen::MatrixXd GridWeightDerivativeContribution(const std::vector< AtomicReference > &atoms, Index target_atom_index, const Eigen::MatrixXd &density_matrix, const QMMolecule &mol, const Vxc_Grid &grid)
static Eigen::Vector3d EvaluateAtomicDensityGradient(const AOBasis &atom_basis, const Eigen::MatrixXd &reference_density, const Eigen::Vector3d &point)
static Eigen::MatrixXd BuildWeightMatrix(const std::vector< AtomicReference > &atoms, Index target_atom_index, const AOBasis &full_dftbasis, const Vxc_Grid &grid)
static Eigen::MatrixXd PulayAndTranslationContribution(const std::vector< AtomicReference > &atoms, Index target_atom_index, const Eigen::MatrixXd &density_matrix, const AOBasis &full_dftbasis, const Vxc_Grid &grid)
static Eigen::MatrixXd ComputeCDFTForceContribution(const std::vector< AtomicReference > &atoms, Index target_atom_index, const Eigen::MatrixXd &density_matrix, const QMMolecule &mol, const AOBasis &full_dftbasis, const Vxc_Grid &grid)
container for QM atoms
Definition qmatom.h:37
Index getBoxesSize() const
Definition vxc_grid.h:42
Eigen::MatrixXd CalcInverseAtomDist(const QMMolecule &atoms) const
Definition vxc_grid.cc:125
Index getMaxThreads()
Definition eigen.h:128
Index getThreadId()
Definition eigen.h:143
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
Eigen::MatrixX3d derivatives
Definition aoshell.h:131