votca 2026-dev
Loading...
Searching...
No Matches
bsecoupling.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2020 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// Third party includes
21#include <boost/format.hpp>
22
23// VOTCA includes
25
26// Local VOTCA includes
27#include "votca/xtp/aomatrix.h"
28#include "votca/xtp/bse.h"
31#include "votca/xtp/citations.h"
32
33namespace votca {
34namespace xtp {
35using namespace std;
36using boost::format;
37using namespace tools;
38
40 string spintype = options.get("spin").as<std::string>();
41 if (spintype == "all") {
42 doSinglets_ = true;
43 doTriplets_ = true;
44 } else if (spintype == "triplet") {
45 doTriplets_ = true;
46 doSinglets_ = false;
47 } else if (spintype == "singlet") {
48 doSinglets_ = true;
49 doTriplets_ = false;
50 } else {
51 throw std::runtime_error(
52 (boost::format(
53 "Choice % for type not known. Available singlet,triplet,all") %
54 spintype)
55 .str());
56 }
57 output_perturbation_ = options.get("use_perturbation").as<bool>();
58 // When true, write monomer energies, transition dipoles, diagnostics,
59 // and raw H/S TB matrices to the XML output. Default false for compact
60 // output in KMC/rate workflows that only need scalar couplings.
62 options.ifExistsReturnElseReturnDefault<bool>("output_tb", false);
63
64 levA_ = options.get("moleculeA.states").as<Index>();
65 levB_ = options.get("moleculeB.states").as<Index>();
66 occA_ = options.get("moleculeA.occLevels").as<Index>();
67 occB_ = options.get("moleculeB.occLevels").as<Index>();
68 unoccA_ = options.get("moleculeA.unoccLevels").as<Index>();
69 unoccB_ = options.get("moleculeB.unoccLevels").as<Index>();
70}
71
72// =============================================================================
73// WriteMatrixToProperty
74// Write an Eigen matrix as space-separated rows into an XML property node.
75// Each row is stored as attribute "row_N". Unit conversion is applied if given.
76// =============================================================================
78 const std::string& name,
79 const Eigen::MatrixXd& mat,
80 double conversion) {
81 tools::Property& mat_prop = prop.add(name, "");
82 mat_prop.setAttribute("rows", mat.rows());
83 mat_prop.setAttribute("cols", mat.cols());
84 for (Index i = 0; i < mat.rows(); i++) {
85 std::string row_str;
86 for (Index j = 0; j < mat.cols(); j++) {
87 if (j > 0) row_str += " ";
88 row_str += (format("%1$1.6e") % (mat(i, j) * conversion)).str();
89 }
90 mat_prop.setAttribute("row_" + std::to_string(i), row_str);
91 }
92}
93
94// =============================================================================
95// WriteToProperty
96// Write the pairwise effective couplings for one (stateA, stateB) pair.
97// Backward compatible: attributes unchanged.
98// =============================================================================
99void BSECoupling::WriteToProperty(Property& summary, const QMState& stateA,
100 const QMState& stateB) const {
101 Property& coupling_summary = summary.add("coupling", "");
102 double JAB_pert = 0;
103 double JAB_diag = 0;
104 if (stateA.Type() == QMStateType::Singlet) {
105 JAB_pert =
106 getSingletCouplingElement(stateA.StateIdx(), stateB.StateIdx(), 0);
107 JAB_diag =
108 getSingletCouplingElement(stateA.StateIdx(), stateB.StateIdx(), 1);
109 } else if (stateA.Type() == QMStateType::Triplet) {
110 JAB_pert =
111 getTripletCouplingElement(stateA.StateIdx(), stateB.StateIdx(), 0);
112 JAB_diag =
113 getTripletCouplingElement(stateA.StateIdx(), stateB.StateIdx(), 1);
114 }
115 coupling_summary.setAttribute("stateA", stateA.ToString());
116 coupling_summary.setAttribute("stateB", stateB.ToString());
117 coupling_summary.setAttribute("j_pert", (format("%1$1.6e") % JAB_pert).str());
118 coupling_summary.setAttribute("j_diag", (format("%1$1.6e") % JAB_diag).str());
119}
120
121// =============================================================================
122// Addoutput
123// Write all results to the XML property tree.
124// Extends the existing pairwise coupling output with:
125// - diagnostics node (xi, PT/RM discrepancy, downfolding_safe flag)
126// - tb_matrices node (raw H and S blocks in FE+CT basis, in eV)
127// The existing <coupling> elements are unchanged for backward compatibility.
128// =============================================================================
129void BSECoupling::Addoutput(Property& type_summary, const Orbitals& orbitalsA,
130 const Orbitals& orbitalsB) const {
131 tools::Property& bsecoupling = type_summary.add(Identify(), "");
132 string algorithm = "j_diag";
134 algorithm = "j_pert";
135 }
136
137 // Lambda to write one spin channel — avoids duplicating singlet/triplet logic
138 auto WriteSpinChannel = [&](const std::string& spin_name,
139 const Eigen::MatrixXd& J_dimer,
140 const Eigen::MatrixXd& S_dimer,
141 const Diagnostics& diag,
142 const Eigen::VectorXd& monA_energies,
143 const Eigen::VectorXd& monB_energies,
144 const std::vector<Eigen::Vector3d>& tdipsA,
145 const std::vector<Eigen::Vector3d>& tdipsB,
146 QMStateType spin_type) {
147 Property& spin_summary = bsecoupling.add(spin_name, "");
148 spin_summary.setAttribute("algorithm", algorithm);
149
150 // --- existing pairwise effective couplings (backward compatible) ---
151 for (Index stateA = 0; stateA < levA_; ++stateA) {
152 QMState qmstateA = QMState(spin_type, stateA, false);
153 for (Index stateB = 0; stateB < levB_; ++stateB) {
154 QMState qmstateB = QMState(spin_type, stateB, false);
155 WriteToProperty(spin_summary, qmstateA, qmstateB);
156 }
157 }
158
159 // --- TB output: monomer energies, diagnostics, raw matrices ---
160 // Only written when output_tb_ = true. For KMC/rate workflows that only
161 // need scalar effective couplings, leave output_tb_ = false.
162 if (output_tb_) {
163 // Monomer excitation energies and transition dipoles
164 {
165 Property& mono_prop = spin_summary.add("monomer_energies", "");
166 Property& propA = mono_prop.add("fragmentA", "");
167 for (Index i = 0; i < monA_energies.size(); i++) {
168 Property& e = propA.add("energy", "");
169 QMState st(spin_type, i, false);
170 e.setAttribute("state", st.ToString());
171 e.setAttribute("eV", (format("%1$1.6e") % monA_energies(i)).str());
172 if (i < static_cast<Index>(tdipsA.size())) {
173 e.setAttribute("tdx", (format("%1$1.6e") % tdipsA[i].x()).str());
174 e.setAttribute("tdy", (format("%1$1.6e") % tdipsA[i].y()).str());
175 e.setAttribute("tdz", (format("%1$1.6e") % tdipsA[i].z()).str());
176 }
177 }
178 Property& propB = mono_prop.add("fragmentB", "");
179 for (Index i = 0; i < monB_energies.size(); i++) {
180 Property& e = propB.add("energy", "");
181 QMState st(spin_type, i, false);
182 e.setAttribute("state", st.ToString());
183 e.setAttribute("eV", (format("%1$1.6e") % monB_energies(i)).str());
184 if (i < static_cast<Index>(tdipsB.size())) {
185 e.setAttribute("tdx", (format("%1$1.6e") % tdipsB[i].x()).str());
186 e.setAttribute("tdy", (format("%1$1.6e") % tdipsB[i].y()).str());
187 e.setAttribute("tdz", (format("%1$1.6e") % tdipsB[i].z()).str());
188 }
189 }
190 }
191
192 // Diagnostics
193 {
194 Property& diag_prop = spin_summary.add("diagnostics", "");
195 diag_prop.setAttribute("xi", (format("%1$1.4f") % diag.xi).str());
196 diag_prop.setAttribute(
197 "pt_rm_discrepancy_eV",
198 (format("%1$1.6e") %
199 (diag.pt_rm_discrepancy * votca::tools::conv::hrt2ev))
200 .str());
201 diag_prop.setAttribute("downfolding_safe",
202 diag.downfolding_safe ? "true" : "false");
203 }
204
205 // Raw TB matrices (H in eV, S dimensionless)
206 // Block layout: [ H_FE_FE H_FE_CT ] / [ H_CT_FE H_CT_CT ]
207 {
208 Index bse_exc = levA_ + levB_;
209 Index ct = J_dimer.rows() - bse_exc;
210
211 Property& tb_prop = spin_summary.add("tb_matrices", "");
212 tb_prop.setAttribute("n_FE", bse_exc);
213 tb_prop.setAttribute("n_CT", ct);
214 tb_prop.setAttribute("n_occA", occA_);
215 tb_prop.setAttribute("n_unoccA", unoccA_);
216 tb_prop.setAttribute("n_occB", occB_);
217 tb_prop.setAttribute("n_unoccB", unoccB_);
218
219 WriteMatrixToProperty(tb_prop, "H_FE_FE",
220 J_dimer.topLeftCorner(bse_exc, bse_exc),
222 WriteMatrixToProperty(tb_prop, "S_FE_FE",
223 S_dimer.topLeftCorner(bse_exc, bse_exc));
224
225 if (ct > 0) {
226 WriteMatrixToProperty(tb_prop, "H_FE_CT",
227 J_dimer.topRightCorner(bse_exc, ct),
229 WriteMatrixToProperty(tb_prop, "S_FE_CT",
230 S_dimer.topRightCorner(bse_exc, ct));
231 WriteMatrixToProperty(tb_prop, "H_CT_CT",
232 J_dimer.bottomRightCorner(ct, ct),
234 WriteMatrixToProperty(tb_prop, "S_CT_CT",
235 S_dimer.bottomRightCorner(ct, ct));
236 }
237 }
238 } // output_tb_
239 };
240
241 if (doSinglets_) {
242 WriteSpinChannel(
247 }
248 if (doTriplets_) {
249 // Triplet states have zero electric transition dipole from the singlet
250 // ground state — pass empty vectors so no tdx/tdy/tdz attributes are
251 // written, making clear that oscillator strengths are not meaningful.
252 const std::vector<Eigen::Vector3d> empty_dipoles;
253 WriteSpinChannel(QMStateType(QMStateType::Triplet).ToLongString(),
256 empty_dipoles, empty_dipoles,
258 }
259}
260
262 Index methodindex) const {
263 return JAB_singlet[methodindex](levelA, levelB + levA_) *
265}
266
268 Index methodindex) const {
269 return JAB_triplet[methodindex](levelA, levelB + levA_) *
271}
272
273Eigen::MatrixXd BSECoupling::SetupCTStates(Index bseA_vtotal, Index bseB_vtotal,
274 Index bseAB_vtotal,
275 Index bseAB_ctotal,
276 const Eigen::MatrixXd& A_AB,
277 const Eigen::MatrixXd& B_AB) const {
278 Index noAB = occA_ * unoccB_;
279 Index noBA = unoccA_ * occB_;
280 Index bseAB_size = bseAB_vtotal * bseAB_ctotal;
281 Eigen::MatrixXd CTstates = Eigen::MatrixXd::Zero(bseAB_size, noAB + noBA);
282
283 auto A_occ = A_AB.middleCols(bseA_vtotal - occA_, occA_);
284 auto A_unocc = A_AB.middleCols(bseA_vtotal, unoccA_);
285 auto B_occ = B_AB.middleCols(bseB_vtotal - occB_, occB_);
286 auto B_unocc = B_AB.middleCols(bseB_vtotal, unoccB_);
287
288 const Eigen::MatrixXd A_occ_occ = A_occ.topRows(bseAB_vtotal);
289 const Eigen::MatrixXd B_unocc_unocc = B_unocc.bottomRows(bseAB_ctotal);
290
291 // notation AB is CT states with A+B-, BA is the counterpart
293 << TimeStamp() << " Setting up CT-states" << flush;
294
295#pragma omp parallel for
296 for (Index a_occ = 0; a_occ < occA_; a_occ++) {
297 for (Index b_unocc = 0; b_unocc < unoccB_; b_unocc++) {
298 Index index = a_occ * unoccB_ + b_unocc;
299 Eigen::MatrixXd Coeff =
300 B_unocc_unocc.col(b_unocc) * A_occ_occ.col(a_occ).transpose();
301 CTstates.col(index) =
302 Eigen::Map<Eigen::VectorXd>(Coeff.data(), bseAB_size);
303 }
304 }
306 << TimeStamp() << " " << noBA << " CT states A+B- created" << flush;
307
308 const Eigen::MatrixXd A_unocc_unocc = A_unocc.bottomRows(bseAB_ctotal);
309 const Eigen::MatrixXd B_occ_occ = B_occ.topRows(bseAB_vtotal);
310
311#pragma omp parallel for
312 for (Index b_occ = 0; b_occ < occB_; b_occ++) {
313 for (Index a_unocc = 0; a_unocc < unoccA_; a_unocc++) {
314 Index index = b_occ * unoccA_ + a_unocc + noAB;
315 Eigen::MatrixXd Coeff =
316 A_unocc_unocc.col(a_unocc) * B_occ_occ.col(b_occ).transpose();
317 CTstates.col(index) =
318 Eigen::Map<Eigen::VectorXd>(Coeff.data(), bseAB_size);
319 }
320 }
322 << TimeStamp() << " " << noBA << " CT states A-B+ created" << flush;
323 return CTstates;
324}
325
327 const Eigen::MatrixXd& BSE_Coeffs, const Eigen::MatrixXd& X_AB,
328 Index bseX_vtotal, Index bseX_ctotal, Index bseAB_vtotal,
329 Index bseAB_ctotal) const {
330 Index bseAB_size = bseAB_vtotal * bseAB_ctotal;
331 auto X_occ = X_AB.leftCols(bseX_vtotal);
332 auto X_unocc = X_AB.rightCols(bseX_ctotal);
333 const Eigen::MatrixXd X_occ_occ = X_occ.topRows(bseAB_vtotal);
334 const Eigen::MatrixXd X_unocc_unocc = X_unocc.bottomRows(bseAB_ctotal);
335 Eigen::MatrixXd result = Eigen::MatrixXd::Zero(bseAB_size, BSE_Coeffs.cols());
336 // no pragma here because often we will only have one Coeff
337 for (Index i = 0; i < BSE_Coeffs.cols(); i++) {
338 Eigen::VectorXd coeff = BSE_Coeffs.col(i);
339 Eigen::Map<Eigen::MatrixXd> coeffmatrix =
340 Eigen::Map<Eigen::MatrixXd>(coeff.data(), bseX_ctotal, bseX_vtotal);
341 Eigen::MatrixXd proj = X_unocc_unocc * coeffmatrix * X_occ_occ.transpose();
342 result.col(i) = Eigen::Map<Eigen::VectorXd>(proj.data(), proj.size());
343 }
344 return result;
345}
346
347int GetSign(double value) {
348 if (value < 0) {
349 return -1;
350 } else if (value > 0) {
351 return 1;
352 }
353 return 0;
354}
355
357 const Orbitals& orbitalsB,
358 const Orbitals& orbitalsAB) {
359 citations::Cite("Wehner:2017");
361 << TimeStamp() << " Calculating exciton couplings" << flush;
362 XTP_LOG(Log::error, *pLog_) << TimeStamp() << " Using "
363 << OPENMP::getMaxThreads() << " threads" << flush;
364
365 CheckAtomCoordinates(orbitalsA, orbitalsB, orbitalsAB);
366
367 Index basisB = orbitalsB.getBasisSetSize();
368 Index basisA = orbitalsA.getBasisSetSize();
369 if ((basisA == 0) || (basisB == 0)) {
370 throw std::runtime_error("Basis set size is not stored in monomers");
371 }
372
373 // get exciton information of molecule A
374 Index bseA_cmax = orbitalsA.getBSEcmax();
375 Index bseA_cmin = orbitalsA.getBSEcmin();
376 Index bseA_vmax = orbitalsA.getBSEvmax();
377 Index bseA_vmin = orbitalsA.getBSEvmin();
378 Index bseA_vtotal = bseA_vmax - bseA_vmin + 1;
379 Index bseA_ctotal = bseA_cmax - bseA_cmin + 1;
380 Index bseA_total = bseA_vtotal + bseA_ctotal;
381 Index bseA_size = bseA_vtotal * bseA_ctotal;
382 Index bseA_singlet_exc = orbitalsA.BSESinglets().eigenvectors().cols();
383 Index bseA_triplet_exc = orbitalsA.BSETriplets().eigenvectors().cols();
384
386 << TimeStamp() << " molecule A has " << bseA_singlet_exc
387 << " singlet excitons with dimension " << bseA_size << flush;
389 << TimeStamp() << " molecule A has " << bseA_triplet_exc
390 << " triplet excitons with dimension " << bseA_size << flush;
391
392 // get exciton information of molecule B
393 Index bseB_cmax = orbitalsB.getBSEcmax();
394 Index bseB_cmin = orbitalsB.getBSEcmin();
395 Index bseB_vmax = orbitalsB.getBSEvmax();
396 Index bseB_vmin = orbitalsB.getBSEvmin();
397 Index bseB_vtotal = bseB_vmax - bseB_vmin + 1;
398 Index bseB_ctotal = bseB_cmax - bseB_cmin + 1;
399 Index bseB_total = bseB_vtotal + bseB_ctotal;
400 Index bseB_size = bseB_vtotal * bseB_ctotal;
401 Index bseB_singlet_exc = orbitalsB.BSESinglets().eigenvectors().cols();
402 Index bseB_triplet_exc = orbitalsB.BSETriplets().eigenvectors().cols();
403
405 << TimeStamp() << " molecule B has " << bseB_singlet_exc
406 << " singlet excitons with dimension " << bseB_size << flush;
408 << TimeStamp() << " molecule B has " << bseB_triplet_exc
409 << " triplet excitons with dimension " << bseB_size << flush;
410
411 if (doSinglets_) {
412 if (levA_ > bseA_singlet_exc) {
414 << " Number of excitons you want is greater "
415 "than stored for molecule "
416 "A. Setting to max number available"
417 << flush;
418 levA_ = bseA_singlet_exc;
419 }
420 if (levB_ > bseB_singlet_exc) {
422 << " Number of excitons you want is greater "
423 "than stored for molecule "
424 "B. Setting to max number available"
425 << flush;
426 levB_ = bseB_singlet_exc;
427 }
428 }
429 if (doTriplets_) {
430 if (levA_ > bseA_triplet_exc) {
432 << TimeStamp()
433 << " Number of Frenkel states you want is greater than stored for "
434 "molecule A. Setting to max number available"
435 << flush;
436 levA_ = bseA_triplet_exc;
437 }
438 if (levB_ > bseB_triplet_exc) {
440 << TimeStamp()
441 << " Number of Frenkel states you want is greater than stored for "
442 "molecule B. Setting to max number available"
443 << flush;
444 levB_ = bseB_triplet_exc;
445 }
446 }
447 if (unoccA_ > bseA_ctotal || unoccA_ < 0) {
449 << TimeStamp()
450 << " Number of unoccupied orbitals in molecule A for CT creation "
451 "exceeds number of KS-orbitals in BSE"
452 << flush;
453 unoccA_ = bseA_ctotal;
454 }
455 if (unoccB_ > bseB_ctotal || unoccB_ < 0) {
457 << TimeStamp()
458 << " Number of unoccupied orbitals in molecule B for CT creation "
459 "exceeds number of KS-orbitals in BSE"
460 << flush;
461 unoccB_ = bseB_ctotal;
462 }
463 if (occA_ > bseA_vtotal || occA_ < 0) {
465 << TimeStamp()
466 << " Number of occupied orbitals in molecule A for CT creation "
467 "exceeds number of KS-orbitals in BSE"
468 << flush;
469 occA_ = bseA_vtotal;
470 }
471 if (occB_ > bseB_vtotal || occB_ < 0) {
473 << TimeStamp()
474 << " Number of occupied orbitals in molecule B for CT creation "
475 "exceeds number of KS-orbitals in BSE"
476 << flush;
477 occB_ = bseB_vtotal;
478 }
479
480 // get exciton information of pair AB
481 Index bseAB_cmax = orbitalsAB.getBSEcmax();
482 Index bseAB_cmin = orbitalsAB.getBSEcmin();
483 Index bseAB_vmax = orbitalsAB.getBSEvmax();
484 Index bseAB_vmin = orbitalsAB.getBSEvmin();
485 Index bseAB_vtotal = bseAB_vmax - bseAB_vmin + 1;
486 Index bseAB_ctotal = bseAB_cmax - bseAB_cmin + 1;
487 Index bseAB_total = bseAB_vtotal + bseAB_ctotal;
488 Index bseAB_size = bseAB_vtotal * bseAB_ctotal;
489
491 << TimeStamp() << " levels used for BSE of molA: " << bseA_vmin
492 << " to " << bseA_cmax << " total: " << bseA_total << flush;
494 << TimeStamp() << " levels used for BSE of molB: " << bseB_vmin
495 << " to " << bseB_cmax << " total: " << bseB_total << flush;
497 << TimeStamp() << " levels used for BSE of dimer AB: " << bseAB_vmin
498 << " to " << bseAB_cmax << " total: " << bseAB_total << flush;
499
500 Eigen::MatrixXd MOsA =
501 orbitalsA.MOs().eigenvectors().middleCols(bseA_vmin, bseA_total);
502 Eigen::MatrixXd MOsB =
503 orbitalsB.MOs().eigenvectors().middleCols(bseB_vmin, bseB_total);
504 Eigen::MatrixXd MOsAB =
505 orbitalsAB.MOs().eigenvectors().middleCols(bseAB_vmin, bseAB_total);
506
508 << TimeStamp() << " Calculating overlap matrix for basisset: "
509 << orbitalsAB.getDFTbasisName() << flush;
510
511 Eigen::MatrixXd overlap = CalculateOverlapMatrix(orbitalsAB) * MOsAB;
513 << TimeStamp() << " Projecting monomers onto dimer orbitals" << flush;
514
515 Eigen::MatrixXd A_AB = overlap.topRows(basisA).transpose() * MOsA;
516 Eigen::MatrixXd B_AB = overlap.bottomRows(basisB).transpose() * MOsB;
517 Eigen::VectorXd mag_A = A_AB.colwise().squaredNorm();
518 if (mag_A.any() < 0.95) {
520 << "\nWarning: "
521 << "Projection of orbitals of monomer A on dimer is insufficient,mag="
522 << mag_A.minCoeff() << flush;
523 }
524 Eigen::VectorXd mag_B = B_AB.colwise().squaredNorm();
525 if (mag_B.any() < 0.95) {
527 << "\nWarning: "
528 << "Projection of orbitals of monomer B on dimer is insufficient,mag="
529 << mag_B.minCoeff() << flush;
530 }
531
532 AOBasis dftbasis = orbitalsAB.getDftBasis();
533 AOBasis auxbasis = orbitalsAB.getAuxBasis();
534 TCMatrix_gwbse Mmn;
535 Mmn.Initialize(auxbasis.AOBasisSize(), orbitalsAB.getRPAmin(),
536 orbitalsAB.getGWAmax(), orbitalsAB.getRPAmin(),
537 orbitalsAB.getRPAmax());
538 Mmn.Fill(auxbasis, dftbasis, orbitalsAB.MOs().eigenvectors());
539
540 const Eigen::MatrixXd& qpcoeff = orbitalsAB.QPdiag().eigenvectors();
541 Eigen::MatrixXd Hqp = qpcoeff *
542 orbitalsAB.QPdiag().eigenvalues().asDiagonal() *
543 qpcoeff.transpose();
544
545 BSE::options opt;
546 opt.cmax = orbitalsAB.getBSEcmax();
547 opt.homo = orbitalsAB.getHomo();
548 opt.qpmin = orbitalsAB.getGWAmin();
549 opt.qpmax = orbitalsAB.getGWAmax();
550 opt.rpamax = orbitalsAB.getRPAmax();
551 opt.rpamin = orbitalsAB.getRPAmin();
552 opt.useTDA = true;
553 opt.vmin = orbitalsAB.getBSEvmin();
554 opt.use_Hqp_offdiag = orbitalsAB.GetFlagUseHqpOffdiag();
555 BSE bse(*pLog_, Mmn);
556 bse.configure(opt, orbitalsAB.RPAInputEnergies(), Hqp);
557 XTP_LOG(Log::error, *pLog_) << TimeStamp() << " Setup BSE operator" << flush;
558
559 if (doSinglets_) {
561 << TimeStamp() << " Evaluating singlets" << flush;
563 << TimeStamp() << " Setup Hamiltonian" << flush;
564
565 // Store isolated monomer singlet excitation energies [eV].
566 // These are pairwise-consistent site energies for TB assembly —
567 // independent of which dimer partner is used.
569 orbitalsA.BSESinglets().eigenvalues().head(levA_) *
572 orbitalsB.BSESinglets().eigenvalues().head(levB_) *
574
575 Eigen::MatrixXd FE_AB = Eigen::MatrixXd::Zero(bseAB_size, levA_ + levB_);
576 const Eigen::MatrixXd bseA =
577 orbitalsA.BSESinglets().eigenvectors().leftCols(levA_);
578 FE_AB.leftCols(levA_) = ProjectFrenkelExcitons(
579 bseA, A_AB, bseA_vtotal, bseA_ctotal, bseAB_vtotal, bseAB_ctotal);
580 const Eigen::MatrixXd bseB =
581 orbitalsB.BSESinglets().eigenvectors().leftCols(levB_);
582 FE_AB.rightCols(levB_) = ProjectFrenkelExcitons(
583 bseB, B_AB, bseB_vtotal, bseB_ctotal, bseAB_vtotal, bseAB_ctotal);
584 Eigen::MatrixXd CTStates = SetupCTStates(
585 bseA_vtotal, bseB_vtotal, bseAB_vtotal, bseAB_ctotal, A_AB, B_AB);
587 ProjectExcitons(FE_AB, CTStates, bse.getSingletOperator_TDA(),
590 << TimeStamp() << " calculated singlet couplings " << flush;
591 }
592 if (doTriplets_) {
594 << TimeStamp() << " Evaluating triplets" << flush;
595
596 // Store isolated monomer triplet excitation energies [eV].
598 orbitalsA.BSETriplets().eigenvalues().head(levA_) *
601 orbitalsB.BSETriplets().eigenvalues().head(levB_) *
603
604 Eigen::MatrixXd FE_AB = Eigen::MatrixXd::Zero(bseAB_size, levA_ + levB_);
605 const Eigen::MatrixXd bseA =
606 orbitalsA.BSETriplets().eigenvectors().leftCols(levA_);
607 FE_AB.leftCols(levA_) = ProjectFrenkelExcitons(
608 bseA, A_AB, bseA_vtotal, bseA_ctotal, bseAB_vtotal, bseAB_ctotal);
609 const Eigen::MatrixXd bseB =
610 orbitalsB.BSETriplets().eigenvectors().leftCols(levB_);
611 FE_AB.rightCols(levB_) = ProjectFrenkelExcitons(
612 bseB, B_AB, bseB_vtotal, bseB_ctotal, bseAB_vtotal, bseAB_ctotal);
613 Eigen::MatrixXd CTStates = SetupCTStates(
614 bseA_vtotal, bseB_vtotal, bseAB_vtotal, bseAB_ctotal, A_AB, B_AB);
616 ProjectExcitons(FE_AB, CTStates, bse.getTripletOperator_TDA(),
619 << TimeStamp() << " calculated triplet couplings " << flush;
620 }
622 << TimeStamp() << " Done with exciton couplings" << flush;
623}
624
625// =============================================================================
626// OrthogonalizeCTs
627// Merge FE and CT projection vectors into a single matrix without any
628// pre-orthogonalization. The non-orthogonality between FE and CT states,
629// and within each block, is handled correctly downstream:
630// - For the reduction method: by the joint Lowdin in CalcJ_dimer.
631// - For TB use of J_dimer/S_dimer: by the generalized eigenvalue problem
632// or SRG.
633//
634// Pre-orthogonalizing CT states against FE states (as done previously) used
635// the incorrect projector P = F*F^T, which is only exact when FE_AB columns
636// are orthonormal. Since projected monomer FE states are generally not
637// orthonormal, this introduced a systematic error — particularly for larger
638// CT manifolds (many occ/unocc pairs) where higher orbital pairs have
639// non-negligible overlap with the FE space. Removing the pre-orthogonalization
640// preserves the physical FE and CT state characters and lets the subsequent
641// linear algebra handle the non-orthogonality correctly and consistently.
642//
643// A linear dependence check is performed on the CT states: if any CT state
644// has norm below threshold after projection onto the dimer basis, it indicates
645// near-linear dependence with other basis states and is flagged.
646// =============================================================================
647Eigen::MatrixXd BSECoupling::OrthogonalizeCTs(Eigen::MatrixXd& FE_AB,
648 Eigen::MatrixXd& CTStates) const {
649 Index ct = CTStates.cols();
650 Index bse_exc = levA_ + levB_;
651 Index bseAB_size = FE_AB.rows();
652
653 // Check for near-linear dependence in CT states via their norms.
654 // A very small norm indicates a CT state that is nearly identical to
655 // another basis state and would cause numerical problems in the Lowdin step.
656 if (ct > 0) {
657 Eigen::VectorXd norm = CTStates.colwise().norm();
658 Index minstateindex = 0;
659 double minnorm = norm.minCoeff(&minstateindex);
660 if (minnorm < 0.95) {
662 << TimeStamp() << " WARNING: CT-state " << minstateindex
663 << " norm is only " << minnorm
664 << " -- near-linear dependence in projection basis" << flush;
665 }
667 << TimeStamp() << " Merging " << ct
668 << " CT states into projection (no pre-orthogonalization)" << flush;
669 }
670
671 Eigen::MatrixXd projection(bseAB_size, bse_exc + ct);
673 << TimeStamp() << " merging projections into one vector " << flush;
674 projection.leftCols(bse_exc) = FE_AB;
675 FE_AB.resize(0, 0);
676 if (ct > 0) {
677 projection.rightCols(ct) = CTStates;
678 }
679 CTStates.resize(0, 0);
680 return projection;
681}
682
683// =============================================================================
684// CalcJ_dimer
685// Form J_dimer and S_dimer from the projection, expose them via output
686// references for TB use, then Lowdin-orthogonalize to produce J_ortho.
687// The block structure of J_dimer and S_dimer is:
688// [ H_FE_FE H_FE_CT ] [ S_FE_FE S_FE_CT ]
689// [ H_CT_FE H_CT_CT ] [ S_CT_FE S_CT_CT ]
690// =============================================================================
691template <class BSE_OPERATOR>
693 Eigen::MatrixXd& projection,
694 Eigen::MatrixXd& J_dimer_out,
695 Eigen::MatrixXd& S_dimer_out) const {
697 << TimeStamp() << " Setting up coupling matrix size "
698 << projection.cols() << flush;
699
700 // this only works for hermitian/symmetric H so only in TDA
701 J_dimer_out = projection.transpose() * (H * projection).eval();
702
704 << TimeStamp() << " Setting up overlap matrix size "
705 << projection.cols() << flush;
706 S_dimer_out = projection.transpose() * projection;
707
708 projection.resize(0, 0);
709
711 << "---------------------------------------" << flush;
712 XTP_LOG(Log::debug, *pLog_) << " J_dimer_[Hrt]" << flush;
713 XTP_LOG(Log::debug, *pLog_) << J_dimer_out << flush;
714 XTP_LOG(Log::debug, *pLog_) << " S_dimer_" << flush;
715 XTP_LOG(Log::debug, *pLog_) << S_dimer_out << flush;
717 << "---------------------------------------" << flush;
718
719 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(S_dimer_out);
721 << TimeStamp() << " Smallest value of dimer overlapmatrix is "
722 << es.eigenvalues()(0) << flush;
723
724 Eigen::MatrixXd Sm1 = es.operatorInverseSqrt();
725 Eigen::MatrixXd J_ortho = Sm1 * J_dimer_out * Sm1;
726
728 << "---------------------------------------" << flush;
729 XTP_LOG(Log::debug, *pLog_) << " J_ortho_[Hrt]" << flush;
730 XTP_LOG(Log::debug, *pLog_) << J_ortho << flush;
731 XTP_LOG(Log::debug, *pLog_) << " S_-1/2" << flush;
732 XTP_LOG(Log::debug, *pLog_) << Sm1 << flush;
734 << "---------------------------------------" << flush;
735
736 return J_ortho;
737}
738
739// =============================================================================
740// ComputeDiagnostics
741// Assess whether CT downfolding is reliable for this pair.
742// xi: max |H_FE_CT| / |E_FE - E_CT| over all FE-CT pairs.
743// Small xi => perturbation theory is safe.
744// pt_rm_discrepancy: max |J_pert - J_diag| over all FE pairs [Hrt].
745// Large discrepancy flags near-resonant CT states.
746// downfolding_safe: true if xi < 0.3 and discrepancy < 1e-4 Hrt.
747// Note: J_dimer here is the raw pre-Lowdin matrix (in Hrt), not J_ortho.
748// =============================================================================
750 const Eigen::MatrixXd& J_dimer, const Eigen::MatrixXd& J_pert,
751 const Eigen::MatrixXd& J_diag) const {
752 Index bse_exc = levA_ + levB_;
753 Index ct = J_dimer.rows() - bse_exc;
754 Diagnostics diag;
755
756 if (ct > 0) {
757 for (Index i = 0; i < bse_exc; i++) {
758 double Ei = J_dimer(i, i);
759 for (Index k = bse_exc; k < bse_exc + ct; k++) {
760 double Ek = J_dimer(k, k);
761 double dE = std::abs(Ei - Ek);
762 double coupling = std::abs(J_dimer(i, k));
763 if (dE > 1e-10) {
764 diag.xi = std::max(diag.xi, coupling / dE);
765 } else {
766 // exact degeneracy — downfolding is meaningless
767 diag.xi = std::numeric_limits<double>::infinity();
768 }
769 }
770 }
771 }
772
773 // PT vs RM discrepancy over all FE pairs
774 for (Index i = 0; i < levA_; i++) {
775 for (Index j = 0; j < levB_; j++) {
776 Index jd = j + levA_;
777 double diff = std::abs(J_pert(i, jd) - J_diag(i, jd));
778 diag.pt_rm_discrepancy = std::max(diag.pt_rm_discrepancy, diff);
779 }
780 }
781
782 diag.downfolding_safe = std::isfinite(diag.xi) && (diag.xi < 0.3) &&
783 (diag.pt_rm_discrepancy < 1e-4);
784
785 return diag;
786}
787
788// =============================================================================
789// ProjectExcitons
790// Top-level driver: orthogonalize CTs, form J_dimer/S_dimer, Lowdin-
791// orthogonalize, run perturbation and reduction methods, compute diagnostics.
792// Raw J_dimer and S_dimer (pre-Lowdin, in Hrt) and diagnostics are returned
793// via output references for storage as member variables.
794// =============================================================================
795template <class BSE_OPERATOR>
796std::array<Eigen::MatrixXd, 2> BSECoupling::ProjectExcitons(
797 Eigen::MatrixXd& FE_AB, Eigen::MatrixXd& CTStates, BSE_OPERATOR H,
798 Eigen::MatrixXd& J_dimer_out, Eigen::MatrixXd& S_dimer_out,
799 Diagnostics& diag_out) const {
800
801 Eigen::MatrixXd projection = OrthogonalizeCTs(FE_AB, CTStates);
802 Eigen::MatrixXd J_ortho =
803 CalcJ_dimer(H, projection, J_dimer_out, S_dimer_out);
804
805 std::array<Eigen::MatrixXd, 2> J;
807 << TimeStamp() << " Running Perturbation algorithm" << flush;
808 J[0] = Perturbation(J_ortho);
810 << TimeStamp() << " Running Projection algorithm" << flush;
811 J[1] = Fulldiag(J_ortho);
812
813 diag_out = ComputeDiagnostics(J_dimer_out, J[0], J[1]);
814
816 << TimeStamp() << " Diagnostics: xi=" << diag_out.xi
817 << " PT/RM discrepancy[Hrt]=" << diag_out.pt_rm_discrepancy
818 << " downfolding_safe=" << (diag_out.downfolding_safe ? "true" : "false")
819 << flush;
820
822 << "---------------------------------------" << flush;
823 XTP_LOG(Log::debug, *pLog_) << "Jeff_pert[Hrt]" << flush;
824 XTP_LOG(Log::debug, *pLog_) << J[0] << flush;
825 XTP_LOG(Log::debug, *pLog_) << "Jeff_diag[Hrt]" << flush;
826 XTP_LOG(Log::debug, *pLog_) << J[1] << flush;
828 << "---------------------------------------" << flush;
829
830 return J;
831}
832
833// =============================================================================
834// Perturbation
835// First-order perturbation theory correction for CT-mediated coupling.
836// The factor of 0.5 in the correction term comes from the symmetric treatment
837// of corrections |delta Phi_A> and |delta Phi_B> in eq 28 of Wehner/Baumeier
838// JCTC 2017 — each contributes half the total CT-mediated coupling.
839// =============================================================================
841 const Eigen::MatrixXd& J_dimer) const {
842 Index bse_exc = levA_ + levB_;
843 Index ct = J_dimer.rows() - bse_exc;
844 Eigen::MatrixXd J_result = J_dimer;
845 if (ct > 0) {
846 Eigen::MatrixXd transformation =
847 Eigen::MatrixXd::Identity(J_dimer.rows(), J_dimer.cols());
848 Eigen::MatrixXd Ct = J_dimer.bottomRightCorner(ct, ct);
849 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(Ct);
850 transformation.bottomRightCorner(ct, ct) = es.eigenvectors();
851 Ct.resize(0, 0);
852
853 XTP_LOG(Log::debug, *pLog_) << "FE state hamiltonian" << flush;
855 << J_dimer.topLeftCorner(bse_exc, bse_exc) << flush;
856 XTP_LOG(Log::debug, *pLog_) << "eigenvalues of CT states" << flush;
857 XTP_LOG(Log::debug, *pLog_) << es.eigenvalues().transpose() << flush;
858
859 J_result = transformation.transpose() * J_dimer * transformation;
861 << "---------------------------------------" << flush;
862 XTP_LOG(Log::debug, *pLog_) << " J_ortho_[Hrt] CT-state diag" << flush;
863 XTP_LOG(Log::debug, *pLog_) << J_result << flush;
865 << "---------------------------------------" << flush;
866 }
867
868 Eigen::MatrixXd Jmatrix = Eigen::MatrixXd::Zero(bse_exc, bse_exc);
869 for (Index stateA = 0; stateA < levA_; stateA++) {
870 double Ea = J_result(stateA, stateA);
871 for (Index stateB = 0; stateB < levB_; stateB++) {
872 Index stateBd = stateB + levA_;
874 << TimeStamp() << " Calculating coupling between exciton A"
875 << stateA + 1 << " and exciton B" << stateB + 1 << flush;
876 double J = J_result(stateA, stateBd);
877 double Eb = J_result(stateBd, stateBd);
878 for (Index k = bse_exc; k < (bse_exc + ct); k++) {
879 double Eab = J_result(k, k);
880 if (std::abs(Eab - Ea) < 0.001) {
882 << TimeStamp() << "Energydifference between state A "
883 << stateA + 1 << "and CT state " << k + 1 << " is " << Eab - Ea
884 << "[Hrt]" << flush;
885 }
886 if (std::abs(Eab - Eb) < 0.001) {
888 << TimeStamp() << "Energydifference between state B "
889 << stateB + 1 << "and CT state " << k + 1 << " is " << Eab - Eb
890 << "[Hrt]" << flush;
891 }
892 // Factor 0.5 from symmetric first-order correction: see eq 28,
893 // Wehner/Baumeier JCTC 2017. Each of |delta Phi_A> and |delta Phi_B>
894 // contributes equally, giving the 1/2 * (1/Delta_A + 1/Delta_B) form.
895 J += 0.5 * J_result(k, stateA) * J_result(k, stateBd) *
896 (1.0 / (Ea - Eab) + 1.0 / (Eb - Eab));
897 }
898 Jmatrix(stateA, stateBd) = J;
899 Jmatrix(stateBd, stateA) = J;
900 }
901 }
902 return Jmatrix;
903}
904
905// =============================================================================
906// Fulldiag (reduction method)
907// Diagonalizes J_ortho (full FE+CT system), then for each (stateA, stateB)
908// pair independently extracts the two eigenvectors most resembling those
909// states, projects to the 2x2 FE subspace, Lowdin-orthogonalizes, and
910// rotates back to recover the effective coupling. This per-pair 2x2 approach
911// is consistent with the reduction method of Wehner/Baumeier JCTC 2017 and
912// is robust at CT/FE near-resonance where perturbation theory diverges.
913// =============================================================================
914Eigen::MatrixXd BSECoupling::Fulldiag(const Eigen::MatrixXd& J_dimer) const {
915 Index bse_exc = levA_ + levB_;
916 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(J_dimer);
918 << "---------------------------------------" << flush;
919 XTP_LOG(Log::debug, *pLog_) << "Eigenvectors of J" << flush;
920 XTP_LOG(Log::debug, *pLog_) << es.eigenvectors() << flush;
921 XTP_LOG(Log::debug, *pLog_) << "J_eigenvalues[Hrt]" << flush;
922 XTP_LOG(Log::debug, *pLog_) << es.eigenvalues() << flush;
924 << "---------------------------------------" << flush;
925
926 Eigen::MatrixXd Jmat = Eigen::MatrixXd::Zero(bse_exc, bse_exc);
927 for (Index stateA = 0; stateA < levA_; stateA++) {
928 for (Index stateB = 0; stateB < levB_; stateB++) {
929 Index stateBd = stateB + levA_;
931 << TimeStamp() << " Calculating coupling between exciton A"
932 << stateA + 1 << " and exciton B" << stateB + 1 << flush;
933
934 std::array<int, 2> indexes;
935 std::array<int, 2> signs;
936
937 // Find the eigenstates closest to stateA and stateBd by maximum overlap
938 es.eigenvectors().row(stateA).cwiseAbs().maxCoeff(&indexes[0]);
939 es.eigenvectors().row(stateBd).cwiseAbs().maxCoeff(&indexes[1]);
940 if (indexes[0] == indexes[1]) {
941 // same eigenvector selected for both: pick next best for stateBd
942 Eigen::RowVectorXd stateamplitudes =
943 es.eigenvectors().row(stateBd).cwiseAbs();
944 stateamplitudes[indexes[1]] = 0.0;
945 stateamplitudes.maxCoeff(&indexes[1]);
946 }
947
948 signs[0] = GetSign(es.eigenvectors()(stateA, indexes[0]));
949 signs[1] = GetSign(es.eigenvectors()(stateBd, indexes[1]));
950
952 << TimeStamp() << " Order is: [Initial state n->nth eigenvalue]"
953 << flush;
954 XTP_LOG(Log::info, *pLog_) << " A" << stateA + 1 << ":" << stateA + 1
955 << "->" << indexes[0] + 1 << " ";
956 XTP_LOG(Log::info, *pLog_) << " B" << stateB + 1 << ":" << stateBd + 1
957 << "->" << indexes[1] + 1 << " " << flush;
958
959 // Build 2x2 transformation matrix from the two selected eigenvectors
960 // (rows stateA and stateBd only) and diagonal eigenvalue matrix
961 Eigen::Matrix2d Emat = Eigen::Matrix2d::Zero();
962 Eigen::Matrix2d Tmat = Eigen::Matrix2d::Zero();
963 for (Index i = 0; i < 2; i++) {
964 Index k = indexes[i];
965 double sign = signs[i];
966 Tmat(0, i) = sign * es.eigenvectors()(stateA, k);
967 Tmat(1, i) = sign * es.eigenvectors()(stateBd, k);
968 Emat(i, i) = es.eigenvalues()(k);
969 }
970 Tmat.colwise().normalize();
971
972 if (Tmat.determinant() < 0) {
974 << " Reduced state matrix is not in a right handed basis, "
975 "multiplying second eigenvector by -1 "
976 << flush;
977 Tmat.col(1) *= -1;
978 }
979
981 << "---------------------------------------" << flush;
982 XTP_LOG(Log::debug, *pLog_) << " T_" << flush;
983 XTP_LOG(Log::debug, *pLog_) << Tmat << flush;
984
985 // Lowdin orthogonalize the 2x2 projected subspace
986 Eigen::Matrix2d S_small = Tmat * Tmat.transpose();
987 XTP_LOG(Log::debug, *pLog_) << "S_small" << flush;
988 XTP_LOG(Log::debug, *pLog_) << S_small << flush;
989
990 Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> ss(S_small);
991 Eigen::Matrix2d sm1 = ss.operatorInverseSqrt();
992 Emat = sm1 * Emat * sm1;
993
995 << TimeStamp() << " Smallest value of dimer overlapmatrix is "
996 << ss.eigenvalues()(0) << flush;
997 XTP_LOG(Log::debug, *pLog_) << "S-1/2" << flush;
998 XTP_LOG(Log::debug, *pLog_) << sm1 << flush;
999 XTP_LOG(Log::debug, *pLog_) << "E_ortho" << flush;
1000 XTP_LOG(Log::debug, *pLog_) << Emat << flush;
1001
1002 Tmat = Tmat * sm1;
1003 XTP_LOG(Log::debug, *pLog_) << "T_ortho" << flush;
1004 XTP_LOG(Log::debug, *pLog_) << Tmat << flush;
1006 << "---------------------------------------" << flush;
1007
1008 // H_eff = T * E * T^T; off-diagonal is the effective coupling
1009 Eigen::Matrix2d J_small = Tmat * Emat * Tmat.transpose();
1010 XTP_LOG(Log::debug, *pLog_) << "T_ortho*E_ortho*T_ortho^T" << flush;
1011 XTP_LOG(Log::debug, *pLog_) << J_small << flush;
1012
1013 Jmat(stateA, stateBd) = J_small(0, 1);
1014 Jmat(stateBd, stateA) = J_small(1, 0);
1015 }
1016 }
1017 return Jmat;
1018}
1019
1020} // namespace xtp
1021} // namespace votca
const Eigen::VectorXd & eigenvalues() const
Definition eigensystem.h:30
const Eigen::MatrixXd & eigenvectors() const
Definition eigensystem.h:33
class to manage program options with xml serialization functionality
Definition property.h:55
Property & add(const std::string &key, const std::string &value)
add a new property to structure
Definition property.cc:108
Property & get(const std::string &key)
get existing property
Definition property.cc:79
T as() const
return value as type
Definition property.h:283
T ifExistsReturnElseReturnDefault(const std::string &key, T defaultvalue) const
Definition property.h:332
void setAttribute(const std::string &attribute, const T &value)
set an attribute
Definition property.h:325
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index AOBasisSize() const
Definition aobasis.h:46
Eigen::VectorXd monomerA_energies_triplet_
Eigen::MatrixXd J_dimer_triplet_
double getSingletCouplingElement(Index levelA, Index levelB, Index methodindex) const
Eigen::VectorXd monomerB_energies_singlet_
double getTripletCouplingElement(Index levelA, Index levelB, Index methodindex) const
std::string Identify() const
Definition bsecoupling.h:42
void CalculateCouplings(const Orbitals &orbitalsA, const Orbitals &orbitalsB, const Orbitals &orbitalsAB) override
evaluates electronic couplings
void Addoutput(tools::Property &type_summary, const Orbitals &orbitalsA, const Orbitals &orbitalsB) const override
void WriteToProperty(tools::Property &summary, const QMState &stateA, const QMState &stateB) const
Diagnostics ComputeDiagnostics(const Eigen::MatrixXd &J_dimer, const Eigen::MatrixXd &J_pert, const Eigen::MatrixXd &J_diag) const
Compute diagnostics from raw J_dimer and the two effective coupling matrices.
std::array< Eigen::MatrixXd, 2 > JAB_triplet
Eigen::MatrixXd Perturbation(const Eigen::MatrixXd &J_dimer) const
Eigen::VectorXd monomerB_energies_triplet_
std::array< Eigen::MatrixXd, 2 > ProjectExcitons(Eigen::MatrixXd &FE_AB, Eigen::MatrixXd &CTStates, BSE_OPERATOR H, Eigen::MatrixXd &J_dimer_out, Eigen::MatrixXd &S_dimer_out, Diagnostics &diag_out) const
Compute J_pert and J_diag, and store raw J_dimer/S_dimer and diagnostics for later output.
void Initialize(tools::Property &options) override
Eigen::MatrixXd S_dimer_singlet_
Eigen::VectorXd monomerA_energies_singlet_
Eigen::MatrixXd S_dimer_triplet_
std::array< Eigen::MatrixXd, 2 > JAB_singlet
Eigen::MatrixXd ProjectFrenkelExcitons(const Eigen::MatrixXd &BSE_Coeffs, const Eigen::MatrixXd &X_AB, Index bseX_vtotal, Index bseX_ctotal, Index bseAB_vtotal, Index bseAB_ctotal) const
static void WriteMatrixToProperty(tools::Property &prop, const std::string &name, const Eigen::MatrixXd &mat, double conversion=1.0)
Write an Eigen matrix as rows of space-separated values into an XML property node....
Eigen::MatrixXd SetupCTStates(Index bseA_vtotal, Index bseB_vtotal, Index bseAB_vtotal, Index bseAB_ctotal, const Eigen::MatrixXd &A_AB, const Eigen::MatrixXd &B_AB) const
Eigen::MatrixXd Fulldiag(const Eigen::MatrixXd &J_dimer) const
Eigen::MatrixXd CalcJ_dimer(BSE_OPERATOR &H, Eigen::MatrixXd &projection, Eigen::MatrixXd &J_dimer_out, Eigen::MatrixXd &S_dimer_out) const
Form J_dimer and S_dimer from the projection, then Lowdin orthogonalize to produce J_ortho.
Eigen::MatrixXd OrthogonalizeCTs(Eigen::MatrixXd &FE_AB, Eigen::MatrixXd &CTStates) const
Merge FE and CT projection vectors into a single projection matrix without pre-orthogonalization.
Eigen::MatrixXd J_dimer_singlet_
TripletOperator_TDA getTripletOperator_TDA() const
Definition bse.cc:452
SingletOperator_TDA getSingletOperator_TDA() const
Definition bse.cc:445
void configure(const options &opt, const Eigen::VectorXd &RPAEnergies, const Eigen::MatrixXd &Hqp_in)
Definition bse.cc:63
Eigen::MatrixXd CalculateOverlapMatrix(const Orbitals &orbitalsAB) const
void CheckAtomCoordinates(const Orbitals &orbitalsA, const Orbitals &orbitalsB, const Orbitals &orbitalsAB) const
Container for molecular orbitals and derived one-particle data.
Definition orbitals.h:47
Index getBSEvmax() const
Return the highest valence orbital included in BSE.
Definition orbitals.h:438
const tools::EigenSystem & BSETriplets() const
Return read-only access to triplet BSE eigenpairs.
Definition orbitals.h:497
bool GetFlagUseHqpOffdiag() const
Definition orbitals.h:696
Index getBSEcmin() const
Return the lowest conduction orbital included in BSE.
Definition orbitals.h:441
const tools::EigenSystem & QPdiag() const
Return read-only access to the diagonalized quasiparticle representation.
Definition orbitals.h:482
Index getHomo() const
Return the default HOMO index used by spin-agnostic callers.
Definition orbitals.h:107
const tools::EigenSystem & BSESinglets() const
Return read-only access to singlet BSE eigenpairs.
Definition orbitals.h:510
Index getBSEcmax() const
Return the highest conduction orbital included in BSE.
Definition orbitals.h:444
const AOBasis & getAuxBasis() const
Return the auxiliary AO basis, throwing if it has not been initialized.
Definition orbitals.h:353
Index getBSEvmin() const
Return the lowest valence orbital included in BSE.
Definition orbitals.h:435
Index getRPAmax() const
Return the upper RPA orbital index.
Definition orbitals.h:409
const std::vector< Eigen::Vector3d > & TransitionDipoles() const
Return the stored transition dipole moments.
Definition orbitals.h:582
Index getBasisSetSize() const
Return the number of AO basis functions in the DFT basis.
Definition orbitals.h:72
const Eigen::VectorXd & RPAInputEnergies() const
Return read-only access to the RPA input energies.
Definition orbitals.h:457
Index getGWAmin() const
Return the lower GW orbital index.
Definition orbitals.h:389
const tools::EigenSystem & MOs() const
Return read-only access to alpha/restricted molecular orbitals.
Definition orbitals.h:192
Index getGWAmax() const
Return the upper GW orbital index.
Definition orbitals.h:392
const std::string & getDFTbasisName() const
Return the DFT basis-set name.
Definition orbitals.h:332
Index getRPAmin() const
Return the lower RPA orbital index.
Definition orbitals.h:406
const AOBasis & getDftBasis() const
Return the DFT AO basis, throwing if it has not been initialized.
Definition orbitals.h:342
Identifier for QMstates. Strings like S1 are converted into enum +zero indexed int.
Definition qmstate.h:135
std::string ToString() const
Definition qmstate.cc:155
Index StateIdx() const
Definition qmstate.h:157
const QMStateType & Type() const
Definition qmstate.h:154
void Fill(const AOBasis &auxbasis, const AOBasis &dftbasis, const Eigen::MatrixXd &dft_orbitals)
void Initialize(Index basissize, Index mmin, Index mmax, Index nmin, Index nmax)
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
#define XTP_LOG(level, log)
Definition logger.h:40
const double hrt2ev
Definition constants.h:53
Index getMaxThreads()
Definition eigen.h:145
void Cite(const std::string &key)
marks a reference as used (thread safe; unknown keys are printed as such)
Definition citations.cc:289
Charge transport classes.
Definition ERIs.h:28
int GetSign(double value)
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
Diagnostic quantities for assessing whether CT downfolding is safe.
Definition bsecoupling.h:69