votca 2026-dev
Loading...
Searching...
No Matches
dftgradient.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// Local VOTCA includes
22#include "votca/xtp/aomatrix.h"
24
25namespace votca {
26namespace xtp {
27
28// Defined in libint2_derivative_calls.cc (no header).
29using AOMatrixDerivative = std::array<Eigen::MatrixXd, 3>;
30using ThreeCenterDerivative = std::array<std::vector<Eigen::MatrixXd>, 3>;
31std::vector<AOMatrixDerivative> ComputeCoulombMetricDerivatives(
32 const AOBasis& aobasis);
33std::vector<ThreeCenterDerivative> ComputeThreeCenterDerivatives(
34 const AOBasis& auxbasis, const AOBasis& dftbasis);
35// Density-contracted version of ComputeThreeCenterDerivatives, which stores
36// the full tensor and is only usable for small systems.
37std::vector<Eigen::MatrixXd> ComputeThreeCenterDerivativeContraction(
38 const AOBasis& auxbasis, const AOBasis& dftbasis,
39 const Eigen::MatrixXd& density);
40// Occupied-MO-transformed three-centre derivatives, used by RIKGradient.
41std::vector<ThreeCenterDerivative> ComputeThreeCenterDerivativesMOTransformed(
42 const AOBasis& auxbasis, const AOBasis& dftbasis,
43 const Eigen::MatrixXd& occ_mo_coeffs);
44// Per-atom variant, superseded by ComputeThreeCenterDerivativesMOTransformed.
46 const AOBasis& auxbasis, const AOBasis& dftbasis, Index target_atom);
47std::vector<Eigen::MatrixXd> ComputeThreeCenterIntegrals(
48 const AOBasis& auxbasis, const AOBasis& dftbasis);
49
51 Index natoms = mol.size();
52 Eigen::MatrixXd deriv = Eigen::MatrixXd::Zero(natoms, 3);
53
54 // dE_nn/dR_A = sum_{B != A} Z_A Z_B * d(1/R_AB)/dR_A
55 // = -sum_{B != A} Z_A Z_B (R_A - R_B) / |R_A - R_B|^3
56 //
57 // Returns the gradient dE/dR, not the force.
58 for (Index a = 0; a < natoms; ++a) {
59 double Za = static_cast<double>(mol[a].getNuccharge());
60 const Eigen::Vector3d& Ra = mol[a].getPos();
61 Eigen::Vector3d sum = Eigen::Vector3d::Zero();
62 for (Index b = 0; b < natoms; ++b) {
63 if (b == a) {
64 continue;
65 }
66 double Zb = static_cast<double>(mol[b].getNuccharge());
67 const Eigen::Vector3d& Rb = mol[b].getPos();
68 Eigen::Vector3d Rab_vec = Ra - Rb;
69 double Rab = Rab_vec.norm();
70 sum += Za * Zb * Rab_vec / (Rab * Rab * Rab);
71 }
72 deriv.row(a) = -sum.transpose();
73 }
74 return deriv;
75}
76
77Eigen::MatrixXd DFTGradient::RIJGradient(const Eigen::MatrixXd& density,
78 const AOBasis& auxbasis,
79 const AOBasis& dftbasis) {
80 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
81 Index n_aux_bf = auxbasis.AOBasisSize();
82
83 // d_P = sum_{mu,nu} P_{mu,nu} (mu,nu|P)
84 std::vector<Eigen::MatrixXd> tensor =
85 ComputeThreeCenterIntegrals(auxbasis, dftbasis);
86 Eigen::VectorXd d(n_aux_bf);
87 for (Index p = 0; p < n_aux_bf; ++p) {
88 d(p) = (density.array() * tensor[p].array()).sum();
89 }
90
91 // V_PQ = (P|Q), c = V^-1 d. Plain solve, without the eigenvalue
92 // truncation of Pseudo_InvSqrt used in the SCF; may need revisiting for
93 // nearly linearly dependent aux bases.
94 AOCoulomb aocoulomb;
95 aocoulomb.Fill(auxbasis);
96 const Eigen::MatrixXd& V = aocoulomb.Matrix();
97 // V is positive definite; Cholesky, LDLT as before if it is not
98 const CholeskyFactor V_chol(V);
99 Eigen::VectorXd c = V_chol.ok() ? Eigen::VectorXd(V_chol.Solve(d))
100 : Eigen::VectorXd(V.ldlt().solve(d));
101
102 // ddP_dR[a](xyz, p) = sum_{mu,nu} P_{mu,nu} d(mu,nu|p)/dR_a,xyz, contracted
103 // on the fly (the full derivative tensor does not fit in memory for
104 // realistic systems).
105 std::vector<Eigen::MatrixXd> ddP_dR =
106 ComputeThreeCenterDerivativeContraction(auxbasis, dftbasis, density);
107 std::vector<AOMatrixDerivative> dV =
109
110 // dE_J/dR = sum_P c_P d(d_P)/dR - 1/2 sum_PQ c_P c_Q d(V_PQ)/dR with
111 // d(d_P)/dR = sum_{mu,nu} P_{mu,nu} d(mu,nu|P)/dR at fixed density (see
112 // dftgradient.h).
113 Eigen::MatrixXd grad = Eigen::MatrixXd::Zero(natoms, 3);
114 for (Index a = 0; a < natoms; ++a) {
115 for (Index xyz = 0; xyz < 3; ++xyz) {
116 double term1 = ddP_dR[a].row(xyz).dot(c);
117 double term2 = 0.5 * c.dot(dV[a][xyz] * c);
118 grad(a, xyz) = term1 - term2;
119 }
120 }
121 return grad;
122}
123
124Eigen::MatrixXd DFTGradient::RIKGradient(const Eigen::MatrixXd& occ_mo_coeffs,
125 const AOBasis& auxbasis,
126 const AOBasis& dftbasis) {
127 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
128 Index n_aux_bf = auxbasis.AOBasisSize();
129 Index nocc = occ_mo_coeffs.cols();
130
131 std::vector<Eigen::MatrixXd> tensor =
132 ComputeThreeCenterIntegrals(auxbasis, dftbasis);
133
134 AOCoulomb aocoulomb;
135 aocoulomb.Fill(auxbasis);
136 const Eigen::MatrixXd& V = aocoulomb.Matrix();
137 // V is positive definite; Cholesky, LDLT as before if it is not
138 const CholeskyFactor V_chol(V);
139 Eigen::LDLT<Eigen::MatrixXd> V_ldlt;
140 if (!V_chol.ok()) {
141 V_ldlt.compute(V);
142 }
143
144 std::vector<AOMatrixDerivative> dV =
146
147 // d3c_mo[a][xyz][p](i, j) = C_i^T d(tensor[p])/dR_a,xyz C_j, all atoms in
148 // one pass; memory natoms * 3 * Naux * nocc^2.
149 std::vector<ThreeCenterDerivative> d3c_mo =
151 occ_mo_coeffs);
152
153 Eigen::MatrixXd grad = Eigen::MatrixXd::Zero(natoms, 3);
154
155 // d_ij(P) = C_i^T tensor[P] C_j, c_ij = V^-1 d_ij over all ordered pairs
156 // of occupied MOs i, j. The exchange energy is
157 // E_K = -2 * sum_{i,j} [0.5 * c_ij . d_ij] = -sum_{i,j} c_ij . d_ij,
158 // equal to that of ERIs::CalculateEXX_mos.
159 //
160 // tensor[p] * C is formed once per p, so each d_ij(p) is an O(nao) dot
161 // product.
162 std::vector<Eigen::MatrixXd> tensor_half(n_aux_bf);
163 for (Index p = 0; p < n_aux_bf; ++p) {
164 tensor_half[p] = tensor[p] * occ_mo_coeffs; // (nao, nocc)
165 }
166 std::vector<std::vector<Eigen::VectorXd>> c_ij(
167 nocc, std::vector<Eigen::VectorXd>(nocc));
168 for (Index i = 0; i < nocc; ++i) {
169 for (Index j = 0; j < nocc; ++j) {
170 Eigen::VectorXd d(n_aux_bf);
171 for (Index p = 0; p < n_aux_bf; ++p) {
172 d(p) = occ_mo_coeffs.col(i).dot(tensor_half[p].col(j));
173 }
174 c_ij[i][j] = V_chol.ok() ? Eigen::VectorXd(V_chol.Solve(d))
175 : Eigen::VectorXd(V_ldlt.solve(d));
176 }
177 }
178
179 // CalculateEXX_mos uses the symmetric V^-1/2 fit; the V^-1 fit used here
180 // gives the same energy (|V^-1/2 x|^2 = x^T V^-1 x for positive-definite
181 // V), so no derivative of V^-1/2 is needed. Both MO indices must be
182 // transformed: a half-transformed (one MO, one AO index) expression does
183 // not reproduce the energy.
184
185#pragma omp parallel for
186 for (Index a = 0; a < natoms; ++a) {
187 for (Index xyz = 0; xyz < 3; ++xyz) {
188 double energy_term = 0.0;
189 double metric_term = 0.0;
190 for (Index i = 0; i < nocc; ++i) {
191 for (Index j = 0; j < nocc; ++j) {
192 const Eigen::VectorXd& c = c_ij[i][j];
193 // d(d_ij(p))/dR_a,xyz
194 Eigen::VectorXd dd(n_aux_bf);
195 for (Index p = 0; p < n_aux_bf; ++p) {
196 dd(p) = d3c_mo[a][xyz][p](i, j);
197 }
198 energy_term += c.dot(dd);
199 metric_term += c.dot(dV[a][xyz] * c);
200 }
201 }
202 // dE_K/dR = -2 sum_ij [c_ij . d(d_ij)/dR - 0.5 c_ij . dV/dR c_ij]
203 grad(a, xyz) = -2.0 * (energy_term - 0.5 * metric_term);
204 }
205 }
206 return grad;
207}
208
209} // namespace xtp
210} // namespace votca
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index AOBasisSize() const
Definition aobasis.h:46
const std::vector< Index > & getFuncPerAtom() const
Definition aobasis.h:72
void Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
Definition aomatrix.h:72
const Eigen::Vector3d & getPos() const
Eigen::MatrixXd Solve(const Eigen::MatrixXd &B) const
A^-1 B.
static Eigen::MatrixXd RIKGradient(const Eigen::MatrixXd &occ_mo_coeffs, const AOBasis &auxbasis, const AOBasis &dftbasis)
static Eigen::MatrixXd NuclearRepulsionDerivative(const QMMolecule &mol)
static Eigen::MatrixXd RIJGradient(const Eigen::MatrixXd &density, const AOBasis &auxbasis, const AOBasis &dftbasis)
Charge transport classes.
Definition ERIs.h:28
std::vector< AOMatrixDerivative > ComputeCoulombMetricDerivatives(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 > 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