votca 2026-dev
Loading...
Searching...
No Matches
libint2_calls.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2024 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// Standard includes
21#include <algorithm>
22#include <chrono>
23#include <numeric>
24
25// Local VOTCA includes
26#include "votca/xtp/ERIs.h"
27#include "votca/xtp/aobasis.h"
28#include "votca/xtp/aomatrix.h"
31
32// include libint last otherwise it overrides eigen
34#define LIBINT2_CONSTEXPR_STATICS 0
35#if defined(__clang__)
36#pragma clang diagnostic push
37#pragma clang diagnostic ignored "-W#warnings"
38#elif defined(__GNUC__)
39#pragma GCC diagnostic push
40#pragma GCC diagnostic ignored "-Warray-bounds"
41#pragma GCC diagnostic ignored "-Wcpp"
42#endif
43#include <libint2.hpp>
44#include <libint2/statics_definition.h>
45#if defined(__clang__)
46#pragma clang diagnostic pop
47#elif defined(__GNUC__)
48#pragma GCC diagnostic pop
49#endif
50
51namespace votca {
52namespace xtp {
53
54std::vector<std::vector<Index>> AOBasis::ComputeShellPairs(
55 double threshold) const {
56
57 Index nthreads = OPENMP::getMaxThreads();
58
59 std::vector<libint2::Shell> shells = GenerateLibintBasis();
60
61 // construct the 2-electron repulsion integrals engine
62 std::vector<libint2::Engine> engines;
63 engines.reserve(nthreads);
64 engines.emplace_back(libint2::Operator::overlap, getMaxNprim(), getMaxL(), 0);
65 for (Index i = 1; i != nthreads; ++i) {
66 engines.push_back(engines[0]);
67 }
68
69 std::vector<std::vector<Index>> pairs(shells.size());
70
71#pragma omp parallel for schedule(dynamic)
72 for (Index s1 = 0; s1 < Index(shells.size()); ++s1) {
73 Index thread_id = OPENMP::getThreadId();
74
75 libint2::Engine& engine = engines[thread_id];
76 const libint2::Engine::target_ptr_vec& buf = engine.results();
77 Index n1 = shells[s1].size();
78
79 for (Index s2 = 0; s2 <= s1; ++s2) {
80 bool on_same_center = (shells[s1].O == shells[s2].O);
81 bool significant = on_same_center;
82 if (!on_same_center) {
83 Index n2 = shells[s2].size();
84 engine.compute(shells[s1], shells[s2]);
85 Eigen::Map<const Eigen::MatrixXd> buf_mat(buf[0], n1, n2);
86 significant = (buf_mat.norm() >= threshold);
87 }
88 if (significant) {
89 pairs[s1].push_back(s2);
90 }
91 }
92 std::sort(pairs[s1].begin(), pairs[s1].end());
93 }
94 return pairs;
95}
96
97using MatrixLibInt =
98 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
99
100template <libint2::Operator obtype,
101 typename OperatorParams =
102 typename libint2::operator_traits<obtype>::oper_params_type>
103std::array<MatrixLibInt, libint2::operator_traits<obtype>::nopers>
105 OperatorParams oparams = OperatorParams()) {
106
107 Index nthreads = OPENMP::getMaxThreads();
108 std::vector<libint2::Shell> shells = aobasis.GenerateLibintBasis();
109
110 std::vector<std::vector<Index>> shellpair_list = aobasis.ComputeShellPairs();
111
112 Index nopers = static_cast<Index>(libint2::operator_traits<obtype>::nopers);
113 std::array<MatrixLibInt, libint2::operator_traits<obtype>::nopers> result;
114 for (MatrixLibInt& r : result) {
115 r = MatrixLibInt::Zero(aobasis.AOBasisSize(), aobasis.AOBasisSize());
116 }
117
118 std::vector<libint2::Engine> engines(nthreads);
119 engines[0] = libint2::Engine(obtype, aobasis.getMaxNprim(),
120 static_cast<int>(aobasis.getMaxL()), 0);
121 engines[0].set_params(oparams);
122 for (Index i = 1; i < nthreads; ++i) {
123 engines[i] = engines[0];
124 }
125
126 std::vector<Index> shell2bf = aobasis.getMapToBasisFunctions();
127
128#pragma omp parallel for schedule(dynamic)
129 for (Index s1 = 0; s1 < aobasis.getNumofShells(); ++s1) {
130 Index thread_id = OPENMP::getThreadId();
131 libint2::Engine& engine = engines[thread_id];
132 const libint2::Engine::target_ptr_vec& buf = engine.results();
133
134 Index bf1 = shell2bf[s1];
135 Index n1 = shells[s1].size();
136
137 for (Index s2 : shellpair_list[s1]) {
138
139 engine.compute(shells[s1], shells[s2]);
140 if (buf[0] == nullptr) {
141 continue; // if all integrals screened out, skip to next shell set
142 }
143 Index bf2 = shell2bf[s2];
144 Index n2 = shells[s2].size();
145 for (unsigned int op = 0; op != nopers; ++op) {
146 Eigen::Map<const MatrixLibInt> buf_mat(buf[op], n1, n2);
147 result[op].block(bf1, bf2, n1, n2) = buf_mat;
148 if (s1 != s2) { // if s1 >= s2, copy {s1,s2} to the corresponding
149 // {s2,s1} block, note the transpose!
150 result[op].block(bf2, bf1, n2, n1) = buf_mat.transpose();
151 }
152 }
153 }
154 }
155 return result;
156}
157
158/***********************************
159 * KINETIC
160 ***********************************/
164
165/***********************************
166 * OVERLAP
167 ***********************************/
171
172Eigen::MatrixXd AOOverlap::singleShellOverlap(const AOShell& shell) const {
173 libint2::Shell::do_enforce_unit_normalization(false);
174 libint2::Operator obtype = libint2::Operator::overlap;
175 try {
176 libint2::Engine engine(obtype, shell.getSize(),
177 static_cast<int>(shell.getL()), 0);
178
179 const libint2::Engine::target_ptr_vec& buf = engine.results();
180
181 libint2::Shell s = shell.LibintShell();
182 engine.compute(s, s);
183
184 Eigen::Map<const MatrixLibInt> buf_mat(buf[0], shell.getNumFunc(),
185 shell.getNumFunc());
186 return buf_mat;
187 } catch (const libint2::Engine::lmax_exceeded& error) {
188 std::ostringstream oss;
189 oss << "\nA libint error occured:\n"
190 << error.what() << "\n"
191 << "\nYou requested a computation for a shell with angular momentum "
192 << error.lmax_requested()
193 << ",\nbut your libint package only supports angular momenta upto "
194 << error.lmax_limit() - 1 << ".\n"
195 << "\nTo fix this error you will need to reinstall libint with "
196 "support\n"
197 "for higher angular momenta. If you installed your own libint it\n"
198 "should be reconfigured and installed with the option "
199 "--with-max-am=<maxAngularMomentum>.\n"
200 "If you installed libint with your OS package manager, you will\n"
201 "need to setup you own libint installation with the \n"
202 "--with-max-am=<maxAngularMomentum> option set."
203 << std::endl;
204 throw std::runtime_error(oss.str());
205 }
206}
207
208/***********************************
209 * DIPOLE
210 ***********************************/
211void AODipole::Fill(const AOBasis& aobasis) {
212 auto results = computeOneBodyIntegrals<libint2::Operator::emultipole1,
213 std::array<libint2::Shell::real_t, 3>>(
214 aobasis, r_);
215
216 for (Index i = 0; i < 3; i++) {
217 aomatrix_[i] = results[1 + i]; // emultipole1 returns: overlap, x-dipole,
218 // y-dipole, z-dipole
219 }
220}
221
222/***********************************
223 * COULOMB
224 ***********************************/
225void AOCoulomb::Fill(const AOBasis& aobasis) {
227}
228
230 Index nthreads = OPENMP::getMaxThreads();
231 std::vector<libint2::Shell> shells = aobasis.GenerateLibintBasis();
232 std::vector<Index> shell2bf = aobasis.getMapToBasisFunctions();
233
234 aomatrix_ =
235 Eigen::MatrixXd::Zero(aobasis.AOBasisSize(), aobasis.AOBasisSize());
236
237 // build engines for each thread
238 std::vector<libint2::Engine> engines(nthreads);
239 engines[0] =
240 libint2::Engine(libint2::Operator::coulomb, aobasis.getMaxNprim(),
241 static_cast<int>(aobasis.getMaxL()), 0);
242 engines[0].set(libint2::BraKet::xs_xs);
243 for (Index i = 1; i != nthreads; ++i) {
244 engines[i] = engines[0];
245 }
246
247#pragma omp parallel for schedule(dynamic)
248 for (Index s1 = 0; s1 < aobasis.getNumofShells(); ++s1) {
249 libint2::Engine& engine = engines[OPENMP::getThreadId()];
250 const libint2::Engine::target_ptr_vec& buf = engine.results();
251
252 Index bf1 = shell2bf[s1];
253 Index n1 = shells[s1].size();
254 // cannot use shellpairs because this is still a two-center integral and
255 // overlap screening would give wrong result
256 for (Index s2 = 0; s2 <= s1; ++s2) {
257
258 engine.compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xs, 0>(
259 shells[s1], libint2::Shell::unit(), shells[s2],
260 libint2::Shell::unit());
261
262 if (buf[0] == nullptr) {
263 continue; // if all integrals screened out, skip to next shell set
264 }
265 Index bf2 = shell2bf[s2];
266 Index n2 = shells[s2].size();
267
268 Eigen::Map<const MatrixLibInt> buf_mat(buf[0], n1, n2);
269 aomatrix_.block(bf1, bf2, n1, n2) = buf_mat;
270 if (s1 != s2) { // if s1 >= s2, copy {s1,s2} to the corresponding
271 // {s2,s1} block, note the transpose!
272 aomatrix_.block(bf2, bf1, n2, n1) = buf_mat.transpose();
273 }
274 }
275 }
276}
277
278Eigen::MatrixXd ERIs::ComputeSchwarzShells(const AOBasis& basis) const {
279
280 Index noshells = basis.getNumofShells();
281
282 Eigen::MatrixXd result = Eigen::MatrixXd::Zero(noshells, noshells);
283 Index nthreads = OPENMP::getMaxThreads();
284 std::vector<libint2::Engine> engines(nthreads);
285 double epsilon = 0.0;
286 engines[0] = libint2::Engine(libint2::Operator::coulomb, basis.getMaxNprim(),
287 static_cast<int>(basis.getMaxL()), 0, epsilon);
288
289 for (Index i = 1; i < nthreads; ++i) {
290 engines[i] = engines[0];
291 }
292
293 std::vector<libint2::Shell> shells = basis.GenerateLibintBasis();
294
295#pragma omp parallel for schedule(dynamic)
296 for (Index s1 = 0l; s1 < basis.getNumofShells(); ++s1) {
297 Index thread_id = OPENMP::getThreadId();
298 libint2::Engine& engine = engines[thread_id];
299 const libint2::Engine::target_ptr_vec& buf = engine.results();
300 Index n1 = shells[s1].size();
301
302 for (Index s2 = 0l; s2 <= s1; ++s2) {
303 Index n2 = shells[s2].size();
304 Index n12 = n1 * n2;
305
306 engines[thread_id]
307 .compute2<libint2::Operator::coulomb, libint2::BraKet::xx_xx, 0>(
308 shells[s1], shells[s2], shells[s1], shells[s2]);
309
310 Eigen::Map<const MatrixLibInt> buf_mat(buf[0], n12, n12);
311
312 result(s2, s1) = std::sqrt(buf_mat.cwiseAbs().maxCoeff());
313 }
314 }
315 return result.selfadjointView<Eigen::Upper>();
316}
317
318template <bool with_exchange>
319std::array<Eigen::MatrixXd, 2> ERIs::Compute4c(const Eigen::MatrixXd& dmat,
320 double error) const {
321 assert(schwarzscreen_.rows() > 0 && schwarzscreen_.cols() > 0 &&
322 "Please call Initialize_4c before running this");
323 Index nthreads = OPENMP::getMaxThreads();
324
325 Eigen::MatrixXd hartree = Eigen::MatrixXd::Zero(dmat.rows(), dmat.cols());
326 Eigen::MatrixXd exchange;
327 if (with_exchange) {
328 exchange = Eigen::MatrixXd::Zero(dmat.rows(), dmat.cols());
329 }
330 Eigen::MatrixXd dnorm_block = ComputeShellBlockNorm(dmat);
331 double fock_precision = error;
332 // engine precision controls primitive truncation, assume worst-case scenario
333 // (all primitive combinations add up constructively)
334 Index max_nprim4 = maxnprim_ * maxnprim_ * maxnprim_ * maxnprim_;
335 double engine_precision = std::min(fock_precision / dnorm_block.maxCoeff(),
336 std::numeric_limits<double>::epsilon()) /
337 double(max_nprim4);
338 std::vector<libint2::Engine> engines(nthreads);
339 engines[0] = libint2::Engine(libint2::Operator::coulomb, int(maxnprim_),
340 int(maxL_), 0);
341 engines[0].set_precision(engine_precision); // shellset-dependent precision
342 // control will likely break
343 // positive definiteness
344 // stick with this simple recipe
345 for (Index i = 1; i < nthreads; ++i) {
346 engines[i] = engines[0];
347 }
348 Index nshells = basis_.size();
349
350#pragma omp parallel for schedule(dynamic) reduction(+ : hartree) \
351 reduction(+ : exchange)
352 for (Index s1 = 0; s1 < nshells; ++s1) {
353 Index thread_id = OPENMP::getThreadId();
354 libint2::Engine& engine = engines[thread_id];
355 const auto& buf = engine.results();
356 Index start_1 = starts_[s1];
357 const libint2::Shell& shell1 = basis_[s1];
358 Index n1 = shell1.size();
359
360 auto sp12_iter = shellpairdata_[s1].begin();
361 for (Index s2 : shellpairs_[s1]) {
362 Index start_2 = starts_[s2];
363 const libint2::Shell& shell2 = basis_[s2];
364 Index n2 = shell2.size();
365 double dnorm_12 = dnorm_block(s1, s2);
366 const libint2::ShellPair* sp12 = &(*sp12_iter);
367 ++sp12_iter;
368
369 for (Index s3 = 0; s3 <= s1; ++s3) {
370
371 Index start_3 = starts_[s3];
372 const libint2::Shell& shell3 = basis_[s3];
373 Index n3 = shell3.size();
374 auto sp34_iter = shellpairdata_[s3].begin();
375 double dnorm_123 = std::max(dnorm_block(s1, s3),
376 std::max(dnorm_block(s2, s3), dnorm_12));
377 Index s4max = (s1 == s3) ? s2 : s3;
378 for (Index s4 : shellpairs_[s3]) {
379 if (s4 > s4max) {
380 break;
381 } // for each s3, s4 are stored in monotonically increasing
382 // order
383
384 const libint2::ShellPair* sp34 = &(*sp34_iter);
385 // must update the iter even if going to skip s4
386 ++sp34_iter;
387 double dnorm_1234 =
388 std::max(dnorm_block(s1, s4),
389 std::max(dnorm_block(s2, s4),
390 std::max(dnorm_block(s3, s4), dnorm_123)));
391
392 if (dnorm_1234 * schwarzscreen_(s1, s2) * schwarzscreen_(s3, s4) <
393 fock_precision) {
394 continue;
395 }
396
397 const libint2::Shell& shell4 = basis_[s4];
398 engine
399 .compute2<libint2::Operator::coulomb, libint2::BraKet::xx_xx, 0>(
400 shell1, shell2, shell3, shell4, sp12, sp34);
401 const auto* buf_1234 = buf[0];
402 if (buf_1234 == nullptr) {
403 continue; // if all integrals screened out, skip to next quartet
404 }
405 Index start_4 = starts_[s4];
406 Index n4 = shell4.size();
407 Index s12_deg = (s1 == s2) ? 1 : 2;
408 Index s34_deg = (s3 == s4) ? 1 : 2;
409 Index s12_34_deg = (s1 == s3) ? (s2 == s4 ? 1 : 2) : 2;
410 Index s1234_deg = s12_deg * s34_deg * s12_34_deg;
411
412 for (Index f1 = 0, f1234 = 0; f1 != n1; ++f1) {
413 const Index bf1 = f1 + start_1;
414 for (Index f2 = 0; f2 != n2; ++f2) {
415 const Index bf2 = f2 + start_2;
416 for (Index f3 = 0; f3 != n3; ++f3) {
417 const Index bf3 = f3 + start_3;
418 for (Index f4 = 0; f4 != n4; ++f4, ++f1234) {
419 const Index bf4 = f4 + start_4;
420
421 const double value = buf_1234[f1234];
422
423 const double value_scal_by_deg = value * double(s1234_deg);
424
425 hartree(bf1, bf2) += dmat(bf3, bf4) * value_scal_by_deg;
426 hartree(bf3, bf4) += dmat(bf1, bf2) * value_scal_by_deg;
427 if (with_exchange) {
428 exchange(bf1, bf3) -= dmat(bf2, bf4) * value_scal_by_deg;
429 exchange(bf2, bf3) -= dmat(bf1, bf4) * value_scal_by_deg;
430 exchange(bf2, bf4) -= dmat(bf1, bf3) * value_scal_by_deg;
431 exchange(bf1, bf4) -= dmat(bf2, bf3) * value_scal_by_deg;
432 }
433 }
434 }
435 }
436 }
437 }
438 }
439 }
440 }
441 std::array<Eigen::MatrixXd, 2> result2;
442 // 0.25=0.5(symmetrisation)*0.5(our dmat has a factor 2)
443 result2[0] = 0.25 * (hartree + hartree.transpose());
444 if (with_exchange) {
445 // prefactor
446 result2[1] = 0.125 * (exchange + exchange.transpose());
447 }
448 return result2;
449}
450
451template std::array<Eigen::MatrixXd, 2> ERIs::Compute4c<true>(
452 const Eigen::MatrixXd& dmat, double error) const;
453template std::array<Eigen::MatrixXd, 2> ERIs::Compute4c<false>(
454 const Eigen::MatrixXd& dmat, double error) const;
455
456std::vector<std::vector<Index>> TCMatrix_dft::SignificantPairs(
457 const AOBasis& dftbasis, const Eigen::MatrixXd& metric,
458 double pair_threshold) const {
459 const Index nshells = dftbasis.getNumofShells();
460 std::vector<std::vector<Index>> kept(nshells);
461 if (pair_threshold <= 0.0) {
462 for (Index a = 0; a < nshells; ++a) {
463 kept[a].resize(a + 1);
464 std::iota(kept[a].begin(), kept[a].end(), Index(0));
465 }
466 return kept;
467 }
468 // |(P|ab)| <= sqrt((P|P)) sqrt((ab|ab))
469 const double max_aux = std::sqrt(metric.diagonal().cwiseAbs().maxCoeff());
470 const Index nthreads = OPENMP::getMaxThreads();
471 std::vector<libint2::Engine> engines(nthreads);
472 engines[0] =
473 libint2::Engine(libint2::Operator::coulomb, dftbasis.getMaxNprim(),
474 static_cast<int>(dftbasis.getMaxL()), 0, 0.0);
475 for (Index i = 1; i < nthreads; ++i) {
476 engines[i] = engines[0];
477 }
478 const std::vector<libint2::Shell> shells = dftbasis.GenerateLibintBasis();
479#pragma omp parallel for schedule(dynamic)
480 for (Index a = nshells - 1; a >= 0; --a) {
481 libint2::Engine& engine = engines[OPENMP::getThreadId()];
482 const libint2::Engine::target_ptr_vec& buf = engine.results();
483 for (Index b = 0; b < a; ++b) {
484 engine.compute2<libint2::Operator::coulomb, libint2::BraKet::xx_xx, 0>(
485 shells[a], shells[b], shells[a], shells[b]);
486 if (buf[0] == nullptr) {
487 continue;
488 }
489 const Index n = Index(shells[a].size() * shells[b].size());
490 const double schwarz =
491 std::sqrt(Eigen::Map<const Eigen::MatrixXd>(buf[0], n, n)
492 .cwiseAbs()
493 .maxCoeff());
494 if (max_aux * schwarz >= pair_threshold) {
495 kept[a].push_back(b);
496 }
497 }
498 kept[a].push_back(a); // diagonal shell pairs are always kept
499 }
500 return kept;
501}
502
503void TCMatrix_dft::Fill(const AOBasis& auxbasis, const AOBasis& dftbasis,
504 double pair_threshold, Index build_columns) {
505 std::vector<std::vector<Index>> kept;
506 {
507 auto metric_start = std::chrono::steady_clock::now();
508 AOCoulomb auxAOcoulomb;
509 auxAOcoulomb.Fill(auxbasis);
510 inv_sqrt_ = auxAOcoulomb.Pseudo_InvSqrt(1e-8);
511 removedfunctions_ = auxAOcoulomb.Removedfunctions();
512 metric_seconds_ = std::chrono::duration<double>(
513 std::chrono::steady_clock::now() - metric_start)
514 .count();
515 kept = SignificantPairs(dftbasis, auxAOcoulomb.Matrix(), pair_threshold);
516 }
517 SetupLayout(dftbasis, kept);
518 const Index naux = auxbasis.AOBasisSize();
519 // every stored entry is written below
520 data_.resize(
521 Index(runs_.empty() ? 0 : runs_.back().offset + runs_.back().length),
522 naux);
523
524 const std::vector<Index> shell2bf = dftbasis.getMapToBasisFunctions();
525 const std::vector<Index> auxshell2bf = auxbasis.getMapToBasisFunctions();
526 const std::vector<libint2::Shell> dftshells = dftbasis.GenerateLibintBasis();
527 const std::vector<libint2::Shell> auxshells = auxbasis.GenerateLibintBasis();
528
529 // Work unit: shell a with the kept shells kept[a][first, last). Its pair
530 // columns are built for all aux functions at once, at most about
531 // build_columns of them, which bounds the scratch memory per thread.
532 struct Unit {
533 Index a;
534 Index first;
535 Index last;
536 };
537 std::vector<Unit> units;
538 Index max_rows = 0;
539 for (Index a = dftbasis.getNumofShells() - 1; a >= 0; --a) {
540 const Index na = Index(dftshells[a].size());
541 Index first = 0;
542 while (first < Index(kept[a].size())) {
543 Index last = first;
544 Index rows = 0;
545 while (last < Index(kept[a].size()) &&
546 (last == first ||
547 rows + na * Index(dftshells[kept[a][last]].size()) <=
548 build_columns)) {
549 rows += na * Index(dftshells[kept[a][last]].size());
550 ++last;
551 }
552 max_rows = std::max(max_rows, rows);
553 units.push_back({a, first, last});
554 first = last;
555 }
556 }
557
558 const Index nthreads = OPENMP::getMaxThreads();
559 std::vector<libint2::Engine> engines(nthreads);
560 engines[0] = libint2::Engine(
561 libint2::Operator::coulomb,
562 std::max(dftbasis.getMaxNprim(), auxbasis.getMaxNprim()),
563 static_cast<int>(std::max(dftbasis.getMaxL(), auxbasis.getMaxL())), 0);
564 engines[0].set(libint2::BraKet::xs_xx);
565 for (Index i = 1; i < nthreads; ++i) {
566 engines[i] = engines[0];
567 }
568
569#pragma omp parallel
570 {
571 libint2::Engine& engine = engines[OPENMP::getThreadId()];
572 const libint2::Engine::target_ptr_vec& buf = engine.results();
573 // rows: pair columns (nu, mu) of the unit, columns: aux functions
574 Eigen::MatrixXd raw(max_rows, naux);
575 Eigen::MatrixXd transformed(max_rows, naux);
576 std::vector<Index> row_start;
577 std::vector<Index> chunk_pos;
578
579#pragma omp for schedule(dynamic)
580 for (Index u = 0; u < Index(units.size()); ++u) {
581 const Unit& unit = units[u];
582 const libint2::Shell& shell_a = dftshells[unit.a];
583 const Index na = Index(shell_a.size());
584 const Index start_a = shell2bf[unit.a];
585 const bool has_diagonal = kept[unit.a][unit.last - 1] == unit.a;
586
587 // position of each kept shell in the unit's part of a column
588 chunk_pos.assign(1, 0);
589 for (Index k = unit.first; k < unit.last; ++k) {
590 chunk_pos.push_back(chunk_pos.back() +
591 Index(dftshells[kept[unit.a][k]].size()));
592 }
593 const Index width = chunk_pos.back();
594 // column mu = start_a + i holds width rows, fewer on the diagonal
595 row_start.assign(1, 0);
596 for (Index i = 0; i < na; ++i) {
597 const Index len = has_diagonal ? width - (na - i - 1) : width;
598 row_start.push_back(row_start.back() + len);
599 }
600 const Index rows = row_start.back();
601 raw.topRows(rows).setZero();
602
603 for (Index aux = 0; aux < Index(auxshells.size()); ++aux) {
604 const libint2::Shell& auxshell = auxshells[aux];
605 const Index aux_start = auxshell2bf[aux];
606 for (Index k = unit.first; k < unit.last; ++k) {
607 const Index b = kept[unit.a][k];
608 const libint2::Shell& shell_b = dftshells[b];
609 engine
610 .compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xx, 0>(
611 auxshell, libint2::Shell::unit(), shell_a, shell_b);
612 if (buf[0] == nullptr) {
613 continue;
614 }
615 Eigen::TensorMap<
616 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
617 result(buf[0], auxshell.size(), shell_a.size(), shell_b.size());
618 const Index nb = Index(shell_b.size());
619 const Index pos = chunk_pos[k - unit.first];
620 for (Index i = 0; i < na; ++i) {
621 // nu <= mu
622 const Index ncol = (b == unit.a) ? std::min(nb, i + 1) : nb;
623 for (Index c = 0; c < ncol; ++c) {
624 for (Index f = 0; f < Index(auxshell.size()); ++f) {
625 raw(row_start[i] + pos + c, aux_start + f) = result(f, i, c);
626 }
627 }
628 }
629 }
630 }
631
632 // B_P = sum_Q V^-1/2_PQ (Q|..)
633 transformed.topRows(rows).noalias() =
634 raw.topRows(rows) * inv_sqrt_.transpose();
635 // unit rows of column mu continue the column after the shells before
636 Index before = 0;
637 for (Index k = 0; k < unit.first; ++k) {
638 before += Index(dftshells[kept[unit.a][k]].size());
639 }
640 for (Index i = 0; i < na; ++i) {
641 const Index len = row_start[i + 1] - row_start[i];
642 data_.block(col_offset_[start_a + i] + before, 0, len, naux) =
643 transformed.middleRows(row_start[i], len);
644 }
645 }
646 }
647}
648
649/*
650 * Determines the 3-center integrals for a given shell in the aux basis
651 * by calculating the 3-center repulsion integral of the functions in the
652 * aux shell with ALL functions in the DFT basis set
653 */
654std::vector<Eigen::MatrixXd> ComputeAO3cBlock(const libint2::Shell& auxshell,
655 const AOBasis& dftbasis,
656 libint2::Engine& engine) {
657 std::vector<Eigen::MatrixXd> ao3c = std::vector<Eigen::MatrixXd>(
658 auxshell.size(),
659 Eigen::MatrixXd::Zero(dftbasis.AOBasisSize(), dftbasis.AOBasisSize()));
660
661 std::vector<libint2::Shell> dftshells = dftbasis.GenerateLibintBasis();
662 std::vector<Index> shell2bf = dftbasis.getMapToBasisFunctions();
663
664 const libint2::Engine::target_ptr_vec& buf = engine.results();
665 // alpha-loop over the "left" DFT basis function
666 for (Index row = 0; row < Index(dftshells.size()); row++) {
667
668 const libint2::Shell& shell_row = dftshells[row];
669 const Index row_start = shell2bf[row];
670 // ThreecMatrix is symmetric, restrict explicit calculation to triangular
671 // matrix
672 for (Index col = 0; col <= row; col++) {
673 const libint2::Shell& shell_col = dftshells[col];
674 const Index col_start = shell2bf[col];
675
676 engine.compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xx, 0>(
677 auxshell, libint2::Shell::unit(), shell_col, shell_row);
678
679 if (buf[0] == nullptr) {
680 continue;
681 }
682 Eigen::TensorMap<Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
683 result(buf[0], auxshell.size(), shell_col.size(), shell_row.size());
684
685 for (size_t aux_c = 0; aux_c < auxshell.size(); aux_c++) {
686 for (size_t col_c = 0; col_c < shell_col.size(); col_c++) {
687 for (size_t row_c = 0; row_c < shell_row.size(); row_c++) {
688 // ao3c is col major result is row-major so this is ideal
689 ao3c[aux_c](row_start + row_c, col_start + col_c) =
690 result(aux_c, col_c, row_c);
691 } // ROW copy
692 } // COL copy
693 } // AUX copy
694
695 } // gamma-loop
696 } // alpha-loop
697
698 for (Eigen::MatrixXd& mat : ao3c) {
699 mat.triangularView<Eigen::Upper>() =
700 mat.triangularView<Eigen::Lower>().transpose();
701 }
702 return ao3c;
703}
704
705namespace {
706// L * A * R evaluated in the cheaper order. For the AO->MO transformation of
707// the three-centre integrals L = C_n^T (n x N), A = (mu nu|P) (N x N) and
708// R = C_m (N x m) with m < n, so A * R first saves a factor of about n/m on
709// the dominant product.
710Eigen::MatrixXd MultiplyLeftRight(const Eigen::MatrixXd& L,
711 const Eigen::MatrixXd& A,
712 const Eigen::MatrixXd& R) {
713 const double left_first =
714 double(L.rows()) * double(A.rows()) * double(A.cols()) +
715 double(L.rows()) * double(A.cols()) * double(R.cols());
716 const double right_first =
717 double(A.rows()) * double(A.cols()) * double(R.cols()) +
718 double(L.rows()) * double(A.rows()) * double(R.cols());
719 if (right_first < left_first) {
720 return L * (A * R);
721 }
722 return (L * A) * R;
723}
724} // namespace
725
726void TCMatrix_gwbse::Fill3cMO(const AOBasis& auxbasis, const AOBasis& dftbasis,
727 const Eigen::MatrixXd& dft_orbitals) {
728
729 const Eigen::MatrixXd dftm = dft_orbitals.middleCols(mmin_, mtotal_);
730 const Eigen::MatrixXd dftn =
731 dft_orbitals.middleCols(nmin_, ntotal_).transpose();
732
733 Index nthreads = OPENMP::getMaxThreads();
734
735 std::vector<libint2::Shell> auxshells = auxbasis.GenerateLibintBasis();
736 std::vector<libint2::Engine> engines(nthreads);
737 engines[0] = libint2::Engine(
738 libint2::Operator::coulomb,
739 std::max(dftbasis.getMaxNprim(), auxbasis.getMaxNprim()),
740 static_cast<int>(std::max(dftbasis.getMaxL(), auxbasis.getMaxL())), 0);
741 engines[0].set(libint2::BraKet::xs_xx);
742 for (Index i = 1; i < nthreads; ++i) {
743 engines[i] = engines[0];
744 }
745 std::vector<Index> auxshell2bf = auxbasis.getMapToBasisFunctions();
746
747#pragma omp parallel
748 {
749 Index threadid = OPENMP::getThreadId();
750#pragma omp for schedule(dynamic)
751 for (Index aux = 0; aux < Index(auxshells.size()); aux++) {
752 const libint2::Shell& auxshell = auxshells[aux];
753
754 std::vector<Eigen::MatrixXd> ao3c =
755 ComputeAO3cBlock(auxshell, dftbasis, engines[threadid]);
756
757 // AO->MO transformation of each function of the aux shell, written
758 // straight into its column of every m-slice. Each aux function is a
759 // different column, so the threads write to disjoint memory.
760 const Index dim = static_cast<Index>(ao3c.size());
761 const Index aux0 = auxshell2bf[aux];
762 for (Index k = 0; k < dim; ++k) {
763 ao3c[k] = MultiplyLeftRight(dftn, ao3c[k], dftm);
764 for (Index m_level = 0; m_level < mtotal_; m_level++) {
765 matrix_[m_level].col(aux0 + k) = ao3c[k].col(m_level);
766 }
767 ao3c[k].resize(0, 0);
768 }
769 } // shells of GW basis set
770 }
771}
772
773// EnvironmentScreening: the two functions that need libint2 engines live
774// here, so that environmentscreening.cc does not include libint2.hpp (a
775// single include of it takes 2-5 GB to compile, depending on the libint2
776// version).
777
779 const AOBasis& auxbasis, const std::vector<Eigen::Vector3d>& points,
780 const std::vector<double>& widths) {
781 if (!widths.empty() && widths.size() != points.size()) {
782 throw std::runtime_error("EnvironmentScreening::AuxFieldAtPoints: " +
783 std::to_string(widths.size()) + " widths for " +
784 std::to_string(points.size()) + " points.");
785 }
786
787 // Same shells, same function offsets, same normalization as
788 // TCMatrix_gwbse::Fill3cMO -- which is what makes the rows of F line up
789 // with the auxiliary index of the stored three-centre integrals.
790 const std::vector<libint2::Shell> shells = auxbasis.GenerateLibintBasis();
791 const std::vector<Index> shell2bf = auxbasis.getMapToBasisFunctions();
792 const Index n_points = Index(points.size());
793
794 Eigen::MatrixXd F =
795 Eigen::MatrixXd::Zero(auxbasis.AOBasisSize(), 3 * n_points);
796 if (n_points == 0 || shells.empty()) {
797 return F;
798 }
799
800 const Index nthreads = OPENMP::getMaxThreads();
801 std::vector<libint2::Engine> engines(nthreads);
802 engines[0] =
803 libint2::Engine(libint2::Operator::nuclear, int(auxbasis.getMaxNprim()),
804 int(auxbasis.getMaxL()), 0);
805 for (Index i = 1; i < nthreads; ++i) {
806 engines[i] = engines[0];
807 }
808 // Smeared sites: (chi_Q | g) with g a single s primitive, as the
809 // auxiliary metric is computed (xs_xs, unit shells on the other side).
810 std::vector<libint2::Engine> coulomb(nthreads);
811 coulomb[0] =
812 libint2::Engine(libint2::Operator::coulomb, int(auxbasis.getMaxNprim()),
813 int(auxbasis.getMaxL()), 0);
814 coulomb[0].set(libint2::BraKet::xs_xs);
815 for (Index i = 1; i < nthreads; ++i) {
816 coulomb[i] = coulomb[0];
817 }
818
819 // Four-point central difference for d/dC, error O(h^4):
820 // f'(C) = [-f(C+2h) + 8 f(C+h) - 8 f(C-h) + f(C-2h)] / (12 h)
821 const double h = 1e-3;
822 const std::array<double, 4> offset = {2.0 * h, h, -h, -2.0 * h};
823 const std::array<double, 4> weight = {-1.0 / (12.0 * h), 8.0 / (12.0 * h),
824 -8.0 / (12.0 * h), 1.0 / (12.0 * h)};
825
826 // Sign. libint2's nuclear operator with a unit charge at C is
827 // -1/|r - C|, so the integral against Shell::unit() is -phi_P(C), minus
828 // the potential of the density chi_P at C (verified against the closed
829 // form for an s-Gaussian). The field is E = -grad_C phi = +grad_C of
830 // that integral, so the stencil is applied to the raw integral as is.
831#pragma omp parallel for schedule(dynamic)
832 for (Index p = 0; p < n_points; ++p) {
833 const double width = widths.empty() ? 0.0 : widths[std::size_t(p)];
834 if (width > 0.0) {
835 // Field of chi_Q averaged over g: E = -grad_C (chi_Q | g_C), with
836 // (chi_Q | g_C) the potential of chi_Q averaged over g (positive
837 // for a positive chi_Q), so the stencil is applied with a minus.
838 // libint2 normalizes the primitive to unit L2 norm, N = (2b/pi)^3/4;
839 // dividing by its charge N (pi/b)^3/2 makes it a unit charge.
840 libint2::Engine& engine = coulomb[OPENMP::getThreadId()];
841 const libint2::Engine::target_ptr_vec& buf = engine.results();
842 const double beta = 1.0 / (width * width);
843 const double charge =
844 std::pow(2.0 * beta / M_PI, 0.75) * std::pow(M_PI / beta, 1.5);
845 for (Index k = 0; k < 3; ++k) {
846 for (std::size_t s = 0; s < offset.size(); ++s) {
847 Eigen::Vector3d C = points[std::size_t(p)];
848 C(k) += offset[s];
849 const libint2::Shell g{
850 {beta}, {{0, false, {1.0}}}, {{C.x(), C.y(), C.z()}}};
851 for (std::size_t sh = 0; sh < shells.size(); ++sh) {
852 engine.compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xs,
853 0>(shells[sh], libint2::Shell::unit(), g,
854 libint2::Shell::unit());
855 if (buf[0] == nullptr) {
856 continue;
857 }
858 const Index start = shell2bf[sh];
859 for (std::size_t f = 0; f < shells[sh].size(); ++f) {
860 F(start + Index(f), 3 * p + k) -= weight[s] * buf[0][f] / charge;
861 }
862 }
863 }
864 }
865 continue;
866 }
867 libint2::Engine& engine = engines[OPENMP::getThreadId()];
868 const libint2::Engine::target_ptr_vec& buf = engine.results();
869 for (Index k = 0; k < 3; ++k) {
870 for (std::size_t s = 0; s < offset.size(); ++s) {
871 Eigen::Vector3d C = points[std::size_t(p)];
872 C(k) += offset[s];
873 std::vector<std::pair<double, std::array<double, 3>>> charge{
874 {1.0, {C.x(), C.y(), C.z()}}};
875 engine.set_params(charge);
876 for (std::size_t sh = 0; sh < shells.size(); ++sh) {
877 engine.compute(shells[sh], libint2::Shell::unit());
878 if (buf[0] == nullptr) {
879 continue; // screened out by libint2: exactly zero
880 }
881 const Index start = shell2bf[sh];
882 for (std::size_t f = 0; f < shells[sh].size(); ++f) {
883 F(start + Index(f), 3 * p + k) += weight[s] * buf[0][f];
884 }
885 }
886 }
887 }
888 }
889 return F;
890}
891
892// Net charge of every auxiliary function, integral chi_Q d^3r: an overlap
893// integral against the unit shell, in the order and normalization of the
894// three-centre integrals (same shells, same offsets).
895Eigen::VectorXd EnvironmentScreening::AuxCharges(const AOBasis& auxbasis) {
896 const std::vector<libint2::Shell> shells = auxbasis.GenerateLibintBasis();
897 const std::vector<Index> shell2bf = auxbasis.getMapToBasisFunctions();
898 Eigen::VectorXd q = Eigen::VectorXd::Zero(auxbasis.AOBasisSize());
899 libint2::Engine engine(libint2::Operator::overlap,
900 int(auxbasis.getMaxNprim()), int(auxbasis.getMaxL()),
901 0);
902 const libint2::Engine::target_ptr_vec& buf = engine.results();
903 for (std::size_t sh = 0; sh < shells.size(); ++sh) {
904 engine.compute(shells[sh], libint2::Shell::unit());
905 if (buf[0] == nullptr) {
906 continue;
907 }
908 for (std::size_t f = 0; f < shells[sh].size(); ++f) {
909 q(shell2bf[sh] + Index(f)) = buf[0][f];
910 }
911 }
912 return q;
913}
914
915} // namespace xtp
916} // namespace votca
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index getMaxL() const
Definition aobasis.cc:29
AOShellIterator begin() const
Definition aobasis.h:49
Index AOBasisSize() const
Definition aobasis.h:46
AOShellIterator end() const
Definition aobasis.h:50
std::vector< Index > getMapToBasisFunctions() const
Definition aobasis.cc:45
Index getNumofShells() const
Definition aobasis.h:56
Index getMaxNprim() const
Definition aobasis.cc:37
std::vector< std::vector< Index > > ComputeShellPairs(double threshold=1e-20) const
std::vector< libint2::Shell > GenerateLibintBasis() const
Definition aobasis.cc:119
void Fill(const AOBasis &aobasis) final
Eigen::MatrixXd aomatrix_
Definition aomatrix.h:82
const Eigen::MatrixXd & Matrix() const
Definition aomatrix.h:72
void computeCoulombIntegrals(const AOBasis &aobasis)
Eigen::MatrixXd Pseudo_InvSqrt(double etol)
Definition aomatrix.cc:88
Index Removedfunctions() const
Definition aomatrix.h:77
std::array< Eigen::MatrixXd, 3 > aomatrix_
Definition aomatrix.h:101
std::array< libint2::Shell::real_t, 3 > r_
Definition aomatrix.h:102
void Fill(const AOBasis &aobasis) final
void Fill(const AOBasis &aobasis) final
Eigen::MatrixXd aomatrix_
Definition aomatrix.h:44
void Fill(const AOBasis &aobasis) final
Eigen::MatrixXd singleShellOverlap(const AOShell &shell) const
Eigen::MatrixXd aomatrix_
Definition aomatrix.h:64
libint2::Shell LibintShell() const
Definition aoshell.cc:65
Index getSize() const
Definition aoshell.h:109
Index getNumFunc() const
Definition aoshell.h:103
std::vector< std::vector< libint2::ShellPair > > shellpairdata_
Definition ERIs.h:95
std::vector< libint2::Shell > basis_
Definition ERIs.h:91
Eigen::MatrixXd ComputeSchwarzShells(const AOBasis &dftbasis) const
std::vector< std::vector< Index > > shellpairs_
Definition ERIs.h:94
std::array< Eigen::MatrixXd, 2 > Compute4c(const Eigen::MatrixXd &dmat, double error) const
Index maxL_
Definition ERIs.h:97
Eigen::MatrixXd schwarzscreen_
Definition ERIs.h:124
Eigen::MatrixXd ComputeShellBlockNorm(const Eigen::MatrixXd &dmat) const
Definition ERIs.cc:70
std::vector< Index > starts_
Definition ERIs.h:92
Index maxnprim_
Definition ERIs.h:96
static Eigen::VectorXd AuxCharges(const AOBasis &auxbasis)
static Eigen::MatrixXd AuxFieldAtPoints(const AOBasis &auxbasis, const std::vector< Eigen::Vector3d > &points, const std::vector< double > &widths={})
F: the electric field at each point produced by each auxiliary basis function, taken as a charge dens...
std::vector< Index > col_offset_
std::vector< Run > runs_
std::vector< std::vector< Index > > SignificantPairs(const AOBasis &dftbasis, const Eigen::MatrixXd &metric, double pair_threshold) const
Index size() const
Number of aux functions.
Definition threecenter.h:82
void Fill(const AOBasis &auxbasis, const AOBasis &dftbasis, double pair_threshold=0.0, Index build_columns=kBuildColumns)
void SetupLayout(const AOBasis &dftbasis, const std::vector< std::vector< Index > > &kept)
Eigen::MatrixXd data_
void Fill3cMO(const AOBasis &auxbasis, const AOBasis &dftbasis, const Eigen::MatrixXd &dft_orbitals)
std::vector< Eigen::MatrixXd > matrix_
Eigen::MatrixXd inv_sqrt_
Definition threecenter.h:57
Index getMaxThreads()
Definition eigen.h:145
Index getThreadId()
Definition eigen.h:160
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > MatrixLibInt
Definition aoecp.cc:56
std::vector< Eigen::MatrixXd > ComputeAO3cBlock(const libint2::Shell &auxshell, const AOBasis &dftbasis, libint2::Engine &engine)
std::array< MatrixLibInt, libint2::operator_traits< obtype >::nopers > computeOneBodyIntegrals(const AOBasis &aobasis, OperatorParams oparams=OperatorParams())
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26