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"
23
24namespace votca {
25namespace xtp {
26
27// Defined in libint2_derivative_calls.cc, not yet in any header (same
28// STATUS noted throughout that file and libint2_derivative_calls.cc
29// itself) -- forward declared here rather than adding a new header at
30// this stage, consistent with how these functions have been consumed
31// from test files so far.
32using AOMatrixDerivative = std::array<Eigen::MatrixXd, 3>;
33using ThreeCenterDerivative = std::array<std::vector<Eigen::MatrixXd>, 3>;
34std::vector<AOMatrixDerivative> ComputeCoulombMetricDerivatives(
35 const AOBasis& aobasis);
36std::vector<ThreeCenterDerivative> ComputeThreeCenterDerivatives(
37 const AOBasis& auxbasis, const AOBasis& dftbasis);
38std::vector<Eigen::MatrixXd> ComputeThreeCenterIntegrals(
39 const AOBasis& auxbasis, const AOBasis& dftbasis);
40
42 Index natoms = mol.size();
43 Eigen::MatrixXd deriv = Eigen::MatrixXd::Zero(natoms, 3);
44
45 // dE_nn/dR_A = sum_{B != A} Z_A Z_B * d(1/R_AB)/dR_A
46 // = -sum_{B != A} Z_A Z_B (R_A - R_B) / |R_A - R_B|^3
47 //
48 // NOTE: this returns the GRADIENT dE/dR, not the force -dE/dR -- the
49 // sign convention for the final assembled total gradient (which this
50 // feeds into) is a decision for whatever code combines this with the
51 // RI-J and XC terms, not fixed here. Keep this consistent when
52 // validating against finite differences: a finite difference of the
53 // energy directly gives dE/dR, matching this function's output as-is,
54 // with no extra sign flip needed.
55 for (Index a = 0; a < natoms; ++a) {
56 double Za = static_cast<double>(mol[a].getNuccharge());
57 const Eigen::Vector3d& Ra = mol[a].getPos();
58 Eigen::Vector3d sum = Eigen::Vector3d::Zero();
59 for (Index b = 0; b < natoms; ++b) {
60 if (b == a) {
61 continue;
62 }
63 double Zb = static_cast<double>(mol[b].getNuccharge());
64 const Eigen::Vector3d& Rb = mol[b].getPos();
65 Eigen::Vector3d Rab_vec = Ra - Rb;
66 double Rab = Rab_vec.norm();
67 sum += Za * Zb * Rab_vec / (Rab * Rab * Rab);
68 }
69 deriv.row(a) = -sum.transpose();
70 }
71 return deriv;
72}
73
74Eigen::MatrixXd DFTGradient::RIJGradient(const Eigen::MatrixXd& density,
75 const AOBasis& auxbasis,
76 const AOBasis& dftbasis) {
77 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
78 Index n_aux_bf = auxbasis.AOBasisSize();
79
80 // d_P = sum_{mu,nu} P_{mu,nu} (mu,nu|P)
81 std::vector<Eigen::MatrixXd> tensor =
82 ComputeThreeCenterIntegrals(auxbasis, dftbasis);
83 Eigen::VectorXd d(n_aux_bf);
84 for (Index p = 0; p < n_aux_bf; ++p) {
85 d(p) = (density.array() * tensor[p].array()).sum();
86 }
87
88 // V_PQ = (P|Q), c = V^-1 d. Using a plain SPD solve here rather than
89 // the eigenvalue-truncated Pseudo_InvSqrt approach AOCoulomb also
90 // offers (used in production for numerical stability against
91 // near-linear-dependence in the aux basis) -- adequate for this
92 // validation-scale use, but worth revisiting if this is ever used on
93 // a genuinely large or near-linearly-dependent aux basis.
94 AOCoulomb aocoulomb;
95 aocoulomb.Fill(auxbasis);
96 const Eigen::MatrixXd& V = aocoulomb.Matrix();
97 Eigen::VectorXd c = V.ldlt().solve(d);
98
99 // Derivative tensors -- both already validated (finite-difference
100 // tested) in test_aoderivatives.cc.
101 std::vector<ThreeCenterDerivative> d3c =
102 ComputeThreeCenterDerivatives(auxbasis, dftbasis);
103 std::vector<AOMatrixDerivative> dV =
105
106 // dE_J/dR = sum_P c_P d(d_P)/dR - 1/2 sum_PQ c_P c_Q d(V_PQ)/dR
107 // d(d_P)/dR = sum_{mu,nu} P_{mu,nu} d(mu,nu|P)/dR (density held fixed --
108 // see the "IMPORTANT" note on this function in dftgradient.h for why
109 // that's valid regardless of whether density is a converged SCF
110 // density or an arbitrary fixed matrix).
111 Eigen::MatrixXd grad = Eigen::MatrixXd::Zero(natoms, 3);
112 for (Index a = 0; a < natoms; ++a) {
113 for (Index xyz = 0; xyz < 3; ++xyz) {
114 double term1 = 0.0;
115 for (Index p = 0; p < n_aux_bf; ++p) {
116 term1 += c(p) * (density.array() * d3c[a][xyz][p].array()).sum();
117 }
118 double term2 = 0.5 * c.dot(dV[a][xyz] * c);
119 grad(a, xyz) = term1 - term2;
120 }
121 }
122 return grad;
123}
124
125Eigen::MatrixXd DFTGradient::RIKGradient(const Eigen::MatrixXd& occ_mo_coeffs,
126 const AOBasis& auxbasis,
127 const AOBasis& dftbasis) {
128 Index natoms = static_cast<Index>(dftbasis.getFuncPerAtom().size());
129 Index n_aux_bf = auxbasis.AOBasisSize();
130 Index nocc = occ_mo_coeffs.cols();
131
132 std::vector<Eigen::MatrixXd> tensor =
133 ComputeThreeCenterIntegrals(auxbasis, dftbasis);
134
135 AOCoulomb aocoulomb;
136 aocoulomb.Fill(auxbasis);
137 const Eigen::MatrixXd& V = aocoulomb.Matrix();
138 Eigen::LDLT<Eigen::MatrixXd> V_ldlt(V);
139
140 std::vector<ThreeCenterDerivative> d3c =
141 ComputeThreeCenterDerivatives(auxbasis, dftbasis);
142 std::vector<AOMatrixDerivative> dV =
144
145 Eigen::MatrixXd grad = Eigen::MatrixXd::Zero(natoms, 3);
146
147 // NOTE ON HISTORY: an earlier revision of this function briefly
148 // switched to a "half-transformed" structure (one index MO, one AO),
149 // reasoned (incorrectly, via error-prone hand algebra) to be needed
150 // to match ERIs::CalculateEXX_mos's real K matrix. Settled by DIRECT
151 // NUMERICAL SIMULATION of CalculateEXX_mos's actual algorithm
152 // (symmetric V^-1/2 fit, TCxMOs_T = occMos^T*B_tilde, etc.) against
153 // both candidate formulas, on several random test systems: the
154 // FULLY-MO-transformed structure below (both indices occupied MOs,
155 // matching the ORIGINAL version of this function) is correct, and
156 // matches the real energy EXACTLY (to ~1e-14) once multiplied by 2 --
157 // not the half-transformed structure, which did not match at all
158 // (not even up to a constant factor). See conversation history for
159 // the verification. This confirms the earlier documented concern
160 // (needing to differentiate a matrix square root) was never actually
161 // a problem -- V^-1 and V^-1/2 fitting give identical energies
162 // (|V^-1/2 x|^2 == x^T V^-1 x exactly, for symmetric positive-definite
163 // V) -- the only real fix needed here was the missing factor of 2.
164 //
165 // d_ij(P) = C_i^T tensor[P] C_j, c_ij = V^-1 d_ij (i,j both occupied
166 // MOs, all ordered pairs including i==j).
167 // E_K = -2 * sum_{i,j} [0.5 * c_ij . d_ij] = -sum_{i,j} c_ij . d_ij
168 // (matches ERIs::CalculateEXX_mos's real physical exchange energy
169 // exactly, confirmed numerically, not just up to an unknown scale).
170 std::vector<std::vector<Eigen::VectorXd>> c_ij(
171 nocc, std::vector<Eigen::VectorXd>(nocc));
172 for (Index i = 0; i < nocc; ++i) {
173 for (Index j = 0; j < nocc; ++j) {
174 Eigen::VectorXd d(n_aux_bf);
175 for (Index p = 0; p < n_aux_bf; ++p) {
176 d(p) = occ_mo_coeffs.col(i).dot(tensor[p] * occ_mo_coeffs.col(j));
177 }
178 c_ij[i][j] = V_ldlt.solve(d);
179 }
180 }
181
182 for (Index a = 0; a < natoms; ++a) {
183 for (Index xyz = 0; xyz < 3; ++xyz) {
184 double energy_term = 0.0;
185 double metric_term = 0.0;
186 for (Index i = 0; i < nocc; ++i) {
187 for (Index j = 0; j < nocc; ++j) {
188 const Eigen::VectorXd& c = c_ij[i][j];
189 Eigen::VectorXd dd(n_aux_bf);
190 for (Index p = 0; p < n_aux_bf; ++p) {
191 dd(p) =
192 occ_mo_coeffs.col(i).dot(d3c[a][xyz][p] * occ_mo_coeffs.col(j));
193 }
194 energy_term += c.dot(dd);
195 metric_term += c.dot(dV[a][xyz] * c);
196 }
197 }
198 // Factor of -2, matching E_K = -2*sum[0.5*c.d] confirmed above --
199 // NOT the "-(energy_term - 0.5*metric_term)" (factor of -1) used
200 // in the intermediate, incorrect half-transformed revision.
201 grad(a, xyz) = -2.0 * (energy_term - 0.5 * metric_term);
202 }
203 }
204 return grad;
205}
206
207} // namespace xtp
208} // 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
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< Eigen::MatrixXd > ComputeThreeCenterIntegrals(const AOBasis &auxbasis, const AOBasis &dftbasis)
std::array< std::vector< Eigen::MatrixXd >, 3 > ThreeCenterDerivative
std::array< Eigen::MatrixXd, 3 > AOMatrixDerivative
Definition dftengine.cc:409
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26