votca 2026-dev
Loading...
Searching...
No Matches
rpa.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2021 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
21#include "votca/xtp/rpa.h"
22#include "votca/xtp/aomatrix.h"
25#include "votca/xtp/vc2index.h"
26#include <iomanip>
27#include <sstream>
28
29namespace votca {
30namespace xtp {
31
32void RPA::UpdateRPAInputEnergies(const Eigen::VectorXd& dftenergies,
33 const Eigen::VectorXd& gwaenergies,
34 Index qpmin) {
35 Index rpatotal = rpamax_ - rpamin_ + 1;
36 energies_ = dftenergies.segment(rpamin_, rpatotal);
37 Index gwsize = Index(gwaenergies.size());
38
39 energies_.segment(qpmin - rpamin_, gwsize) = gwaenergies;
40
41 ShiftUncorrectedEnergies(dftenergies, qpmin, gwsize);
42}
43
44// Shifts energies of levels that are not QP corrected but
45// used in the RPA:
46// between rpamin and qpmin: by maximum abs of explicit QP corrections
47// from qpmin to HOMO
48// between qpmax and rpamax: by maximum abs of explicit QP corrections
49// from LUMO to qpmax
50void RPA::ShiftUncorrectedEnergies(const Eigen::VectorXd& dftenergies,
51 Index qpmin, Index gwsize) {
52
53 Index lumo = homo_ + 1;
54 Index qpmax = qpmin + gwsize - 1;
55
56 // get max abs QP corrections for occupied/virtual levels
57 double max_correction_occ = getMaxCorrection(dftenergies, qpmin, homo_);
58 double max_correction_virt = getMaxCorrection(dftenergies, lumo, qpmax);
59
60 // shift energies
61 energies_.head(qpmin).array() -= max_correction_occ;
62 energies_.tail(rpamax_ - qpmax).array() += max_correction_virt;
63}
64
65double RPA::getMaxCorrection(const Eigen::VectorXd& dftenergies, Index min,
66 Index max) const {
67
68 Index range = max - min + 1;
69 Eigen::VectorXd corrections =
70 energies_.segment(min, range) - dftenergies.segment(min - rpamin_, range);
71
72 return (corrections.cwiseAbs()).maxCoeff();
73}
74
75template <bool imag>
76Eigen::MatrixXd RPA::calculate_epsilon(double frequency) const {
77 const Index size = Mmn_.auxsize();
78
79 const Index lumo = homo_ + 1;
80 const Index n_occ = lumo - rpamin_;
81 const Index n_unocc = rpamax_ - lumo + 1;
82 const double freq2 = frequency * frequency;
83 const double eta2 = eta_ * eta_;
84
85 OpenMP_CUDA transform;
86 transform.createTemporaries(n_unocc, size);
87
88#pragma omp parallel
89 {
90 Index threadid = OPENMP::getThreadId();
91#pragma omp for schedule(dynamic)
92 for (Index m_level = 0; m_level < n_occ; m_level++) {
93 const double qp_energy_m = energies_(m_level);
94
95 // QSGW: for hole slices in the QP window, apply the m-rotation on the
96 // fly. This makes the RPA hole states consistent with the current QP
97 // wavefunctions while keeping Mmn_ unmodified (its m-index remains in the
98 // DFT-MO basis for correct sigma matrix element evaluation).
99 Eigen::MatrixXd Mmn_RPA;
100 if (qsgw_U_ != nullptr) {
101 const Index qp_offset_m = qsgw_qpmin_ - rpamin_;
102 const Index qptotal = Index(qsgw_U_->cols());
103 const Index qp_end_occ =
104 std::min(qsgw_homo_ - rpamin_ + 1, qp_offset_m + qptotal);
105 if (m_level >= qp_offset_m && m_level < qp_end_occ) {
106 // This hole slice is within the QP window: apply m-rotation
107 const Index v_qp = m_level - qp_offset_m;
108 Mmn_RPA = Eigen::MatrixXd::Zero(n_unocc, Mmn_.auxsize());
109 for (Index vp = 0; vp < qptotal; vp++) {
110 Mmn_RPA.noalias() += (*qsgw_U_)(vp, v_qp) *
111 Mmn_[vp + qp_offset_m].bottomRows(n_unocc);
112 }
113 } else {
114 Mmn_RPA = Mmn_[m_level].bottomRows(n_unocc);
115 }
116 } else {
117 Mmn_RPA = Mmn_[m_level].bottomRows(n_unocc);
118 }
119
120 transform.PushMatrix(Mmn_RPA, threadid);
121 const Eigen::ArrayXd deltaE =
122 energies_.tail(n_unocc).array() - qp_energy_m;
123 Eigen::VectorXd denom;
124 if (imag) {
125 denom = 4 * deltaE / (deltaE.square() + freq2);
126 } else {
127 Eigen::ArrayXd deltEf = deltaE - frequency;
128 Eigen::ArrayXd sum = deltEf / (deltEf.square() + eta2);
129 deltEf = deltaE + frequency;
130 sum += deltEf / (deltEf.square() + eta2);
131 denom = 2 * sum;
132 }
133
134 transform.A_TDA(denom, threadid);
135 }
136 }
137 Eigen::MatrixXd result = transform.getReductionVar();
138 result.diagonal().array() += 1.0;
139 return result;
140}
141
142template Eigen::MatrixXd RPA::calculate_epsilon<true>(double frequency) const;
143template Eigen::MatrixXd RPA::calculate_epsilon<false>(double frequency) const;
144
145Eigen::MatrixXd RPA::calculate_epsilon_r(std::complex<double> frequency) const {
146
147 const Index size = Mmn_.auxsize();
148
149 const Index lumo = homo_ + 1;
150 const Index n_occ = lumo - rpamin_;
151 const Index n_unocc = rpamax_ - lumo + 1;
152 OpenMP_CUDA transform;
153 transform.createTemporaries(n_unocc, size);
154#pragma omp parallel
155 {
156 Index threadid = OPENMP::getThreadId();
157#pragma omp for schedule(dynamic)
158 for (Index m_level = 0; m_level < n_occ; m_level++) {
159
160 const double qp_energy_m = energies_(m_level);
161
162 // QSGW: apply m-rotation to QP-window hole slices on the fly
163 Eigen::MatrixXd Mmn_RPA;
164 if (qsgw_U_ != nullptr) {
165 const Index qp_offset_m = qsgw_qpmin_ - rpamin_;
166 const Index qptotal = Index(qsgw_U_->cols());
167 const Index qp_end_occ =
168 std::min(qsgw_homo_ - rpamin_ + 1, qp_offset_m + qptotal);
169 if (m_level >= qp_offset_m && m_level < qp_end_occ) {
170 const Index v_qp = m_level - qp_offset_m;
171 Mmn_RPA = Eigen::MatrixXd::Zero(n_unocc, Mmn_.auxsize());
172 for (Index vp = 0; vp < qptotal; vp++) {
173 Mmn_RPA.noalias() += (*qsgw_U_)(vp, v_qp) *
174 Mmn_[vp + qp_offset_m].bottomRows(n_unocc);
175 }
176 } else {
177 Mmn_RPA = Mmn_[m_level].bottomRows(n_unocc);
178 }
179 } else {
180 Mmn_RPA = Mmn_[m_level].bottomRows(n_unocc);
181 }
182
183 transform.PushMatrix(Mmn_RPA, threadid);
184 const Eigen::ArrayXd deltaE =
185 energies_.tail(n_unocc).array() - qp_energy_m;
186
187 Eigen::ArrayXd deltaEm = frequency.real() - deltaE;
188 Eigen::ArrayXd deltaEp = frequency.real() + deltaE;
189
190 double sigma_1 = std::pow(frequency.imag() + eta_, 2);
191 double sigma_2 = std::pow(frequency.imag() - eta_, 2);
192
193 Eigen::VectorXd chi =
194 deltaEm * (deltaEm.cwiseAbs2() + sigma_1).cwiseInverse() -
195 deltaEp * (deltaEp.cwiseAbs2() + sigma_2).cwiseInverse();
196 transform.A_TDA(chi, threadid);
197 }
198 }
199 Eigen::MatrixXd result = -2 * transform.getReductionVar();
200 result.diagonal().array() += 1.0;
201 return result;
202}
203
205 const Index lumo = homo_ + 1;
206 const Index n_occ = lumo - rpamin_;
207 const Index n_unocc = rpamax_ - lumo + 1;
208 const Index rpasize = n_occ * n_unocc;
209
210 Eigen::VectorXd AmB = Calculate_H2p_AmB();
211 Eigen::MatrixXd ApB = Calculate_H2p_ApB();
212
214 sol.ERPA_correlation = -0.25 * (ApB.trace() + AmB.sum());
215
216 // C = AmB^1/2 * ApB * AmB^1/2
217 Eigen::MatrixXd& C = ApB;
218 C.applyOnTheLeft(AmB.cwiseSqrt().asDiagonal());
219 C.applyOnTheRight(AmB.cwiseSqrt().asDiagonal());
220
221 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es = Diagonalize_H2p_C(C);
222
223 // Do not remove this line! It has to be there for MKL to not crash
224 sol.omega = Eigen::VectorXd::Zero(es.eigenvalues().size());
225 sol.omega = es.eigenvalues().cwiseSqrt();
226 sol.ERPA_correlation += 0.5 * sol.omega.sum();
227
228 {
229 std::ostringstream oss;
230 oss << TimeStamp() << " RKS H2p poles (first 20, eV): ";
231 const Index nprint = std::min<Index>(20, sol.omega.size());
232 for (Index i = 0; i < nprint; ++i) {
233 oss << std::fixed << std::setprecision(6)
234 << tools::conv::hrt2ev * sol.omega(i);
235 if (i + 1 < nprint) {
236 oss << ", ";
237 }
238 }
239 XTP_LOG(Log::error, log_) << oss.str() << std::flush;
240 }
241
243 << " Lowest neutral excitation energy (eV): "
244 << tools::conv::hrt2ev * sol.omega.minCoeff()
245 << std::flush;
246
247 // RPA correlation energy calculated from Eq.9 of J. Chem. Phys. 132, 234114
248 // (2010)
250 << TimeStamp()
251 << " RPA correlation energy (Hartree): " << sol.ERPA_correlation
252 << std::flush;
253
254 sol.XpY = Eigen::MatrixXd(rpasize, rpasize);
255
256 Eigen::VectorXd AmB_sqrt = AmB.cwiseSqrt();
257 Eigen::VectorXd Omega_sqrt_inv = sol.omega.cwiseSqrt().cwiseInverse();
258 for (int s = 0; s < rpasize; s++) {
259 sol.XpY.col(s) =
260 Omega_sqrt_inv(s) * AmB_sqrt.cwiseProduct(es.eigenvectors().col(s));
261 }
262
263 return sol;
264}
265
266Eigen::VectorXd RPA::Calculate_H2p_AmB() const {
267 const Index lumo = homo_ + 1;
268 const Index n_occ = lumo - rpamin_;
269 const Index n_unocc = rpamax_ - lumo + 1;
270 const Index rpasize = n_occ * n_unocc;
271 vc2index vc = vc2index(0, 0, n_unocc);
272 Eigen::VectorXd AmB = Eigen::VectorXd::Zero(rpasize);
273 for (Index v = 0; v < n_occ; v++) {
274 Index i = vc.I(v, 0);
275 AmB.segment(i, n_unocc) =
276 energies_.segment(n_occ, n_unocc).array() - energies_(v);
277 }
278 return AmB;
279}
280
281Eigen::MatrixXd RPA::Calculate_H2p_ApB() const {
282 const Index lumo = homo_ + 1;
283 const Index n_occ = lumo - rpamin_;
284 const Index n_unocc = rpamax_ - lumo + 1;
285 const Index rpasize = n_occ * n_unocc;
286 vc2index vc = vc2index(0, 0, n_unocc);
287 Eigen::MatrixXd ApB = Eigen::MatrixXd::Zero(rpasize, rpasize);
288 // QSGW: pre-compute m-rotated slices for QP-window hole states
289 // to avoid repeated on-the-fly rotation inside the double loop.
290 const Index qp_offset_m_apb =
291 (qsgw_U_ != nullptr) ? (qsgw_qpmin_ - rpamin_) : -1;
292 const Index qptotal_apb = (qsgw_U_ != nullptr) ? Index(qsgw_U_->cols()) : 0;
293 const Index qp_end_occ_apb =
294 (qsgw_U_ != nullptr)
295 ? std::min(qsgw_homo_ - rpamin_ + 1, qp_offset_m_apb + qptotal_apb)
296 : 0;
297
298 // Pre-build rotated virtual-row blocks for occupied QP-window slices
299 std::vector<Eigen::MatrixXd> Mmn_virt_rotated(n_occ);
300 for (Index v = 0; v < n_occ; v++) {
301 if (qsgw_U_ != nullptr && v >= qp_offset_m_apb && v < qp_end_occ_apb) {
302 const Index v_qp = v - qp_offset_m_apb;
303 Mmn_virt_rotated[v] = Eigen::MatrixXd::Zero(n_unocc, Mmn_.auxsize());
304 for (Index vp = 0; vp < qptotal_apb; vp++) {
305 Mmn_virt_rotated[v].noalias() +=
306 (*qsgw_U_)(vp, v_qp) *
307 Mmn_[vp + qp_offset_m_apb].middleRows(n_occ, n_unocc);
308 }
309 } else {
310 Mmn_virt_rotated[v] = Mmn_[v].middleRows(n_occ, n_unocc);
311 }
312 }
313#pragma omp parallel for schedule(guided)
314 for (Index v2 = 0; v2 < n_occ; v2++) {
315 Index i2 = vc.I(v2, 0);
316 const Eigen::MatrixXd Mmn_v2T = Mmn_virt_rotated[v2].transpose();
317 for (Index v1 = v2; v1 < n_occ; v1++) {
318 Index i1 = vc.I(v1, 0);
319 // Multiply with factor 2 to sum over both (identical) spin states
320 ApB.block(i1, i2, n_unocc, n_unocc) =
321 2 * 2 * Mmn_virt_rotated[v1] * Mmn_v2T;
322 }
323 }
324 ApB.diagonal() += Calculate_H2p_AmB();
325 return ApB;
326}
327
328Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> RPA::Diagonalize_H2p_C(
329 const Eigen::MatrixXd& C) const {
331 << TimeStamp() << " Diagonalizing two-particle Hamiltonian "
332 << std::flush;
333 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(C); // Uses lower triangle
335 << TimeStamp() << " Diagonalization done " << std::flush;
336 double minCoeff = es.eigenvalues().minCoeff();
337 if (minCoeff <= 0.0) {
339 << TimeStamp() << " Detected non-positive eigenvalue: " << minCoeff
340 << std::flush;
341 throw std::runtime_error("Detected non-positive eigenvalue.");
342 }
343 return es;
344}
345
346} // namespace xtp
347} // namespace votca
void A_TDA(const Eigen::VectorXd &vec, Index OpenmpThread)
Eigen::MatrixXd getReductionVar()
void createTemporaries(Index rows, Index cols)
void PushMatrix(const Eigen::MatrixXd &mat, Index OpenmpThread)
Eigen::MatrixXd Calculate_H2p_ApB() const
Definition rpa.cc:281
void ShiftUncorrectedEnergies(const Eigen::VectorXd &dftenergies, Index qpmin, Index gwsize)
Definition rpa.cc:50
Index rpamax_
Definition rpa.h:89
const Eigen::MatrixXd * qsgw_U_
Definition rpa.h:93
Eigen::VectorXd energies_
Definition rpa.h:97
Eigen::MatrixXd calculate_epsilon(double frequency) const
Definition rpa.cc:76
Eigen::MatrixXd calculate_epsilon_r(double frequency) const
Definition rpa.h:51
Index qsgw_qpmin_
Definition rpa.h:94
const TCMatrix_gwbse & Mmn_
Definition rpa.h:100
const double eta_
Definition rpa.h:90
Index rpamin_
Definition rpa.h:88
Eigen::VectorXd Calculate_H2p_AmB() const
Definition rpa.cc:266
Eigen::SelfAdjointEigenSolver< Eigen::MatrixXd > Diagonalize_H2p_C(const Eigen::MatrixXd &C) const
Definition rpa.cc:328
Logger & log_
Definition rpa.h:99
rpa_eigensolution Diagonalize_H2p() const
Definition rpa.cc:204
void UpdateRPAInputEnergies(const Eigen::VectorXd &dftenergies, const Eigen::VectorXd &gwaenergies, Index qpmin)
Definition rpa.cc:32
double getMaxCorrection(const Eigen::VectorXd &dftenergies, Index min, Index max) const
Definition rpa.cc:65
Index qsgw_homo_
Definition rpa.h:95
Index homo_
Definition rpa.h:87
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
This class transforms a pair of indices v,c into a compound index I, via I=ctotal*v+c the fast dimens...
Definition vc2index.h:36
Index I(Index v, Index c) const
Definition vc2index.h:42
#define XTP_LOG(level, log)
Definition logger.h:40
const double hrt2ev
Definition constants.h:53
Index getThreadId()
Definition eigen.h:143
Charge transport classes.
Definition ERIs.h:28
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26