votca 2026-dev
Loading...
Searching...
No Matches
libint2_derivative_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// Nuclear-coordinate derivatives of AO integrals (overlap, kinetic, nuclear
21// attraction, two-centre Coulomb metric, three-centre RI) via libint2 engines
22// with deriv_order = 1, for the analytic DFT gradient. Derivatives are with
23// respect to atomic positions in Bohr.
24//
25// Conventions:
26// - libint2::Engine returns the derivatives with respect to every centre
27// explicitly (buffers ordered by centre, then x, y, z); none is omitted via
28// translational invariance.
29// - Shell-pair buffers are row-major and are mapped with MatrixLibInt, as in
30// libint2_calls.cc. A column-major map is indistinguishable for s shells
31// only.
32// - Each derivative category needs its own libint2 configure option (see the
33// compile guards below); missing support makes the functions throw.
34// - Exceptions inside OpenMP regions are captured and rethrown afterwards.
35
36// Local VOTCA includes
37#include "votca/xtp/aobasis.h"
38#include "votca/xtp/aomatrix.h"
41#include <atomic>
42#include <exception>
43#include <iostream>
44#include <stdexcept>
45#include <string>
46
47// include libint last otherwise it overrides eigen
49#define LIBINT2_CONSTEXPR_STATICS 0
50#if defined(__clang__)
51#pragma clang diagnostic push
52#pragma clang diagnostic ignored "-W#warnings"
53#elif defined(__GNUC__)
54#pragma GCC diagnostic push
55#pragma GCC diagnostic ignored "-Warray-bounds"
56#pragma GCC diagnostic ignored "-Wcpp"
57#endif
58#include <libint2.hpp>
59// <libint2/statics_definition.h> defines libint2's static tables and must be
60// included in exactly one translation unit; libint2_calls.cc does that.
61#if defined(__clang__)
62#pragma clang diagnostic pop
63#elif defined(__GNUC__)
64#pragma GCC diagnostic pop
65#endif
66
67// libint2 releases up to 2.9 (autotools-generated config.h) name the
68// per-category derivative orders INCLUDE_ONEBODY/_ERI2/_ERI3; newer CMake
69// builds use the LIBINT_ prefix. Without this mapping an older libint2 with
70// derivative integrals (e.g. the conda-forge build, 2.9.0, orders 2) is
71// treated as having none, and compute_forces is rejected.
72#if !defined(LIBINT_INCLUDE_ONEBODY) && defined(INCLUDE_ONEBODY)
73#define LIBINT_INCLUDE_ONEBODY INCLUDE_ONEBODY
74#endif
75#if !defined(LIBINT_INCLUDE_ERI2) && defined(INCLUDE_ERI2)
76#define LIBINT_INCLUDE_ERI2 INCLUDE_ERI2
77#endif
78#if !defined(LIBINT_INCLUDE_ERI) && defined(INCLUDE_ERI)
79#define LIBINT_INCLUDE_ERI INCLUDE_ERI
80#endif
81#if !defined(LIBINT_INCLUDE_ERI3) && defined(INCLUDE_ERI3)
82#define LIBINT_INCLUDE_ERI3 INCLUDE_ERI3
83#endif
84
85// The libint2 headers compiled here must be those of the libint2 that CMake
86// found and links (CheckLibintFeatures.cmake passes its version and
87// derivative orders). Another libint2 earlier in the include path (e.g. a
88// Homebrew or conda one next to Boost's headers) would otherwise compile
89// against mismatched headers without any diagnostic.
90#ifdef VOTCA_LIBINT_EXPECT_MAJOR
91#ifdef LIBINT_INCLUDE_ONEBODY
92#define VOTCA_LIBINT_HAVE_ONEBODY LIBINT_INCLUDE_ONEBODY
93#else
94#define VOTCA_LIBINT_HAVE_ONEBODY -1
95#endif
96#ifdef LIBINT_INCLUDE_ERI
97#define VOTCA_LIBINT_HAVE_ERI LIBINT_INCLUDE_ERI
98#else
99#define VOTCA_LIBINT_HAVE_ERI -1
100#endif
101#ifdef LIBINT_INCLUDE_ERI2
102#define VOTCA_LIBINT_HAVE_ERI2 LIBINT_INCLUDE_ERI2
103#else
104#define VOTCA_LIBINT_HAVE_ERI2 -1
105#endif
106#ifdef LIBINT_INCLUDE_ERI3
107#define VOTCA_LIBINT_HAVE_ERI3 LIBINT_INCLUDE_ERI3
108#else
109#define VOTCA_LIBINT_HAVE_ERI3 -1
110#endif
111#if LIBINT_MAJOR_VERSION != VOTCA_LIBINT_EXPECT_MAJOR || \
112 LIBINT_MINOR_VERSION != VOTCA_LIBINT_EXPECT_MINOR || \
113 LIBINT_MICRO_VERSION != VOTCA_LIBINT_EXPECT_MICRO || \
114 VOTCA_LIBINT_HAVE_ONEBODY != VOTCA_LIBINT_EXPECT_ONEBODY || \
115 VOTCA_LIBINT_HAVE_ERI != VOTCA_LIBINT_EXPECT_ERI || \
116 VOTCA_LIBINT_HAVE_ERI2 != VOTCA_LIBINT_EXPECT_ERI2 || \
117 VOTCA_LIBINT_HAVE_ERI3 != VOTCA_LIBINT_EXPECT_ERI3
118#error "The libint2 headers found by the compiler are not those of the libint2 CMake configured (VOTCA_LIBINT_CONFIG_H in CMakeCache.txt). Another libint2 is earlier in the include path; remove it from there or uninstall it."
119#endif
120#endif
121
122namespace votca {
123namespace xtp {
124
125// Defined in libint2_calls.cc, not declared in any header; forward
126// declared here for use by ComputeThreeCenterIntegrals below, to avoid
127// re-deriving the same shell-iteration logic a third time.
128std::vector<Eigen::MatrixXd> ComputeAO3cBlock(const libint2::Shell& auxshell,
129 const AOBasis& dftbasis,
130 libint2::Engine& engine);
131
132// Result type: one entry per atom, each holding the three Cartesian
133// derivative matrices (d/dx, d/dy, d/dz) of the full AO matrix with respect
134// to that atom's nuclear coordinate. Matches the AO matrix convention used
135// elsewhere in this file (dense Eigen::MatrixXd, size AOBasisSize^2).
136using AOMatrixDerivative = std::array<Eigen::MatrixXd, 3>;
137
138// libint2 returns each shell-pair (derivative) buffer in row-major order,
139// n1 rows x n2 columns, as in libint2_calls.cc. Mapping it as column-major
140// transposes every block with p or higher shells; for s shells (1x1 blocks)
141// both agree, so tests on all-s bases cannot detect it.
142using MatrixLibInt =
143 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
144
145namespace {
146// Thrown by the stubs compiled when a derivative category (ONEBODY, ERI2,
147// ERI3; independent libint2 configure options) is unavailable.
148// [[maybe_unused]] because no stub is compiled if all three are supported
149// (-Werror -Wunused-function).
150[[noreturn, maybe_unused]] void ThrowNoDerivativeSupport(
151 const char* function_name, const char* configure_flag) {
152 throw std::runtime_error(
153 std::string(function_name) +
154 ": this libint2 build was compiled without derivative integral "
155 "support for this operator category, so analytic nuclear forces "
156 "are unavailable. Rebuild libint2 with " +
157 configure_flag +
158 " to use this feature. Many packaged libint2 builds (Homebrew, "
159 "Ubuntu apt, etc.) do not enable it; one-body, two-center Coulomb "
160 "metric and three-center RI integrals each have their own "
161 "configure option.");
162}
163} // namespace
164
165// Nuclear-coordinate derivatives of a two-centre one-electron integral
166// matrix for obtype = libint2::Operator::overlap or kinetic (operators with
167// parameters or more centres are not handled).
168//
169// Compile guard: LIBINT2_MAX_DERIV_ORDER is the maximum over all operator
170// categories, so it is not sufficient; a category built without derivatives
171// fails inside engine.compute() (null buffers or an uncatchable crash). The
172// per-category macros LIBINT_INCLUDE_ONEBODY/_ERI2/_ERI3 hold the configured
173// derivative order (LIBINT2_ENABLE_ONEBODY/_ERI2/_ERI3, -1 = off), so their
174// value must be checked, not just whether they are defined: ONEBODY defaults
175// to 0 (energies only), e.g. in the Homebrew formula, which only enables
176// ERI/ERI2/ERI3, while Psi4's default enables ONEBODY but not ERI2/ERI3.
177//
178// ONEBODY: computeOneBodyIntegralDerivatives,
179// ComputeNuclearAttractionDerivatives; ERI2: ComputeCoulombMetricDerivatives;
180// ERI3: the three-centre functions.
181#if defined(LIBINT2_MAX_DERIV_ORDER) && LIBINT2_MAX_DERIV_ORDER >= 1 && \
182 (LIBINT_INCLUDE_ONEBODY >= 1)
183template <libint2::Operator obtype>
184std::vector<AOMatrixDerivative> computeOneBodyIntegralDerivatives(
185 const AOBasis& aobasis) {
186
187 static_assert(
188 libint2::operator_traits<obtype>::nopers == 1,
189 "computeOneBodyIntegralDerivatives only supports single-operator "
190 "types (overlap, kinetic); multipole-type operators with multiple "
191 "result matrices are not handled.");
192
193 // AOBasis has no getNumofAtoms(); getFuncPerAtom() has one entry per atom.
194 Index natoms = static_cast<Index>(aobasis.getFuncPerAtom().size());
195 Index nthreads = OPENMP::getMaxThreads();
196 std::vector<libint2::Shell> shells = aobasis.GenerateLibintBasis();
197 std::vector<std::vector<Index>> shellpair_list = aobasis.ComputeShellPairs();
198 std::vector<Index> shell2bf = aobasis.getMapToBasisFunctions();
199
200 // shell -> atom index, to assign each derivative buffer to its atom
201 std::vector<Index> shell2atom;
202 shell2atom.reserve(shells.size());
203 for (Index s = 0; s < aobasis.getNumofShells(); ++s) {
204 shell2atom.push_back(aobasis.getShell(s).getAtomIndex());
205 }
206
207 Index nbf = aobasis.AOBasisSize();
208 std::vector<AOMatrixDerivative> result(natoms);
209 for (Index a = 0; a < natoms; ++a) {
210 for (Index xyz = 0; xyz < 3; ++xyz) {
211 result[a][xyz] = Eigen::MatrixXd::Zero(nbf, nbf);
212 }
213 }
214
215 std::vector<libint2::Engine> engines(nthreads);
216 // Same as computeOneBodyIntegrals(), but with deriv_order = 1.
217 engines[0] = libint2::Engine(obtype, aobasis.getMaxNprim(),
218 static_cast<int>(aobasis.getMaxL()), 1);
219 for (Index i = 1; i < nthreads; ++i) {
220 engines[i] = engines[0];
221 }
222
223 std::exception_ptr eptr = nullptr;
224 std::atomic<bool> any_nonnull_buffer{false};
225#pragma omp parallel for schedule(dynamic)
226 for (Index s1 = 0; s1 < aobasis.getNumofShells(); ++s1) {
227 try {
228 Index thread_id = OPENMP::getThreadId();
229 libint2::Engine& engine = engines[thread_id];
230 const libint2::Engine::target_ptr_vec& buf = engine.results();
231
232 Index bf1 = shell2bf[s1];
233 Index n1 = shells[s1].size();
234 Index atom1 = shell2atom[s1];
235
236 for (Index s2 : shellpair_list[s1]) {
237
238 engine.compute(shells[s1], shells[s2]);
239 // buf[0..2]: derivatives w.r.t. the centre of s1, buf[3..5]: w.r.t.
240 // the centre of s2. Both must be checked before dereferencing, since
241 // buf.size() reports 6 even if buf[3..5] are unpopulated.
242 if (buf[0] == nullptr || buf[3] == nullptr) {
243 continue; // screened out, or no derivative support (checked
244 // via any_nonnull_buffer after the loop)
245 }
246 any_nonnull_buffer.store(true, std::memory_order_relaxed);
247
248 Index bf2 = shell2bf[s2];
249 Index n2 = shells[s2].size();
250 Index atom2 = shell2atom[s2];
251
252 for (Index xyz = 0; xyz < 3; ++xyz) {
253 Eigen::Map<const MatrixLibInt> buf_mat1(buf[xyz], n1, n2);
254 result[atom1][xyz].block(bf1, bf2, n1, n2) += buf_mat1;
255 if (s1 != s2) {
256 result[atom1][xyz].block(bf2, bf1, n2, n1) += buf_mat1.transpose();
257 }
258
259 Eigen::Map<const MatrixLibInt> buf_mat2(buf[3 + xyz], n1, n2);
260 result[atom2][xyz].block(bf1, bf2, n1, n2) += buf_mat2;
261 if (s1 != s2) {
262 result[atom2][xyz].block(bf2, bf1, n2, n1) += buf_mat2.transpose();
263 }
264 }
265 }
266 } catch (...) {
267 // An exception escaping an OpenMP thread is undefined behaviour;
268 // keep the first one and rethrow after the parallel region.
269#pragma omp critical
270 {
271 if (!eptr) {
272 eptr = std::current_exception();
273 }
274 }
275 }
276 }
277 if (eptr) {
278 std::rethrow_exception(eptr);
279 }
280 if (!any_nonnull_buffer.load() && aobasis.getNumofShells() > 0) {
281 // Runtime counterpart of the compile guard: obtype never produced a
282 // non-null buffer.
283 throw std::runtime_error(
284 "computeOneBodyIntegralDerivatives: engine.results() returned a "
285 "null buffer for every shell pair; this libint2 build does not "
286 "support this operator's derivative integrals at runtime, even "
287 "though it may report LIBINT2_MAX_DERIV_ORDER >= 1 for other "
288 "operators. Rebuild libint2 with --enable-1body=1 to use this "
289 "feature.");
290 }
291 return result;
292}
293
294// Free-function counterparts of AOOverlap::Fill / AOKinetic::Fill.
295std::vector<AOMatrixDerivative> ComputeOverlapDerivatives(
296 const AOBasis& aobasis) {
297 return computeOneBodyIntegralDerivatives<libint2::Operator::overlap>(aobasis);
298}
299
300std::vector<AOMatrixDerivative> ComputeKineticDerivatives(
301 const AOBasis& aobasis) {
302 return computeOneBodyIntegralDerivatives<libint2::Operator::kinetic>(aobasis);
303}
304#else // !(LIBINT_INCLUDE_ONEBODY)
305// Stubs for overlap and kinetic without ONEBODY derivative support; ERI2 and
306// ERI3 are guarded separately below.
307std::vector<AOMatrixDerivative> ComputeOverlapDerivatives(const AOBasis&) {
308 ThrowNoDerivativeSupport("ComputeOverlapDerivatives", "--enable-1body=1");
309}
310std::vector<AOMatrixDerivative> ComputeKineticDerivatives(const AOBasis&) {
311 ThrowNoDerivativeSupport("ComputeKineticDerivatives", "--enable-1body=1");
312}
313#endif // LIBINT_INCLUDE_ONEBODY
314
315// ===========================================================================
316// Two-centre Coulomb metric derivatives d(P|Q)/dR for the RI-J/RI-K
317// gradient, dE_J/dR = sum_P c_P d(d_P)/dR - 1/2 sum_PQ c_P c_Q d(V_PQ)/dR.
318//
319// As in AOCoulomb::computeCoulombIntegrals, (P|Q) is computed as (P 1|Q 1)
320// with unit shells (BraKet::xs_xs), which have no centre; libint2 returns 6
321// buffers, the derivatives w.r.t. the centres of P and Q.
322//
323// Requires LIBINT_INCLUDE_ERI2 >= 1 (libint2 --enable-eri2), independent of
324// ONEBODY and ERI3.
325#if (LIBINT_INCLUDE_ERI2 >= 1)
326std::vector<AOMatrixDerivative> ComputeCoulombMetricDerivatives(
327 const AOBasis& aobasis) {
328 Index natoms = static_cast<Index>(aobasis.getFuncPerAtom().size());
329 Index nthreads = OPENMP::getMaxThreads();
330 std::vector<libint2::Shell> shells = aobasis.GenerateLibintBasis();
331 std::vector<Index> shell2bf = aobasis.getMapToBasisFunctions();
332
333 std::vector<Index> shell2atom;
334 shell2atom.reserve(aobasis.getNumofShells());
335 for (Index s = 0; s < aobasis.getNumofShells(); ++s) {
336 shell2atom.push_back(aobasis.getShell(s).getAtomIndex());
337 }
338
339 Index nbf = aobasis.AOBasisSize();
340 std::vector<AOMatrixDerivative> result(natoms);
341 for (Index a = 0; a < natoms; ++a) {
342 for (Index xyz = 0; xyz < 3; ++xyz) {
343 result[a][xyz] = Eigen::MatrixXd::Zero(nbf, nbf);
344 }
345 }
346
347 std::vector<libint2::Engine> engines(nthreads);
348 engines[0] =
349 libint2::Engine(libint2::Operator::coulomb, aobasis.getMaxNprim(),
350 static_cast<int>(aobasis.getMaxL()), 1);
351 engines[0].set(libint2::BraKet::xs_xs);
352 for (Index i = 1; i < nthreads; ++i) {
353 engines[i] = engines[0];
354 }
355
356 std::exception_ptr eptr_coulmetric = nullptr;
357 std::atomic<bool> any_nonnull_buffer_coulmetric{false};
358#pragma omp parallel for schedule(dynamic)
359 for (Index s1 = 0; s1 < aobasis.getNumofShells(); ++s1) {
360 try {
361 libint2::Engine& engine = engines[OPENMP::getThreadId()];
362 const libint2::Engine::target_ptr_vec& buf = engine.results();
363
364 Index bf1 = shell2bf[s1];
365 Index n1 = shells[s1].size();
366 Index atom1 = shell2atom[s1];
367
368 // Same note as AOCoulomb::computeCoulombIntegrals: cannot use
369 // shellpairs here, this is a two-center integral and overlap
370 // screening would give the wrong result.
371 for (Index s2 = 0; s2 <= s1; ++s2) {
372 engine.compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xs, 1>(
373 shells[s1], libint2::Shell::unit(), shells[s2],
374 libint2::Shell::unit());
375
376 // see computeOneBodyIntegralDerivatives
377 if (buf[0] == nullptr || buf[3] == nullptr) {
378 continue;
379 }
380 any_nonnull_buffer_coulmetric.store(true, std::memory_order_relaxed);
381
382 Index bf2 = shell2bf[s2];
383 Index n2 = shells[s2].size();
384 Index atom2 = shell2atom[s2];
385
386 for (Index xyz = 0; xyz < 3; ++xyz) {
387 Eigen::Map<const MatrixLibInt> buf_mat1(buf[xyz], n1, n2);
388 result[atom1][xyz].block(bf1, bf2, n1, n2) += buf_mat1;
389 if (s1 != s2) {
390 result[atom1][xyz].block(bf2, bf1, n2, n1) += buf_mat1.transpose();
391 }
392
393 Eigen::Map<const MatrixLibInt> buf_mat2(buf[3 + xyz], n1, n2);
394 result[atom2][xyz].block(bf1, bf2, n1, n2) += buf_mat2;
395 if (s1 != s2) {
396 result[atom2][xyz].block(bf2, bf1, n2, n1) += buf_mat2.transpose();
397 }
398 }
399 }
400 } catch (...) {
401#pragma omp critical
402 {
403 if (!eptr_coulmetric) {
404 eptr_coulmetric = std::current_exception();
405 }
406 }
407 }
408 }
409 if (eptr_coulmetric) {
410 std::rethrow_exception(eptr_coulmetric);
411 }
412 if (!any_nonnull_buffer_coulmetric.load() && aobasis.getNumofShells() > 0) {
413 throw std::runtime_error(
414 "ComputeCoulombMetricDerivatives: engine.results() returned a "
415 "null buffer for every shell pair; this libint2 build does not "
416 "support this operator's derivative integrals at runtime, even "
417 "though it may report LIBINT2_MAX_DERIV_ORDER >= 1 for other "
418 "operators. Rebuild libint2 with --enable-eri2=1 to use this "
419 "feature.");
420 }
421 return result;
422}
423#else // !(LIBINT_INCLUDE_ERI2)
424std::vector<AOMatrixDerivative> ComputeCoulombMetricDerivatives(
425 const AOBasis&) {
426 ThrowNoDerivativeSupport("ComputeCoulombMetricDerivatives",
427 "--enable-eri2=1");
428}
429#endif // LIBINT_INCLUDE_ERI2
430
431// ===========================================================================
432// Three-centre RI integral derivatives d(mu,nu|P)/dR. Same shell order as
433// ComputeAO3cBlock (auxshell, unit, shell_col, shell_row, BraKet::xs_xx);
434// libint2 returns 9 buffers, the derivatives w.r.t. the centres of the aux,
435// col and row shells, each x, y, z.
436//
437// ComputeThreeCenterDerivatives stores the full tensor,
438// O(natoms * 3 * Naux * Nao^2) memory, and is only meant for tests; the
439// gradients use the contracted variants below.
440//
441// Requires LIBINT_INCLUDE_ERI3 >= 1 (libint2 --enable-eri3), independent of
442// ONEBODY and ERI2.
443// ===========================================================================
444
445// One entry per Cartesian direction (x,y,z); each holds one matrix per
446// aux basis function (indexed by absolute aux AO index), each matrix
447// being (dftbasis size x dftbasis size).
448using ThreeCenterDerivative = std::array<std::vector<Eigen::MatrixXd>, 3>;
449
450#if (LIBINT_INCLUDE_ERI3 >= 1)
451std::vector<ThreeCenterDerivative> ComputeThreeCenterDerivatives(
452 const AOBasis& auxbasis, const AOBasis& dftbasis) {
453 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
454 // assumes auxbasis and dftbasis are defined on the same molecule
455
456 Index nthreads = OPENMP::getMaxThreads();
457 std::vector<libint2::Shell> dftshells = dftbasis.GenerateLibintBasis();
458 std::vector<libint2::Shell> auxshells = auxbasis.GenerateLibintBasis();
459 std::vector<Index> shell2bf = dftbasis.getMapToBasisFunctions();
460 std::vector<Index> auxshell2bf = auxbasis.getMapToBasisFunctions();
461
462 std::vector<Index> dftshell2atom;
463 dftshell2atom.reserve(dftbasis.getNumofShells());
464 for (Index s = 0; s < dftbasis.getNumofShells(); ++s) {
465 dftshell2atom.push_back(dftbasis.getShell(s).getAtomIndex());
466 }
467 std::vector<Index> auxshell2atom;
468 auxshell2atom.reserve(auxbasis.getNumofShells());
469 for (Index s = 0; s < auxbasis.getNumofShells(); ++s) {
470 auxshell2atom.push_back(auxbasis.getShell(s).getAtomIndex());
471 }
472
473 Index n_dft_bf = dftbasis.AOBasisSize();
474 Index n_aux_bf = auxbasis.AOBasisSize();
475
476 std::vector<ThreeCenterDerivative> result(natoms);
477 for (Index a = 0; a < natoms; ++a) {
478 for (Index xyz = 0; xyz < 3; ++xyz) {
479 result[a][xyz] = std::vector<Eigen::MatrixXd>(
480 n_aux_bf, Eigen::MatrixXd::Zero(n_dft_bf, n_dft_bf));
481 }
482 }
483
484 std::vector<libint2::Engine> engines(nthreads);
485 engines[0] = libint2::Engine(
486 libint2::Operator::coulomb,
487 std::max(dftbasis.getMaxNprim(), auxbasis.getMaxNprim()),
488 static_cast<int>(std::max(dftbasis.getMaxL(), auxbasis.getMaxL())), 1);
489 engines[0].set(libint2::BraKet::xs_xx);
490 for (Index i = 1; i < nthreads; ++i) {
491 engines[i] = engines[0];
492 }
493
494 std::exception_ptr eptr_3c = nullptr;
495 std::atomic<bool> any_nonnull_buffer_3c{false};
496#pragma omp parallel for schedule(dynamic)
497 for (Index aux = 0; aux < auxbasis.getNumofShells(); ++aux) {
498 try {
499 libint2::Engine& engine = engines[OPENMP::getThreadId()];
500 const libint2::Engine::target_ptr_vec& buf = engine.results();
501
502 const libint2::Shell& auxshell = auxshells[aux];
503 Index aux_start = auxshell2bf[aux];
504 Index atom_aux = auxshell2atom[aux];
505
506 for (Index row = 0; row < dftbasis.getNumofShells(); ++row) {
507 const libint2::Shell& shell_row = dftshells[row];
508 Index row_start = shell2bf[row];
509 Index atom_row = dftshell2atom[row];
510
511 // Full col loop: the mu,nu symmetry of (mu,nu|P) is not exploited
512 // here, unlike in ComputeAO3cBlock.
513 for (Index col = 0; col < dftbasis.getNumofShells(); ++col) {
514 const libint2::Shell& shell_col = dftshells[col];
515 Index col_start = shell2bf[col];
516 Index atom_col = dftshell2atom[col];
517
518 engine
519 .compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xx, 1>(
520 auxshell, libint2::Shell::unit(), shell_col, shell_row);
521
522 // see computeOneBodyIntegralDerivatives; three centres here
523 if (buf[0] == nullptr || buf[3] == nullptr || buf[6] == nullptr) {
524 continue;
525 }
526 any_nonnull_buffer_3c.store(true, std::memory_order_relaxed);
527
528 // buffers: [aux centre xyz][col centre xyz][row centre xyz], in
529 // the shell argument order of compute2
530 for (Index xyz = 0; xyz < 3; ++xyz) {
531 Eigen::TensorMap<
532 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
533 result_aux(buf[xyz], auxshell.size(), shell_col.size(),
534 shell_row.size());
535 Eigen::TensorMap<
536 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
537 result_col(buf[3 + xyz], auxshell.size(), shell_col.size(),
538 shell_row.size());
539 Eigen::TensorMap<
540 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
541 result_row(buf[6 + xyz], auxshell.size(), shell_col.size(),
542 shell_row.size());
543
544 for (size_t aux_c = 0; aux_c < auxshell.size(); ++aux_c) {
545 Index global_aux = aux_start + static_cast<Index>(aux_c);
546 for (size_t col_c = 0; col_c < shell_col.size(); ++col_c) {
547 for (size_t row_c = 0; row_c < shell_row.size(); ++row_c) {
548 double val_aux = result_aux(aux_c, col_c, row_c);
549 double val_col = result_col(aux_c, col_c, row_c);
550 double val_row = result_row(aux_c, col_c, row_c);
551 Index r = row_start + static_cast<Index>(row_c);
552 Index c = col_start + static_cast<Index>(col_c);
553 result[atom_aux][xyz][global_aux](r, c) += val_aux;
554 result[atom_col][xyz][global_aux](r, c) += val_col;
555 result[atom_row][xyz][global_aux](r, c) += val_row;
556 }
557 }
558 }
559 }
560 }
561 }
562 } catch (...) {
563#pragma omp critical
564 {
565 if (!eptr_3c) {
566 eptr_3c = std::current_exception();
567 }
568 }
569 }
570 }
571 if (eptr_3c) {
572 std::rethrow_exception(eptr_3c);
573 }
574 if (!any_nonnull_buffer_3c.load() && auxbasis.getNumofShells() > 0 &&
575 dftbasis.getNumofShells() > 0) {
576 throw std::runtime_error(
577 "ComputeThreeCenterDerivatives: engine.results() returned a null "
578 "buffer for every shell triple; this libint2 build does not "
579 "support this operator's derivative integrals at runtime, even "
580 "though it may report LIBINT2_MAX_DERIV_ORDER >= 1 for other "
581 "operators. Rebuild libint2 with --enable-eri3=1 to use this "
582 "feature.");
583 }
584 return result;
585}
586#else // !(LIBINT_INCLUDE_ERI3)
587std::vector<ThreeCenterDerivative> ComputeThreeCenterDerivatives(
588 const AOBasis&, const AOBasis&) {
589 ThrowNoDerivativeSupport("ComputeThreeCenterDerivatives", "--enable-eri3=1");
590}
591#endif // LIBINT_INCLUDE_ERI3
592
593#if (LIBINT_INCLUDE_ERI3 >= 1)
594// Density-contracted three-centre derivatives for DFTGradient::RIJGradient:
595// result[a](xyz, P) = sum_{mu,nu} D_{mu nu} d(mu,nu|P)/dR_a,xyz.
596// Each shell triple is contracted as soon as it is computed, so memory is
597// O(natoms * 3 * Naux) instead of the O(natoms * 3 * Naux * Nao^2) of
598// ComputeThreeCenterDerivatives (e.g. 53 atoms, 943 AOs, 2320 aux functions
599// would need ~2.6 PB). Loop structure and buffer layout as in
600// ComputeThreeCenterDerivatives.
601std::vector<Eigen::MatrixXd> ComputeThreeCenterDerivativeContraction(
602 const AOBasis& auxbasis, const AOBasis& dftbasis,
603 const Eigen::MatrixXd& density) {
604 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
605
606 Index nthreads = OPENMP::getMaxThreads();
607 std::vector<libint2::Shell> dftshells = dftbasis.GenerateLibintBasis();
608 std::vector<libint2::Shell> auxshells = auxbasis.GenerateLibintBasis();
609 std::vector<Index> shell2bf = dftbasis.getMapToBasisFunctions();
610 std::vector<Index> auxshell2bf = auxbasis.getMapToBasisFunctions();
611
612 std::vector<Index> dftshell2atom;
613 dftshell2atom.reserve(dftbasis.getNumofShells());
614 for (Index s = 0; s < dftbasis.getNumofShells(); ++s) {
615 dftshell2atom.push_back(dftbasis.getShell(s).getAtomIndex());
616 }
617 std::vector<Index> auxshell2atom;
618 auxshell2atom.reserve(auxbasis.getNumofShells());
619 for (Index s = 0; s < auxbasis.getNumofShells(); ++s) {
620 auxshell2atom.push_back(auxbasis.getShell(s).getAtomIndex());
621 }
622
623 Index n_aux_bf = auxbasis.AOBasisSize();
624
625 // result[a]: (3, n_aux_bf), rows xyz, columns aux function p
626 std::vector<Eigen::MatrixXd> result(natoms,
627 Eigen::MatrixXd::Zero(3, n_aux_bf));
628
629 std::vector<libint2::Engine> engines(nthreads);
630 engines[0] = libint2::Engine(
631 libint2::Operator::coulomb,
632 std::max(dftbasis.getMaxNprim(), auxbasis.getMaxNprim()),
633 static_cast<int>(std::max(dftbasis.getMaxL(), auxbasis.getMaxL())), 1);
634 engines[0].set(libint2::BraKet::xs_xx);
635 for (Index i = 1; i < nthreads; ++i) {
636 engines[i] = engines[0];
637 }
638
639 // No synchronization needed: the loop is parallel over aux shells, so
640 // different threads write to different columns (global_aux) of
641 // result[atom].
642 std::exception_ptr eptr_3c = nullptr;
643 std::atomic<bool> any_nonnull_buffer_3c{false};
644#pragma omp parallel for schedule(dynamic)
645 for (Index aux = 0; aux < auxbasis.getNumofShells(); ++aux) {
646 try {
647 libint2::Engine& engine = engines[OPENMP::getThreadId()];
648 const libint2::Engine::target_ptr_vec& buf = engine.results();
649
650 const libint2::Shell& auxshell = auxshells[aux];
651 Index aux_start = auxshell2bf[aux];
652 Index atom_aux = auxshell2atom[aux];
653
654 for (Index row = 0; row < dftbasis.getNumofShells(); ++row) {
655 const libint2::Shell& shell_row = dftshells[row];
656 Index row_start = shell2bf[row];
657 Index atom_row = dftshell2atom[row];
658
659 for (Index col = 0; col < dftbasis.getNumofShells(); ++col) {
660 const libint2::Shell& shell_col = dftshells[col];
661 Index col_start = shell2bf[col];
662 Index atom_col = dftshell2atom[col];
663
664 engine
665 .compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xx, 1>(
666 auxshell, libint2::Shell::unit(), shell_col, shell_row);
667
668 if (buf[0] == nullptr || buf[3] == nullptr || buf[6] == nullptr) {
669 continue;
670 }
671 any_nonnull_buffer_3c.store(true, std::memory_order_relaxed);
672
673 for (Index xyz = 0; xyz < 3; ++xyz) {
674 Eigen::TensorMap<
675 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
676 result_aux(buf[xyz], auxshell.size(), shell_col.size(),
677 shell_row.size());
678 Eigen::TensorMap<
679 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
680 result_col(buf[3 + xyz], auxshell.size(), shell_col.size(),
681 shell_row.size());
682 Eigen::TensorMap<
683 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
684 result_row(buf[6 + xyz], auxshell.size(), shell_col.size(),
685 shell_row.size());
686
687 for (size_t aux_c = 0; aux_c < auxshell.size(); ++aux_c) {
688 Index global_aux = aux_start + static_cast<Index>(aux_c);
689 for (size_t col_c = 0; col_c < shell_col.size(); ++col_c) {
690 for (size_t row_c = 0; row_c < shell_row.size(); ++row_c) {
691 double val_aux = result_aux(aux_c, col_c, row_c);
692 double val_col = result_col(aux_c, col_c, row_c);
693 double val_row = result_row(aux_c, col_c, row_c);
694 Index r = row_start + static_cast<Index>(row_c);
695 Index c = col_start + static_cast<Index>(col_c);
696 // contract with the density immediately
697 double d_rc = density(r, c);
698 result[atom_aux](xyz, global_aux) += d_rc * val_aux;
699 result[atom_col](xyz, global_aux) += d_rc * val_col;
700 result[atom_row](xyz, global_aux) += d_rc * val_row;
701 }
702 }
703 }
704 }
705 }
706 }
707 } catch (...) {
708#pragma omp critical
709 {
710 if (!eptr_3c) {
711 eptr_3c = std::current_exception();
712 }
713 }
714 }
715 }
716 if (eptr_3c) {
717 std::rethrow_exception(eptr_3c);
718 }
719 if (!any_nonnull_buffer_3c.load() && auxbasis.getNumofShells() > 0 &&
720 dftbasis.getNumofShells() > 0) {
721 throw std::runtime_error(
722 "ComputeThreeCenterDerivativeContraction: engine.results() "
723 "returned a null buffer for every shell triple; this libint2 "
724 "build does not support this operator's derivative integrals at "
725 "runtime, even though it may report LIBINT2_MAX_DERIV_ORDER >= 1 "
726 "for other operators. Rebuild libint2 with --enable-eri3=1 to "
727 "use this feature.");
728 }
729 return result;
730}
731#else // !(LIBINT_INCLUDE_ERI3)
732std::vector<Eigen::MatrixXd> ComputeThreeCenterDerivativeContraction(
733 const AOBasis&, const AOBasis&, const Eigen::MatrixXd&) {
734 ThrowNoDerivativeSupport("ComputeThreeCenterDerivativeContraction",
735 "--enable-eri3=1");
736}
737#endif // LIBINT_INCLUDE_ERI3
738
739#if (LIBINT_INCLUDE_ERI3 >= 1)
740// Full (Nao x Nao) three-centre derivative matrices for one atom only,
741// (3, n_aux_bf) of them. Shell triples with no shell on target_atom are
742// skipped, but a loop over all atoms still evaluates each triple up to
743// three times. Superseded for RIKGradient by
744// ComputeThreeCenterDerivativesMOTransformed, which needs a single pass.
746 const AOBasis& auxbasis, const AOBasis& dftbasis, Index target_atom) {
747 std::vector<libint2::Shell> dftshells = dftbasis.GenerateLibintBasis();
748 std::vector<libint2::Shell> auxshells = auxbasis.GenerateLibintBasis();
749 std::vector<Index> shell2bf = dftbasis.getMapToBasisFunctions();
750 std::vector<Index> auxshell2bf = auxbasis.getMapToBasisFunctions();
751
752 std::vector<Index> dftshell2atom;
753 dftshell2atom.reserve(dftbasis.getNumofShells());
754 for (Index s = 0; s < dftbasis.getNumofShells(); ++s) {
755 dftshell2atom.push_back(dftbasis.getShell(s).getAtomIndex());
756 }
757 std::vector<Index> auxshell2atom;
758 auxshell2atom.reserve(auxbasis.getNumofShells());
759 for (Index s = 0; s < auxbasis.getNumofShells(); ++s) {
760 auxshell2atom.push_back(auxbasis.getShell(s).getAtomIndex());
761 }
762
763 Index n_dft_bf = dftbasis.AOBasisSize();
764 Index n_aux_bf = auxbasis.AOBasisSize();
765 Index nthreads = OPENMP::getMaxThreads();
766
767 // (3, n_aux_bf) matrices of size nao x nao for target_atom only
769 for (Index xyz = 0; xyz < 3; ++xyz) {
770 result[xyz] = std::vector<Eigen::MatrixXd>(
771 n_aux_bf, Eigen::MatrixXd::Zero(n_dft_bf, n_dft_bf));
772 }
773
774 std::vector<libint2::Engine> engines(nthreads);
775 engines[0] = libint2::Engine(
776 libint2::Operator::coulomb,
777 std::max(dftbasis.getMaxNprim(), auxbasis.getMaxNprim()),
778 static_cast<int>(std::max(dftbasis.getMaxL(), auxbasis.getMaxL())), 1);
779 engines[0].set(libint2::BraKet::xs_xx);
780 for (Index i = 1; i < nthreads; ++i) {
781 engines[i] = engines[0];
782 }
783
784 std::exception_ptr eptr_3c = nullptr;
785 std::atomic<bool> any_nonnull_buffer_3c{false};
786#pragma omp parallel for schedule(dynamic)
787 for (Index aux = 0; aux < auxbasis.getNumofShells(); ++aux) {
788 try {
789 libint2::Engine& engine = engines[OPENMP::getThreadId()];
790 const libint2::Engine::target_ptr_vec& buf = engine.results();
791
792 const libint2::Shell& auxshell = auxshells[aux];
793 Index aux_start = auxshell2bf[aux];
794 Index atom_aux = auxshell2atom[aux];
795
796 for (Index row = 0; row < dftbasis.getNumofShells(); ++row) {
797 const libint2::Shell& shell_row = dftshells[row];
798 Index row_start = shell2bf[row];
799 Index atom_row = dftshell2atom[row];
800
801 for (Index col = 0; col < dftbasis.getNumofShells(); ++col) {
802 const libint2::Shell& shell_col = dftshells[col];
803 Index col_start = shell2bf[col];
804 Index atom_col = dftshell2atom[col];
805
806 // no shell on target_atom: no contribution
807 if (atom_aux != target_atom && atom_col != target_atom &&
808 atom_row != target_atom) {
809 continue;
810 }
811
812 engine
813 .compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xx, 1>(
814 auxshell, libint2::Shell::unit(), shell_col, shell_row);
815
816 if (buf[0] == nullptr || buf[3] == nullptr || buf[6] == nullptr) {
817 continue;
818 }
819 any_nonnull_buffer_3c.store(true, std::memory_order_relaxed);
820
821 for (Index xyz = 0; xyz < 3; ++xyz) {
822 Eigen::TensorMap<
823 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
824 result_aux(buf[xyz], auxshell.size(), shell_col.size(),
825 shell_row.size());
826 Eigen::TensorMap<
827 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
828 result_col(buf[3 + xyz], auxshell.size(), shell_col.size(),
829 shell_row.size());
830 Eigen::TensorMap<
831 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
832 result_row(buf[6 + xyz], auxshell.size(), shell_col.size(),
833 shell_row.size());
834
835 for (size_t aux_c = 0; aux_c < auxshell.size(); ++aux_c) {
836 Index global_aux = aux_start + static_cast<Index>(aux_c);
837 for (size_t col_c = 0; col_c < shell_col.size(); ++col_c) {
838 for (size_t row_c = 0; row_c < shell_row.size(); ++row_c) {
839 Index r = row_start + static_cast<Index>(row_c);
840 Index c = col_start + static_cast<Index>(col_c);
841 // only the centres located on target_atom contribute
842 if (atom_aux == target_atom) {
843 result[xyz][global_aux](r, c) +=
844 result_aux(aux_c, col_c, row_c);
845 }
846 if (atom_col == target_atom) {
847 result[xyz][global_aux](r, c) +=
848 result_col(aux_c, col_c, row_c);
849 }
850 if (atom_row == target_atom) {
851 result[xyz][global_aux](r, c) +=
852 result_row(aux_c, col_c, row_c);
853 }
854 }
855 }
856 }
857 }
858 }
859 }
860 } catch (...) {
861#pragma omp critical
862 {
863 if (!eptr_3c) {
864 eptr_3c = std::current_exception();
865 }
866 }
867 }
868 }
869 if (eptr_3c) {
870 std::rethrow_exception(eptr_3c);
871 }
872 if (!any_nonnull_buffer_3c.load() && auxbasis.getNumofShells() > 0 &&
873 dftbasis.getNumofShells() > 0) {
874 throw std::runtime_error(
875 "ComputeThreeCenterDerivativesForAtom: engine.results() returned "
876 "a null buffer for every shell triple touching this atom; this "
877 "libint2 build does not support this operator's derivative "
878 "integrals at runtime, even though it may report "
879 "LIBINT2_MAX_DERIV_ORDER >= 1 for other operators. Rebuild "
880 "libint2 with --enable-eri3=1 to use this feature.");
881 }
882 return result;
883}
884#else // !(LIBINT_INCLUDE_ERI3)
886 const AOBasis&,
887 Index) {
888 ThrowNoDerivativeSupport("ComputeThreeCenterDerivativesForAtom",
889 "--enable-eri3=1");
890}
891#endif // LIBINT_INCLUDE_ERI3
892
893#if (LIBINT_INCLUDE_ERI3 >= 1)
894// Three-centre derivatives transformed to occupied MOs, for
895// DFTGradient::RIKGradient, in a single pass over the shell triples:
896// result[a][xyz][P](i, j) = sum_{mu,nu} C_mu,i C_nu,j d(mu,nu|P)/dR_a,xyz
897// with C = occ_mo_coeffs, i.e. (nocc x nocc) matrices (ThreeCenterDerivative
898// is shape-agnostic). Memory natoms * 3 * Naux * nocc^2 doubles (~24 GB for
899// 53 atoms, 2320 aux functions, 93 occupied orbitals). Each shell pair and
900// aux function contributes C_row^T B C_col with the (nrow x ncol) integral
901// block B.
902std::vector<ThreeCenterDerivative> ComputeThreeCenterDerivativesMOTransformed(
903 const AOBasis& auxbasis, const AOBasis& dftbasis,
904 const Eigen::MatrixXd& occ_mo_coeffs) {
905 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
906 Index nocc = occ_mo_coeffs.cols();
907
908 Index nthreads = OPENMP::getMaxThreads();
909 std::vector<libint2::Shell> dftshells = dftbasis.GenerateLibintBasis();
910 std::vector<libint2::Shell> auxshells = auxbasis.GenerateLibintBasis();
911 std::vector<Index> shell2bf = dftbasis.getMapToBasisFunctions();
912 std::vector<Index> auxshell2bf = auxbasis.getMapToBasisFunctions();
913
914 std::vector<Index> dftshell2atom;
915 dftshell2atom.reserve(dftbasis.getNumofShells());
916 for (Index s = 0; s < dftbasis.getNumofShells(); ++s) {
917 dftshell2atom.push_back(dftbasis.getShell(s).getAtomIndex());
918 }
919 std::vector<Index> auxshell2atom;
920 auxshell2atom.reserve(auxbasis.getNumofShells());
921 for (Index s = 0; s < auxbasis.getNumofShells(); ++s) {
922 auxshell2atom.push_back(auxbasis.getShell(s).getAtomIndex());
923 }
924
925 Index n_aux_bf = auxbasis.AOBasisSize();
926
927 // result[a][xyz][p]: (nocc, nocc)
928 std::vector<ThreeCenterDerivative> result(natoms);
929 for (Index a = 0; a < natoms; ++a) {
930 for (Index xyz = 0; xyz < 3; ++xyz) {
931 result[a][xyz] = std::vector<Eigen::MatrixXd>(
932 n_aux_bf, Eigen::MatrixXd::Zero(nocc, nocc));
933 }
934 }
935
936 std::vector<libint2::Engine> engines(nthreads);
937 engines[0] = libint2::Engine(
938 libint2::Operator::coulomb,
939 std::max(dftbasis.getMaxNprim(), auxbasis.getMaxNprim()),
940 static_cast<int>(std::max(dftbasis.getMaxL(), auxbasis.getMaxL())), 1);
941 engines[0].set(libint2::BraKet::xs_xx);
942 for (Index i = 1; i < nthreads; ++i) {
943 engines[i] = engines[0];
944 }
945
946 // No synchronization needed: threads write to different global_aux
947 // entries of result[atom][xyz].
948 std::exception_ptr eptr_3c = nullptr;
949 std::atomic<bool> any_nonnull_buffer_3c{false};
950#pragma omp parallel for schedule(dynamic)
951 for (Index aux = 0; aux < auxbasis.getNumofShells(); ++aux) {
952 try {
953 libint2::Engine& engine = engines[OPENMP::getThreadId()];
954 const libint2::Engine::target_ptr_vec& buf = engine.results();
955
956 const libint2::Shell& auxshell = auxshells[aux];
957 Index aux_start = auxshell2bf[aux];
958 Index atom_aux = auxshell2atom[aux];
959
960 for (Index row = 0; row < dftbasis.getNumofShells(); ++row) {
961 const libint2::Shell& shell_row = dftshells[row];
962 Index row_start = shell2bf[row];
963 Index atom_row = dftshell2atom[row];
964
965 for (Index col = 0; col < dftbasis.getNumofShells(); ++col) {
966 const libint2::Shell& shell_col = dftshells[col];
967 Index col_start = shell2bf[col];
968 Index atom_col = dftshell2atom[col];
969
970 engine
971 .compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xx, 1>(
972 auxshell, libint2::Shell::unit(), shell_col, shell_row);
973
974 if (buf[0] == nullptr || buf[3] == nullptr || buf[6] == nullptr) {
975 continue;
976 }
977 any_nonnull_buffer_3c.store(true, std::memory_order_relaxed);
978
979 // MO coefficient rows of this shell pair
980 Index nrow = static_cast<Index>(shell_row.size());
981 Index ncol = static_cast<Index>(shell_col.size());
982 Eigen::MatrixXd mo_row_block =
983 occ_mo_coeffs.middleRows(row_start, nrow);
984 Eigen::MatrixXd mo_col_block =
985 occ_mo_coeffs.middleRows(col_start, ncol);
986
987 for (Index xyz = 0; xyz < 3; ++xyz) {
988 Eigen::TensorMap<
989 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
990 result_aux(buf[xyz], auxshell.size(), shell_col.size(),
991 shell_row.size());
992 Eigen::TensorMap<
993 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
994 result_col(buf[3 + xyz], auxshell.size(), shell_col.size(),
995 shell_row.size());
996 Eigen::TensorMap<
997 Eigen::Tensor<const double, 3, Eigen::RowMajor> const>
998 result_row(buf[6 + xyz], auxshell.size(), shell_col.size(),
999 shell_row.size());
1000
1001 for (size_t aux_c = 0; aux_c < auxshell.size(); ++aux_c) {
1002 Index global_aux = aux_start + static_cast<Index>(aux_c);
1003
1004 // Copy the (nrow, ncol) block of this aux function and
1005 // transform it with two small matrix products; one rank-1
1006 // update per AO element would cost nao^2 * Naux * nocc^2.
1007 Eigen::MatrixXd block_aux(nrow, ncol);
1008 Eigen::MatrixXd block_col(nrow, ncol);
1009 Eigen::MatrixXd block_row(nrow, ncol);
1010 for (Index col_c = 0; col_c < ncol; ++col_c) {
1011 for (Index row_c = 0; row_c < nrow; ++row_c) {
1012 block_aux(row_c, col_c) =
1013 result_aux(aux_c, static_cast<size_t>(col_c),
1014 static_cast<size_t>(row_c));
1015 block_col(row_c, col_c) =
1016 result_col(aux_c, static_cast<size_t>(col_c),
1017 static_cast<size_t>(row_c));
1018 block_row(row_c, col_c) =
1019 result_row(aux_c, static_cast<size_t>(col_c),
1020 static_cast<size_t>(row_c));
1021 }
1022 }
1023 result[atom_aux][xyz][global_aux].noalias() +=
1024 mo_row_block.transpose() * block_aux * mo_col_block;
1025 result[atom_col][xyz][global_aux].noalias() +=
1026 mo_row_block.transpose() * block_col * mo_col_block;
1027 result[atom_row][xyz][global_aux].noalias() +=
1028 mo_row_block.transpose() * block_row * mo_col_block;
1029 }
1030 }
1031 }
1032 }
1033 } catch (...) {
1034#pragma omp critical
1035 {
1036 if (!eptr_3c) {
1037 eptr_3c = std::current_exception();
1038 }
1039 }
1040 }
1041 }
1042 if (eptr_3c) {
1043 std::rethrow_exception(eptr_3c);
1044 }
1045 if (!any_nonnull_buffer_3c.load() && auxbasis.getNumofShells() > 0 &&
1046 dftbasis.getNumofShells() > 0) {
1047 throw std::runtime_error(
1048 "ComputeThreeCenterDerivativesMOTransformed: engine.results() "
1049 "returned a null buffer for every shell triple; this libint2 "
1050 "build does not support this operator's derivative integrals at "
1051 "runtime, even though it may report LIBINT2_MAX_DERIV_ORDER >= 1 "
1052 "for other operators. Rebuild libint2 with --enable-eri3=1 to "
1053 "use this feature.");
1054 }
1055 return result;
1056}
1057#else // !(LIBINT_INCLUDE_ERI3)
1058std::vector<ThreeCenterDerivative> ComputeThreeCenterDerivativesMOTransformed(
1059 const AOBasis&, const AOBasis&, const Eigen::MatrixXd&) {
1060 ThrowNoDerivativeSupport("ComputeThreeCenterDerivativesMOTransformed",
1061 "--enable-eri3=1");
1062}
1063#endif // LIBINT_INCLUDE_ERI3
1064
1065// Three-centre integrals (mu,nu|P) (deriv_order = 0) for the RI gradients,
1066// one Nao x Nao matrix per aux function, built with ComputeAO3cBlock. Kept
1067// here so that libint2 is only used in this file and libint2_calls.cc. No
1068// compile guard needed.
1069std::vector<Eigen::MatrixXd> ComputeThreeCenterIntegrals(
1070 const AOBasis& auxbasis, const AOBasis& dftbasis) {
1071 std::vector<libint2::Shell> auxshells = auxbasis.GenerateLibintBasis();
1072 std::vector<Index> auxshell2bf = auxbasis.getMapToBasisFunctions();
1073
1074 std::vector<Eigen::MatrixXd> tensor(
1075 auxbasis.AOBasisSize(),
1076 Eigen::MatrixXd::Zero(dftbasis.AOBasisSize(), dftbasis.AOBasisSize()));
1077
1078 libint2::Engine engine(
1079 libint2::Operator::coulomb,
1080 std::max(dftbasis.getMaxNprim(), auxbasis.getMaxNprim()),
1081 static_cast<int>(std::max(dftbasis.getMaxL(), auxbasis.getMaxL())), 0);
1082 engine.set(libint2::BraKet::xs_xx);
1083
1084 for (Index aux = 0; aux < auxbasis.getNumofShells(); ++aux) {
1085 std::vector<Eigen::MatrixXd> block =
1086 ComputeAO3cBlock(auxshells[aux], dftbasis, engine);
1087 Index aux_start = auxshell2bf[aux];
1088 for (size_t i = 0; i < block.size(); ++i) {
1089 tensor[aux_start + static_cast<Index>(i)] = block[i];
1090 }
1091 }
1092 return tensor;
1093}
1094
1095// ===========================================================================
1096// Nuclear attraction integral derivatives d(V_ne)/dR,
1097// V_ne,munu = -sum_A Z_A int chi_mu(r) 1/|r - R_A| chi_nu(r) dr.
1098// For each point charge A this is a three-centre derivative (both basis
1099// function centres and R_A); as in libint2's
1100// compute_1body_ints_deriv<Operator::nuclear> (lcao/1body.h), each atom is
1101// set as a single point charge, giving 9 buffers ordered
1102// [shell1 centre][shell2 centre][R_A], each x, y, z.
1103//
1104// libint2::Operator::nuclear already includes the minus sign (attractive),
1105// consistent with AOMultipole::FillPotential/SetupH0, so the buffers are
1106// added without negation.
1107// ===========================================================================
1108#if defined(LIBINT2_MAX_DERIV_ORDER) && LIBINT2_MAX_DERIV_ORDER >= 1 && \
1109 (LIBINT_INCLUDE_ONEBODY >= 1)
1110std::vector<AOMatrixDerivative> ComputeNuclearAttractionDerivatives(
1111 const AOBasis& aobasis, const QMMolecule& mol) {
1112 Index natoms = mol.size();
1113 Index nthreads = OPENMP::getMaxThreads();
1114 std::vector<libint2::Shell> shells = aobasis.GenerateLibintBasis();
1115 std::vector<Index> shell2bf = aobasis.getMapToBasisFunctions();
1116
1117 std::vector<Index> shell2atom;
1118 shell2atom.reserve(aobasis.getNumofShells());
1119 for (Index s = 0; s < aobasis.getNumofShells(); ++s) {
1120 shell2atom.push_back(aobasis.getShell(s).getAtomIndex());
1121 }
1122
1123 Index nbf = aobasis.AOBasisSize();
1124 std::vector<std::vector<AOMatrixDerivative>> result_thread(nthreads);
1125 for (Index t = 0; t < nthreads; ++t) {
1126 result_thread[t].resize(natoms);
1127 for (Index a = 0; a < natoms; ++a) {
1128 for (Index xyz = 0; xyz < 3; ++xyz) {
1129 result_thread[t][a][xyz] = Eigen::MatrixXd::Zero(nbf, nbf);
1130 }
1131 }
1132 }
1133
1134 std::vector<libint2::Engine> engines(nthreads);
1135 for (Index i = 0; i < nthreads; ++i) {
1136 engines[i] =
1137 libint2::Engine(libint2::Operator::nuclear, aobasis.getMaxNprim(),
1138 static_cast<int>(aobasis.getMaxL()), 1);
1139 }
1140
1141 // Parallel over point charges (each needs engine.set_params()), with
1142 // per-thread result matrices summed afterwards.
1143 std::exception_ptr eptr_nucattr = nullptr;
1144 std::atomic<bool> any_nonnull_buffer_nucattr{false};
1145#pragma omp parallel for schedule(dynamic)
1146 for (Index A = 0; A < natoms; ++A) {
1147 try {
1148 Index thread_id = OPENMP::getThreadId();
1149 libint2::Engine& engine = engines[thread_id];
1150
1151 std::vector<libint2::Atom> single_atom(1);
1152 single_atom[0].atomic_number = static_cast<int>(mol[A].getNuccharge());
1153 single_atom[0].x = mol[A].getPos().x();
1154 single_atom[0].y = mol[A].getPos().y();
1155 single_atom[0].z = mol[A].getPos().z();
1156 engine.set_params(libint2::make_point_charges(single_atom));
1157
1158 const libint2::Engine::target_ptr_vec& buf = engine.results();
1159
1160 for (Index s1 = 0; s1 < aobasis.getNumofShells(); ++s1) {
1161 Index bf1 = shell2bf[s1];
1162 Index n1 = shells[s1].size();
1163 Index atom1 = shell2atom[s1];
1164
1165 for (Index s2 = 0; s2 <= s1; ++s2) {
1166 engine.compute(shells[s1], shells[s2]);
1167
1168 // see computeOneBodyIntegralDerivatives; three centres here.
1169 // Zero-filled (non-null) buffers are caught by the norm check
1170 // after the loop.
1171 if (buf[0] == nullptr || buf[3] == nullptr || buf[6] == nullptr) {
1172 continue;
1173 }
1174 any_nonnull_buffer_nucattr.store(true, std::memory_order_relaxed);
1175
1176 Index bf2 = shell2bf[s2];
1177 Index n2 = shells[s2].size();
1178 Index atom2 = shell2atom[s2];
1179
1180 for (Index xyz = 0; xyz < 3; ++xyz) {
1181 // no negation, see above
1182 Eigen::Map<const MatrixLibInt> buf_mat1(buf[xyz], n1, n2);
1183 result_thread[thread_id][atom1][xyz].block(bf1, bf2, n1, n2) +=
1184 buf_mat1;
1185 if (s1 != s2) {
1186 result_thread[thread_id][atom1][xyz].block(bf2, bf1, n2, n1) +=
1187 buf_mat1.transpose();
1188 }
1189
1190 Eigen::Map<const MatrixLibInt> buf_mat2(buf[3 + xyz], n1, n2);
1191 result_thread[thread_id][atom2][xyz].block(bf1, bf2, n1, n2) +=
1192 buf_mat2;
1193 if (s1 != s2) {
1194 result_thread[thread_id][atom2][xyz].block(bf2, bf1, n2, n1) +=
1195 buf_mat2.transpose();
1196 }
1197
1198 Eigen::Map<const MatrixLibInt> buf_mat3(buf[6 + xyz], n1, n2);
1199 result_thread[thread_id][A][xyz].block(bf1, bf2, n1, n2) +=
1200 buf_mat3;
1201 if (s1 != s2) {
1202 result_thread[thread_id][A][xyz].block(bf2, bf1, n2, n1) +=
1203 buf_mat3.transpose();
1204 }
1205 }
1206 }
1207 }
1208 } catch (...) {
1209#pragma omp critical
1210 {
1211 if (!eptr_nucattr) {
1212 eptr_nucattr = std::current_exception();
1213 }
1214 }
1215 }
1216 }
1217 if (eptr_nucattr) {
1218 std::rethrow_exception(eptr_nucattr);
1219 }
1220 if (!any_nonnull_buffer_nucattr.load() && natoms > 0 &&
1221 aobasis.getNumofShells() > 0) {
1222 // A build without derivative support for this operator can return
1223 // null buffers instead of throwing, which would silently give zero.
1224 throw std::runtime_error(
1225 "ComputeNuclearAttractionDerivatives: engine.results() returned "
1226 "a null buffer for every shell pair and point charge; this "
1227 "libint2 build does not support nuclear-attraction derivative "
1228 "integrals at runtime, even though it may report "
1229 "LIBINT2_MAX_DERIV_ORDER >= 1 for other operators. Rebuild "
1230 "libint2 with --enable-1body=1 to use this feature.");
1231 }
1232
1233 // Some builds instead return non-null but zero-filled buffers. The
1234 // derivative cannot vanish identically for a molecule with nonzero
1235 // nuclear charges.
1236 double total_norm_sq_nucattr = 0.0;
1237 for (Index a = 0; a < natoms; ++a) {
1238 for (Index xyz = 0; xyz < 3; ++xyz) {
1239 for (Index t = 0; t < nthreads; ++t) {
1240 total_norm_sq_nucattr += result_thread[t][a][xyz].squaredNorm();
1241 }
1242 }
1243 }
1244 if (total_norm_sq_nucattr < 1.e-20 && natoms > 0 &&
1245 aobasis.getNumofShells() > 0) {
1246 throw std::runtime_error(
1247 "ComputeNuclearAttractionDerivatives: the assembled result is "
1248 "exactly zero everywhere, which is impossible for a real "
1249 "molecule; this libint2 build likely returns zero-filled buffers "
1250 "for this operator. Rebuild libint2 with --enable-1body=1 to use "
1251 "this feature.");
1252 }
1253
1254 std::vector<AOMatrixDerivative> result(natoms);
1255 for (Index a = 0; a < natoms; ++a) {
1256 for (Index xyz = 0; xyz < 3; ++xyz) {
1257 result[a][xyz] = Eigen::MatrixXd::Zero(nbf, nbf);
1258 for (Index t = 0; t < nthreads; ++t) {
1259 result[a][xyz] += result_thread[t][a][xyz];
1260 }
1261 }
1262 }
1263 return result;
1264}
1265#else // !(LIBINT_INCLUDE_ONEBODY)
1266std::vector<AOMatrixDerivative> ComputeNuclearAttractionDerivatives(
1267 const AOBasis&, const QMMolecule&) {
1268 ThrowNoDerivativeSupport("ComputeNuclearAttractionDerivatives",
1269 "--enable-1body=1");
1270}
1271#endif // LIBINT_INCLUDE_ONEBODY
1272
1273// Lets code that does not include libint2 headers (e.g. dftengine.cc) check
1274// for derivative support. The forces need all three categories: ONEBODY
1275// (overlap, kinetic, nuclear attraction), ERI2 (Coulomb metric) and ERI3
1276// (three-centre RI).
1278#if defined(LIBINT2_MAX_DERIV_ORDER) && LIBINT2_MAX_DERIV_ORDER >= 1 && \
1279 (LIBINT_INCLUDE_ONEBODY >= 1) && (LIBINT_INCLUDE_ERI2 >= 1) && \
1280 (LIBINT_INCLUDE_ERI3 >= 1)
1281 return true;
1282#else
1283 return false;
1284#endif
1285}
1286
1287} // namespace xtp
1288} // namespace votca
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index getMaxL() const
Definition aobasis.cc:29
Index AOBasisSize() const
Definition aobasis.h:46
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< libint2::Shell > GenerateLibintBasis() const
Definition aobasis.cc:119
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< AOMatrixDerivative > ComputeNuclearAttractionDerivatives(const AOBasis &aobasis, const QMMolecule &mol)
std::vector< AOMatrixDerivative > ComputeKineticDerivatives(const AOBasis &aobasis)
std::vector< AOMatrixDerivative > ComputeCoulombMetricDerivatives(const AOBasis &aobasis)
std::vector< AOMatrixDerivative > ComputeOverlapDerivatives(const AOBasis &aobasis)
std::vector< ThreeCenterDerivative > ComputeThreeCenterDerivatives(const AOBasis &auxbasis, const AOBasis &dftbasis)
std::vector< ThreeCenterDerivative > ComputeThreeCenterDerivativesMOTransformed(const AOBasis &auxbasis, const AOBasis &dftbasis, const Eigen::MatrixXd &occ_mo_coeffs)
std::vector< Eigen::MatrixXd > ComputeThreeCenterIntegrals(const AOBasis &auxbasis, const AOBasis &dftbasis)
std::vector< Eigen::MatrixXd > ComputeAO3cBlock(const libint2::Shell &auxshell, const AOBasis &dftbasis, libint2::Engine &engine)
std::vector< Eigen::MatrixXd > ComputeThreeCenterDerivativeContraction(const AOBasis &auxbasis, const AOBasis &dftbasis, const Eigen::MatrixXd &density)
std::array< std::vector< Eigen::MatrixXd >, 3 > ThreeCenterDerivative
std::array< Eigen::MatrixXd, 3 > AOMatrixDerivative
Definition dftengine.cc:376
ThreeCenterDerivative ComputeThreeCenterDerivativesForAtom(const AOBasis &auxbasis, const AOBasis &dftbasis, Index target_atom)
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26