votca 2026-dev
Loading...
Searching...
No Matches
orbitals.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2023 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// Standard includes
21#include <cstdio>
22#include <fstream>
23#include <iomanip>
24#include <numeric>
25
26// Local VOTCA includes
27#include "votca/tools/version.h"
28#include "votca/xtp/aomatrix.h"
29#include "votca/xtp/orbitals.h"
31#include "votca/xtp/qmstate.h"
32#include "votca/xtp/vc2index.h"
33
34namespace votca {
35namespace xtp {
36
45
46Orbitals::Orbitals() : atoms_("", 0) { ; }
47
56std::vector<Index> Orbitals::CheckDegeneracy(Index level,
57 double energy_difference) const {
58 if (this->hasUnrestrictedOrbitals()) {
59 throw std::runtime_error(
60 "Checking degeneracy not implemented for open-shell systems.");
61 }
62 std::vector<Index> result;
63 if (level > mos_.eigenvalues().size()) {
64 throw std::runtime_error(
65 "Level for degeneracy is higher than maximum level");
66 }
67 double MOEnergyLevel = mos_.eigenvalues()(level);
68
69 for (Index i = 0; i < mos_.eigenvalues().size(); ++i) {
70 if (std::abs(mos_.eigenvalues()(i) - MOEnergyLevel) < energy_difference) {
71 result.push_back(i);
72 }
73 }
74
75 if (result.empty()) {
76 result.push_back(level);
77 }
78 return result;
79}
80
81std::vector<Index> Orbitals::SortEnergies() {
82 if (this->hasUnrestrictedOrbitals()) {
83 throw std::runtime_error(
84 "MO sorting not implemented for open-shell systems.");
85 }
86 std::vector<Index> index = std::vector<Index>(mos_.eigenvalues().size());
87 std::iota(index.begin(), index.end(), 0);
88 std::stable_sort(index.begin(), index.end(), [this](Index i1, Index i2) {
89 return this->MOs().eigenvalues()[i1] < this->MOs().eigenvalues()[i2];
90 });
91 return index;
92}
93
99void Orbitals::SetupDftBasis(std::string basis_name) {
100 if (this->QMAtoms().size() == 0) {
101 throw std::runtime_error("Can't setup AOBasis without atoms");
102 }
103 BasisSet bs;
104 bs.Load(basis_name);
105 dftbasis_.Fill(bs, this->QMAtoms());
106}
107
108void Orbitals::SetupAuxBasis(std::string aux_basis_name) {
109 if (this->QMAtoms().size() == 0) {
110 throw std::runtime_error("Can't setup Aux AOBasis without atoms");
111 }
112 BasisSet bs;
113 bs.Load(aux_basis_name);
114 auxbasis_.Fill(bs, this->QMAtoms());
115}
116
117// Return the density change relative to the ground state. For excited states
118// this corresponds to the response density Delta P, whereas for charged states
119// it returns the projector that must be added to or removed from the ground-
120// state density.
121Eigen::MatrixXd Orbitals::DensityMatrixWithoutGS(const QMState& state) const {
122 if (this->hasUnrestrictedOrbitals() &&
123 state.Type() != QMStateType::ExcitonUKS) {
124 throw std::runtime_error(
125 "DensityMatrixWithoutGS not implemented for open-shell systems "
126 "except ExcitonUKS.");
127 }
128 if (state.Type().isExciton()) {
129 std::array<Eigen::MatrixXd, 2> DMAT = DensityMatrixExcitedState(state);
130 return DMAT[1] - DMAT[0];
131 } else if (state.Type().isKSState() || state.Type().isPQPState()) {
132 return DensityMatrixKSstate(state);
133 } else if (state.Type() == QMStateType::DQPstate) {
134 Eigen::MatrixXd DMATQP = DensityMatrixQuasiParticle(state);
135 if (state.StateIdx() > getHomo()) {
136 return DMATQP;
137 } else {
138 return -DMATQP;
139 }
140 } else {
141 throw std::runtime_error(
142 "DensityMatrixWithoutGS does not yet implement QMStateType:" +
143 state.Type().ToLongString());
144 }
145}
146
147// Return the full AO density associated with the requested state. Depending
148// on the state type this is the ground-state density with the corresponding
149// particle, hole, or excitonic correction added on top.
150Eigen::MatrixXd Orbitals::DensityMatrixFull(const QMState& state) const {
151 if (state.isTransition()) {
152 return this->TransitionDensityMatrix(state);
153 }
154 Eigen::MatrixXd result = this->DensityMatrixGroundState();
155
156 if (getCalculationType() != "NoEmbedding") {
157 result += getInactiveDensity();
158 }
159 if (state.Type().isExciton()) {
160 std::array<Eigen::MatrixXd, 2> DMAT = DensityMatrixExcitedState(state);
161 result = result - DMAT[0] + DMAT[1]; // Ground state + hole_contribution +
162 // electron contribution
163 } else if (state.Type() == QMStateType::DQPstate) {
164 Eigen::MatrixXd DMATQP = DensityMatrixQuasiParticle(state);
165 if (state.StateIdx() > getHomo()) {
166 result += DMATQP;
167 } else {
168 result -= DMATQP;
169 }
170 } else if (state.Type() == QMStateType::KSstate ||
171 state.Type() == QMStateType::PQPstate) {
172 Eigen::MatrixXd DMATKS = DensityMatrixKSstate(state);
173 if (state.StateIdx() <= getHomo()) {
174 result -= DMATKS;
175 } else {
176 result += DMATKS;
177 }
178 } else if (state.Type() == QMStateType::Hole ||
179 state.Type() == QMStateType::Electron) {
180 if (!this->hasUnrestrictedOrbitals()) {
181 throw std::runtime_error("QMStateType:" + state.Type().ToLongString() +
182 " requires an openshell calculation");
183 }
184 } else if (state.Type() != QMStateType::Gstate) {
185 throw std::runtime_error(
186 "DensityMatrixFull does not yet implement QMStateType:" +
187 state.Type().ToLongString());
188 }
189 return result;
190}
191
192// Determine ground state density matrix
193/*Eigen::MatrixXd Orbitals::DensityMatrixGroundState() const {
194 if (!hasMOs()) {
195 throw std::runtime_error("Orbitals file does not contain MO coefficients");
196 }
197 Eigen::MatrixXd occstates = mos_.eigenvectors().leftCols(occupied_levels_);
198 Eigen::MatrixXd dmatGS = occstates * occstates.transpose();
199 if (this->hasUnrestrictedOrbitals()) {
200 Eigen::MatrixXd occstates_beta =
201 mos_beta_.eigenvectors().leftCols(occupied_levels_beta_);
202 dmatGS += occstates_beta * occstates_beta.transpose();
203 } else {
204 dmatGS *= 2.0;
205 }
206 return dmatGS;
207}*/
208
209// Ground-state one-particle density in the AO basis.
210//
211// For unrestricted calculations the total density is assembled as
212//
213// P = P^alpha + P^beta,
214//
215// with P^sigma = C_occ^sigma (C_occ^sigma)^T.
216//
217// For restricted cases the spin-resolved helper reconstructs the same object
218// from the shared spatial orbitals together with the stored alpha/beta
219// occupation pattern.
220Eigen::MatrixXd Orbitals::DensityMatrixGroundState() const {
222 return spin[0] + spin[1];
223}
224
225// Density matrix for a single KS orbital
226// One-orbital projector in the AO basis,
227//
228// P^(k)_(mu,nu) = C_(mu,k) C_(nu,k),
229//
230// used for adding or removing a single KS occupation relative to the ground
231// state.
232Eigen::MatrixXd Orbitals::DensityMatrixKSstate(const QMState& state) const {
233 if (this->hasUnrestrictedOrbitals()) {
234 throw std::runtime_error(
235 "DensityMatrixKSstate not implemented for open-shell systems.");
236 }
237 if (!hasMOs()) {
238 throw std::runtime_error("Orbitals file does not contain MO coefficients");
239 }
240 if (state.Type() != QMStateType::KSstate &&
241 state.Type() != QMStateType::PQPstate) {
242 throw std::runtime_error("State:" + state.ToString() +
243 " is not a Kohn Sham state");
244 }
245 Eigen::VectorXd KSstate = mos_.eigenvectors().col(state.StateIdx());
246 Eigen::MatrixXd dmatKS = KSstate * KSstate.transpose();
247 return dmatKS;
248}
249
250Eigen::MatrixXd Orbitals::BSEOrbitals() const {
251 Eigen::MatrixXd C = mos_.eigenvectors();
252 if (is_qsgw_) {
253 if (!hasQPdiag()) {
254 throw std::runtime_error(
255 "Orbitals: QSGW flag set, but no QSGW orbitals (QPdiag) stored");
256 }
257 const Index qptotal = qpmax_ - qpmin_ + 1;
258 C.middleCols(qpmin_, qptotal) =
259 mos_.eigenvectors().middleCols(qpmin_, qptotal) *
260 QPdiag_.eigenvectors();
261 }
262 return C;
263}
264
266 if (!hasQPdiag()) {
267 throw std::runtime_error("Orbitals file does not contain QP coefficients");
268 }
269 return mos_.eigenvectors().middleCols(qpmin_, qpmax_ - qpmin_ + 1) *
270 QPdiag_.eigenvectors();
271}
272
273// Determine QuasiParticle Density Matrix
274// Quasiparticle projector in the AO basis,
275//
276// P^QP_(mu,nu) = Lambda_(mu,n) Lambda_(nu,n),
277//
278// where Lambda is the AO representation of the quasiparticle eigenvectors in
279// the selected GW subspace.
281 const QMState& state) const {
282 if (this->hasUnrestrictedOrbitals()) {
283 throw std::runtime_error(
284 "DensityMatrixQuasiParticle not implemented for open-shell systems.");
285 }
286 if (state.Type() != QMStateType::DQPstate) {
287 throw std::runtime_error("State:" + state.ToString() +
288 " is not a quasiparticle state");
289 }
290 Eigen::MatrixXd lambda = CalculateQParticleAORepresentation();
291 Eigen::MatrixXd dmatQP = lambda.col(state.StateIdx() - qpmin_) *
292 lambda.col(state.StateIdx() - qpmin_).transpose();
293 return dmatQP;
294}
295
296// Electric dipole moment relative to the molecular origin,
297//
298// mu = sum_A Z_A (R_A - R_0) - Tr[P M],
299//
300// where M contains the AO dipole integrals for x, y, and z and P is the full
301// state density matrix. Transition densities omit the nuclear term and return
302// only the electronic transition dipole.
303Eigen::Vector3d Orbitals::CalcElDipole(const QMState& state) const {
304 Eigen::Vector3d nuclei_dip = Eigen::Vector3d::Zero();
305 if (!state.isTransition()) {
306 for (const QMAtom& atom : atoms_) {
307 nuclei_dip += (atom.getPos() - atoms_.getPos()) * atom.getNuccharge();
308 }
309 }
310 AOBasis basis = getDftBasis();
311 AODipole dipole;
312 dipole.setCenter(atoms_.getPos());
313 dipole.Fill(basis);
314
315 Eigen::MatrixXd dmat = this->DensityMatrixFull(state);
316 Eigen::Vector3d electronic_dip;
317 for (Index i = 0; i < 3; ++i) {
318 electronic_dip(i) = dmat.cwiseProduct(dipole.Matrix()[i]).sum();
319 }
320 return nuclei_dip - electronic_dip;
321}
322
323// AO transition density for a singlet BSE excitation.
324//
325// In TDA form this evaluates
326//
327// gamma_(mu,nu) = sqrt(2) sum_(v,c) A_(v,c) C_(mu,v) C_(nu,c),
328//
329// and for full BSE the resonant and anti-resonant amplitudes are added before
330// the AO transformation.
331Eigen::MatrixXd Orbitals::TransitionDensityMatrix(const QMState& state) const {
332
333 if (state.Type() == QMStateType::ExcitonUKS) {
334 if (!this->hasUnrestrictedOrbitals()) {
335 throw std::runtime_error(
336 "ExcitonUKS transition density requested, but no unrestricted "
337 "orbitals are present.");
338 }
339
340 const Eigen::MatrixXd& BSECoefs = BSE_uks_.eigenvectors();
341 if (BSECoefs.cols() < state.StateIdx() + 1 || BSECoefs.rows() < 2) {
342 throw std::runtime_error(
343 "Orbitals object has no information about state:" + state.ToString());
344 }
345
346 Eigen::VectorXd coeffs = BSECoefs.col(state.StateIdx());
347 if (!useTDA_) {
348 coeffs += BSE_uks_.eigenvectors2().col(state.StateIdx());
349 }
350
351 const Index alpha_vtotal = getHomoAlpha() - bse_vmin_ + 1;
352 const Index alpha_ctotal = bse_cmax_ - getLumoAlpha() + 1;
353 const Index beta_vtotal = getHomoBeta() - bse_vmin_ + 1;
354 const Index beta_ctotal = bse_cmax_ - getLumoBeta() + 1;
355
356 const Index alpha_size = alpha_vtotal * alpha_ctotal;
357 const Index beta_size = beta_vtotal * beta_ctotal;
358
359 if (coeffs.size() != alpha_size + beta_size) {
360 throw std::runtime_error(
361 "ExcitonUKS coefficient vector size does not match expected "
362 "alpha+beta BSE space dimensions.");
363 }
364
365 Eigen::MatrixXd tdm = Eigen::MatrixXd::Zero(mos_.eigenvectors().rows(),
366 mos_.eigenvectors().rows());
367
368 if (alpha_size > 0) {
369 const Eigen::VectorXd coeffs_alpha = coeffs.head(alpha_size);
370 const auto occ_alpha =
371 mos_.eigenvectors().middleCols(bse_vmin_, alpha_vtotal);
372 const auto virt_alpha =
373 mos_.eigenvectors().middleCols(getLumoAlpha(), alpha_ctotal);
374 const Eigen::Map<const Eigen::MatrixXd> mat_alpha(
375 coeffs_alpha.data(), alpha_ctotal, alpha_vtotal);
376
377 tdm += occ_alpha * mat_alpha.transpose() * virt_alpha.transpose();
378 }
379
380 if (beta_size > 0) {
381 const Eigen::VectorXd coeffs_beta = coeffs.tail(beta_size);
382 const auto occ_beta =
383 mos_beta_.eigenvectors().middleCols(bse_vmin_, beta_vtotal);
384 const auto virt_beta =
385 mos_beta_.eigenvectors().middleCols(getLumoBeta(), beta_ctotal);
386 const Eigen::Map<const Eigen::MatrixXd> mat_beta(
387 coeffs_beta.data(), beta_ctotal, beta_vtotal);
388
389 tdm += occ_beta * mat_beta.transpose() * virt_beta.transpose();
390 }
391
392 return tdm;
393 }
394
395 if (this->hasUnrestrictedOrbitals()) {
396 throw std::runtime_error(
397 "TransitionDensityMatrix not implemented for open-shell systems "
398 "except ExcitonUKS.");
399 }
400 if (state.Type() != QMStateType::Singlet) {
401 throw std::runtime_error(
402 "Spin type not known for transition density matrix. Available only for "
403 "singlet");
404 }
405 const Eigen::MatrixXd& BSECoefs = BSE_singlet_.eigenvectors();
406 if (BSECoefs.cols() < state.StateIdx() + 1 || BSECoefs.rows() < 2) {
407 throw std::runtime_error("Orbitals object has no information about state:" +
408 state.ToString());
409 }
410
411 Eigen::VectorXd coeffs = BSECoefs.col(state.StateIdx());
412
413 if (!useTDA_) {
414 coeffs += BSE_singlet_.eigenvectors2().col(state.StateIdx());
415 }
416 coeffs *= std::sqrt(2.0);
417 const Eigen::MatrixXd orbitals = BSEOrbitals();
418 auto occlevels = orbitals.middleCols(bse_vmin_, bse_vtotal_);
419 auto virtlevels = orbitals.middleCols(bse_cmin_, bse_ctotal_);
420 Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), bse_ctotal_,
422
423 return occlevels * mat.transpose() * virtlevels.transpose();
424}
425
426// Spin-summed excited-state density correction split into hole and electron
427// blocks. For full BSE the anti-resonant contribution is subtracted from the
428// resonant part, yielding the usual RPA/BSE excited-state density.
429std::array<Eigen::MatrixXd, 2> Orbitals::DensityMatrixExcitedState(
430 const QMState& state) const {
431
432 if (state.Type() == QMStateType::ExcitonUKS) {
433 if (!this->hasUnrestrictedOrbitals()) {
434 throw std::runtime_error(
435 "ExcitonUKS excited-state density requested, but no unrestricted "
436 "orbitals are present.");
437 }
438
439 const Eigen::MatrixXd& X = BSE_uks_.eigenvectors();
440 if (X.cols() < state.StateIdx() + 1 || X.rows() < 2) {
441 throw std::runtime_error(
442 "Orbitals object has no information about state:" + state.ToString());
443 }
444
445 const Index alpha_vtotal = getHomoAlpha() - bse_vmin_ + 1;
446 const Index alpha_ctotal = bse_cmax_ - getLumoAlpha() + 1;
447 const Index beta_vtotal = getHomoBeta() - bse_vmin_ + 1;
448 const Index beta_ctotal = bse_cmax_ - getLumoBeta() + 1;
449
450 const Index alpha_size = alpha_vtotal * alpha_ctotal;
451 const Index beta_size = beta_vtotal * beta_ctotal;
452
453 if (X.rows() != alpha_size + beta_size) {
454 throw std::runtime_error(
455 "ExcitonUKS coefficient vector size does not match expected "
456 "alpha+beta BSE space dimensions.");
457 }
458
459 const Index nao = mos_.eigenvectors().rows();
460 std::array<Eigen::MatrixXd, 2> dmat;
461 dmat[0] = Eigen::MatrixXd::Zero(nao, nao); // hole
462 dmat[1] = Eigen::MatrixXd::Zero(nao, nao); // electron
463
464 auto add_resonant = [&](const Eigen::VectorXd& coeffs, bool alpha_spin) {
465 if (coeffs.size() == 0) {
466 return;
467 }
468
469 const Index vtotal = alpha_spin ? alpha_vtotal : beta_vtotal;
470 const Index ctotal = alpha_spin ? alpha_ctotal : beta_ctotal;
471
472 const auto& mos_spin =
473 alpha_spin ? mos_.eigenvectors() : mos_beta_.eigenvectors();
474 const Index lumo = alpha_spin ? getLumoAlpha() : getLumoBeta();
475
476 const auto occlevels = mos_spin.middleCols(bse_vmin_, vtotal);
477 const auto virtlevels = mos_spin.middleCols(lumo, ctotal);
478
479 const Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), ctotal,
480 vtotal);
481
482 dmat[0] += occlevels * (mat.transpose() * mat) * occlevels.transpose();
483 dmat[1] += virtlevels * (mat * mat.transpose()) * virtlevels.transpose();
484 };
485
486 auto subtract_antiresonant = [&](const Eigen::VectorXd& coeffs,
487 bool alpha_spin) {
488 if (coeffs.size() == 0) {
489 return;
490 }
491
492 const Index vtotal = alpha_spin ? alpha_vtotal : beta_vtotal;
493 const Index ctotal = alpha_spin ? alpha_ctotal : beta_ctotal;
494
495 const auto& mos_spin =
496 alpha_spin ? mos_.eigenvectors() : mos_beta_.eigenvectors();
497 const Index lumo = alpha_spin ? getLumoAlpha() : getLumoBeta();
498
499 const auto occlevels = mos_spin.middleCols(bse_vmin_, vtotal);
500 const auto virtlevels = mos_spin.middleCols(lumo, ctotal);
501
502 const Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), ctotal,
503 vtotal);
504
505 // Full-BSE anti-resonant correction:
506 // hole -= virt * (B B^T) * virt^T
507 // electron -= occ * (B^T B) * occ^T
508 dmat[0] -= virtlevels * (mat * mat.transpose()) * virtlevels.transpose();
509 dmat[1] -= occlevels * (mat.transpose() * mat) * occlevels.transpose();
510 };
511
512 const Eigen::VectorXd Xstate = X.col(state.StateIdx());
513 add_resonant(Xstate.head(alpha_size), true);
514 add_resonant(Xstate.tail(beta_size), false);
515
516 if (!useTDA_) {
517 const Eigen::MatrixXd& Y = BSE_uks_.eigenvectors2();
518 if (Y.cols() < state.StateIdx() + 1 || Y.rows() != X.rows()) {
519 throw std::runtime_error(
520 "Orbitals object has inconsistent anti-resonant ExcitonUKS "
521 "coefficients for state:" +
522 state.ToString());
523 }
524
525 const Eigen::VectorXd Ystate = Y.col(state.StateIdx());
526 subtract_antiresonant(Ystate.head(alpha_size), true);
527 subtract_antiresonant(Ystate.tail(beta_size), false);
528 }
529
530 return dmat;
531 }
532
533 if (this->hasUnrestrictedOrbitals()) {
534 throw std::runtime_error(
535 "DensityMatrixExcitedState not implemented for open-shell systems "
536 "except ExcitonUKS.");
537 }
538
539 std::array<Eigen::MatrixXd, 2> dmat = DensityMatrixExcitedState_R(state);
540 if (!useTDA_) {
541 std::array<Eigen::MatrixXd, 2> dmat_AR =
543 dmat[0] -= dmat_AR[0];
544 dmat[1] -= dmat_AR[1];
545 }
546 return dmat;
547}
548
549// Excited state density matrix
550
551std::array<Eigen::MatrixXd, 2> Orbitals::DensityMatrixExcitedState_R(
552 const QMState& state) const {
553 if (!state.Type().isExciton()) {
554 throw std::runtime_error(
555 "Spin type not known for density matrix. Available are singlet and "
556 "triplet");
557 }
558
559 const Eigen::MatrixXd& BSECoefs = (state.Type() == QMStateType::Singlet)
560 ? BSE_singlet_.eigenvectors()
561 : BSE_triplet_.eigenvectors();
562 if (BSECoefs.cols() < state.StateIdx() + 1 || BSECoefs.rows() < 2) {
563 throw std::runtime_error("Orbitals object has no information about state:" +
564 state.ToString());
565 }
566 /******
567 *
568 * Density matrix for GW-BSE based excitations
569 *
570 * - electron contribution
571 * D_ab = \sum{vc} \sum{c'} A_{vc}A_{vc'} mo_a(c)mo_b(c')
572 *
573 * - hole contribution
574 * D_ab = \sum{vc} \sum{v'} A_{vc}A_{v'c} mo_a(v)mo_b(v')
575 *
576 */
577
578 Eigen::VectorXd coeffs = BSECoefs.col(state.StateIdx());
579
580 std::array<Eigen::MatrixXd, 2> dmatEX;
581 const Eigen::MatrixXd orbitals = BSEOrbitals();
582 // hole part as matrix products
583 Eigen::MatrixXd occlevels = orbitals.middleCols(bse_vmin_, bse_vtotal_);
584 dmatEX[0] = occlevels * CalcAuxMat_vv(coeffs) * occlevels.transpose();
585
586 // electron part as matrix products
587 Eigen::MatrixXd virtlevels = orbitals.middleCols(bse_cmin_, bse_ctotal_);
588 dmatEX[1] = virtlevels * CalcAuxMat_cc(coeffs) * virtlevels.transpose();
589
590 return dmatEX;
591}
592
593Eigen::MatrixXd Orbitals::CalcAuxMat_vv(const Eigen::VectorXd& coeffs) const {
594 const Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), bse_ctotal_,
596 return mat.transpose() * mat;
597}
598
599Eigen::MatrixXd Orbitals::CalcAuxMat_cc(const Eigen::VectorXd& coeffs) const {
600 const Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), bse_ctotal_,
602 return mat * mat.transpose();
603}
604
605std::array<Eigen::MatrixXd, 2> Orbitals::DensityMatrixExcitedState_AR(
606 const QMState& state) const {
607
608 if (!state.Type().isExciton()) {
609 throw std::runtime_error(
610 "Spin type not known for density matrix. Available are singlet and "
611 "triplet");
612 }
613
614 const Eigen::MatrixXd& BSECoefs_AR = (state.Type() == QMStateType::Singlet)
615 ? BSE_singlet_.eigenvectors2()
616 : BSE_triplet_.eigenvectors2();
617 if (BSECoefs_AR.cols() < state.StateIdx() + 1 || BSECoefs_AR.rows() < 2) {
618 throw std::runtime_error("Orbitals object has no information about state:" +
619 state.ToString());
620 }
621 /******
622 *
623 * Density matrix for GW-BSE based excitations
624 *
625 * - electron contribution
626 * D_ab = \sum{vc} \sum{v'} B_{vc}B_{v'c} mo_a(v)mo_b(v')
627 *
628 * - hole contribution
629 * D_ab = \sum{vc} \sum{c'} B_{vc}B_{vc'} mo_a(c)mo_b(c')
630 *
631 *
632 * more efficient:
633 *
634 * - electron contribution
635 * D_ab = \sum{v} \sum{v'} mo_a(v)mo_b(v') [ \sum{c} B_{vc}B_{v'c} ]
636 * = \sum{v} \sum{v'} mo_a(v)mo_b(v') B_{vv'}
637 *
638 * - hole contribution
639 * D_ab = \sum{c} \sum{c'} mo_a(c)mo_b(c') [ \sum{v} B_{vc}B_{vc'} ]
640 * = \sum{c} \sum{c'} mo_a(c)mo_b(c') B_{cc'}
641 *
642 */
643
644 Eigen::VectorXd coeffs = BSECoefs_AR.col(state.StateIdx());
645
646 std::array<Eigen::MatrixXd, 2> dmatAR;
647 const Eigen::MatrixXd orbitals = BSEOrbitals();
648 Eigen::MatrixXd virtlevels = orbitals.middleCols(bse_cmin_, bse_ctotal_);
649 dmatAR[0] = virtlevels * CalcAuxMat_cc(coeffs) * virtlevels.transpose();
650 // electron part as matrix products
651 Eigen::MatrixXd occlevels = orbitals.middleCols(bse_vmin_, bse_vtotal_);
652 dmatAR[1] = occlevels * CalcAuxMat_vv(coeffs) * occlevels.transpose();
653
654 return dmatAR;
655}
656
660
661Eigen::VectorXd Orbitals::Oscillatorstrengths(const QMStateType& type) const {
662
663 const tools::EigenSystem* es = nullptr;
664
665 if (type == QMStateType::Singlet) {
666 es = &BSE_singlet_;
667 } else if (type == QMStateType::ExcitonUKS) {
668 es = &BSE_uks_;
669 } else {
670 throw std::runtime_error(
671 "Orbitals::Oscillatorstrengths only implemented for "
672 "restricted singlets and combined UKS excitons.");
673 }
674
675 Index size = Index(transition_dipoles_.size());
676 if (size > es->eigenvalues().size()) {
677 size = es->eigenvalues().size();
678 }
679
680 Eigen::VectorXd oscs = Eigen::VectorXd::Zero(size);
681 for (Index i = 0; i < size; ++i) {
682 // f = (2/3) * Omega * |d|^2
683 // Omega must be in Hartree, d in e*bohr
684 oscs(i) =
685 transition_dipoles_[i].squaredNorm() * 2.0 / 3.0 * es->eigenvalues()(i);
686 }
687 return oscs;
688}
689
690double Orbitals::getTotalStateEnergy(const QMState& state) const {
691 double total_energy = getDFTTotalEnergy();
692 if (state.Type() == QMStateType::Gstate) {
693 return total_energy;
694 }
695 total_energy += getExcitedStateEnergy(state);
696 return total_energy;
697}
698
699double Orbitals::getExcitedStateEnergy(const QMState& state) const {
700
701 double omega = 0.0;
702 if (state.isTransition()) {
703 throw std::runtime_error(
704 "Total Energy does not exist for transition state");
705 }
706
707 if (state.Type() == QMStateType::Singlet) {
708 if (BSE_singlet_.eigenvalues().size() < state.StateIdx() + 1) {
709 throw std::runtime_error("Orbitals::getTotalEnergy You want " +
710 state.ToString() +
711 " which has not been calculated");
712 }
713 omega = BSE_singlet_.eigenvalues()[state.StateIdx()];
714 } else if (state.Type() == QMStateType::Triplet) {
715 if (BSE_triplet_.eigenvalues().size() < state.StateIdx() + 1) {
716 throw std::runtime_error("Orbitals::getTotalEnergy You want " +
717 state.ToString() +
718 " which has not been calculated");
719 }
720 omega = BSE_triplet_.eigenvalues()[state.StateIdx()];
721 } else if (state.Type() == QMStateType::DQPstate) {
722 if (QPdiag_.eigenvalues().size() < state.StateIdx() + 1 - getGWAmin()) {
723 throw std::runtime_error("Orbitals::getTotalEnergy You want " +
724 state.ToString() +
725 " which has not been calculated");
726 }
727 return QPdiag_.eigenvalues()[state.StateIdx() - getGWAmin()];
728 } else if (state.Type() == QMStateType::KSstate) {
729 if (mos_.eigenvalues().size() < state.StateIdx() + 1) {
730 throw std::runtime_error("Orbitals::getTotalEnergy You want " +
731 state.ToString() +
732 " which has not been calculated");
733 }
734 return mos_.eigenvalues()[state.StateIdx()];
735 } else if (state.Type() == QMStateType::PQPstate) {
736 if (this->QPpert_energies_.rows() < state.StateIdx() + 1 - getGWAmin()) {
737 throw std::runtime_error("Orbitals::getTotalEnergy You want " +
738 state.ToString() +
739 " which has not been calculated");
740 }
741 return QPpert_energies_(state.StateIdx() - getGWAmin(), 3);
742 } else if (state.Type() == QMStateType::LMOstate) {
743 if (lmos_energies_.size() < state.StateIdx() + 1) {
744 throw std::runtime_error(
745 "Orbitals::getTotalEnergy You want " + state.ToString() +
746 " which is a LMO for virtual orbitals. Not implemented.");
747 }
748 return lmos_energies_(state.StateIdx());
749 } else {
750 throw std::runtime_error(
751 "GetTotalEnergy only knows states:singlet,triplet,KS,DQP,PQP,LMOs");
752 }
753 return omega; // e.g. hartree
754}
755
756std::array<Eigen::MatrixXd, 3> Orbitals::CalcFreeTransition_Dipoles() const {
757 const Eigen::MatrixXd orbitals = BSEOrbitals();
758 AOBasis basis = getDftBasis();
759 // Testing electric dipole AOMatrix
760 AODipole dft_dipole;
761 dft_dipole.Fill(basis);
762
763 // now transition dipole elements for free interlevel transitions
764 std::array<Eigen::MatrixXd, 3> interlevel_dipoles;
765
766 Eigen::MatrixXd empty = orbitals.middleCols(bse_cmin_, bse_ctotal_);
767 Eigen::MatrixXd occ = orbitals.middleCols(bse_vmin_, bse_vtotal_);
768 for (Index i = 0; i < 3; i++) {
769 interlevel_dipoles[i] = empty.transpose() * dft_dipole.Matrix()[i] * occ;
770 }
771 return interlevel_dipoles;
772}
773
777
779
780 transition_dipoles_.clear();
781
782 if (type == QMStateType::Singlet) {
783 std::array<Eigen::MatrixXd, 3> interlevel_dipoles =
785
786 Index numofstates = BSE_singlet_.eigenvalues().size();
787 transition_dipoles_.reserve(numofstates);
788
789 const double sqrt2 = std::sqrt(2.0);
790
791 for (Index i_exc = 0; i_exc < numofstates; ++i_exc) {
792 Eigen::VectorXd coeffs = BSE_singlet_.eigenvectors().col(i_exc);
793 if (!useTDA_) {
794 coeffs += BSE_singlet_.eigenvectors2().col(i_exc);
795 }
796
797 Eigen::Map<Eigen::MatrixXd> mat(coeffs.data(), bse_ctotal_, bse_vtotal_);
798
799 Eigen::Vector3d tdipole = Eigen::Vector3d::Zero();
800 for (Index i = 0; i < 3; ++i) {
801 tdipole[i] = mat.cwiseProduct(interlevel_dipoles[i]).sum();
802 }
803
804 // Restricted singlet:
805 // the excitation is the spin-adapted combination of alpha and beta
806 // determinants, so the total transition dipole picks up sqrt(2).
807 transition_dipoles_.push_back(-sqrt2 * tdipole);
808 }
809 return;
810 }
811
812 if (type == QMStateType::ExcitonUKS) {
814 throw std::runtime_error(
815 "Orbitals::CalcCoupledTransition_Dipoles(ExcitonUKS) requires "
816 "unrestricted alpha/beta orbitals.");
817 }
818
819 const Index alpha_homo = getHomoAlpha();
820 const Index beta_homo = getHomoBeta();
821
822 const Index alpha_vtotal = alpha_homo - bse_vmin_ + 1;
823 const Index alpha_ctotal = bse_cmax_ - (alpha_homo + 1) + 1;
824 const Index alpha_size = alpha_vtotal * alpha_ctotal;
825
826 const Index beta_vtotal = beta_homo - bse_vmin_ + 1;
827 const Index beta_ctotal = bse_cmax_ - (beta_homo + 1) + 1;
828 const Index beta_size = beta_vtotal * beta_ctotal;
829
830 if (alpha_vtotal < 0 || alpha_ctotal < 0 || beta_vtotal < 0 ||
831 beta_ctotal < 0) {
832 throw std::runtime_error(
833 "Orbitals::CalcCoupledTransition_Dipoles(ExcitonUKS): "
834 "invalid UKS BSE window.");
835 }
836
837 AOBasis basis = getDftBasis();
838 AODipole dft_dipole;
839 dft_dipole.Fill(basis);
840
841 std::array<Eigen::MatrixXd, 3> interlevel_dipoles_alpha;
842 std::array<Eigen::MatrixXd, 3> interlevel_dipoles_beta;
843
844 const Eigen::MatrixXd virt_alpha =
845 mos_.eigenvectors().middleCols(alpha_homo + 1, alpha_ctotal);
846 const Eigen::MatrixXd occ_alpha =
847 mos_.eigenvectors().middleCols(bse_vmin_, alpha_vtotal);
848
849 const Eigen::MatrixXd virt_beta =
850 mos_beta_.eigenvectors().middleCols(beta_homo + 1, beta_ctotal);
851 const Eigen::MatrixXd occ_beta =
852 mos_beta_.eigenvectors().middleCols(bse_vmin_, beta_vtotal);
853
854 for (Index i = 0; i < 3; ++i) {
855 interlevel_dipoles_alpha[i] =
856 virt_alpha.transpose() * dft_dipole.Matrix()[i] * occ_alpha;
857 interlevel_dipoles_beta[i] =
858 virt_beta.transpose() * dft_dipole.Matrix()[i] * occ_beta;
859 }
860
861 const Index numofstates = BSE_uks_.eigenvalues().size();
862 transition_dipoles_.reserve(numofstates);
863
864 for (Index i_exc = 0; i_exc < numofstates; ++i_exc) {
865 Eigen::VectorXd coeffs_x = BSE_uks_.eigenvectors().col(i_exc);
866 Eigen::VectorXd coeffs = coeffs_x;
867 if (!useTDA_) {
868 coeffs += BSE_uks_.eigenvectors2().col(i_exc);
869 }
870
871 if (coeffs.size() != alpha_size + beta_size) {
872 throw std::runtime_error(
873 "Orbitals::CalcCoupledTransition_Dipoles(ExcitonUKS): "
874 "exciton vector size does not match alpha+beta sector sizes.");
875 }
876
877 Eigen::Map<const Eigen::MatrixXd> mat_alpha(coeffs.data(), alpha_ctotal,
878 alpha_vtotal);
879 Eigen::Map<const Eigen::MatrixXd> mat_beta(coeffs.data() + alpha_size,
880 beta_ctotal, beta_vtotal);
881
882 Eigen::Vector3d tdipole = Eigen::Vector3d::Zero();
883 for (Index i = 0; i < 3; ++i) {
884 tdipole[i] = mat_alpha.cwiseProduct(interlevel_dipoles_alpha[i]).sum() +
885 mat_beta.cwiseProduct(interlevel_dipoles_beta[i]).sum();
886 }
887
888 // Combined UKS exciton:
889 // alpha and beta sectors are already explicit components of the
890 // eigenvector, so there is NO extra sqrt(2) factor here.
891 transition_dipoles_.push_back(-tdipole);
892 }
893 return;
894 }
895
896 throw std::runtime_error(
897 "Orbitals::CalcCoupledTransition_Dipoles only implemented for "
898 "restricted singlets and combined UKS excitons.");
899}
900
902 std::vector<Index> sort_index = SortEnergies();
903 tools::EigenSystem MO_copy = mos_;
904 Index size = mos_.eigenvalues().size();
905 for (Index i = 0; i < size; ++i) {
906 mos_.eigenvalues()(i) = MO_copy.eigenvalues()(sort_index[i]);
907 }
908 for (Index i = 0; i < size; ++i) {
909 mos_.eigenvectors().col(i) = MO_copy.eigenvectors().col(sort_index[i]);
910 }
911}
912
913std::vector<Index> Orbitals::SortEnergiesAlpha() {
914 // Same logic as SortEnergies, but deliberately WITHOUT its
915 // hasUnrestrictedOrbitals() guard -- this is meant to be called on an
916 // unrestricted system, sorting the alpha channel only.
917 std::vector<Index> index = std::vector<Index>(mos_.eigenvalues().size());
918 std::iota(index.begin(), index.end(), 0);
919 std::stable_sort(index.begin(), index.end(), [this](Index i1, Index i2) {
920 return this->MOs().eigenvalues()[i1] < this->MOs().eigenvalues()[i2];
921 });
922 return index;
923}
924
925std::vector<Index> Orbitals::SortEnergiesBeta() {
926 std::vector<Index> index = std::vector<Index>(mos_beta_.eigenvalues().size());
927 std::iota(index.begin(), index.end(), 0);
928 std::stable_sort(index.begin(), index.end(), [this](Index i1, Index i2) {
929 return this->MOs_beta().eigenvalues()[i1] <
930 this->MOs_beta().eigenvalues()[i2];
931 });
932 return index;
933}
934
936 std::vector<Index> sort_index = SortEnergiesAlpha();
937 tools::EigenSystem MO_copy = mos_;
938 Index size = mos_.eigenvalues().size();
939 for (Index i = 0; i < size; ++i) {
940 mos_.eigenvalues()(i) = MO_copy.eigenvalues()(sort_index[i]);
941 }
942 for (Index i = 0; i < size; ++i) {
943 mos_.eigenvectors().col(i) = MO_copy.eigenvectors().col(sort_index[i]);
944 }
945}
946
948 std::vector<Index> sort_index = SortEnergiesBeta();
950 Index size = mos_beta_.eigenvalues().size();
951 for (Index i = 0; i < size; ++i) {
952 mos_beta_.eigenvalues()(i) = MO_copy.eigenvalues()(sort_index[i]);
953 }
954 for (Index i = 0; i < size; ++i) {
955 mos_beta_.eigenvectors().col(i) = MO_copy.eigenvectors().col(sort_index[i]);
956 }
957}
958
967 const Orbitals& orbitalsB) {
968
969 // This routine constructs a block-diagonal guess only from the restricted
970 // MO coefficient matrices mos_. It therefore only makes sense for
971 // restricted closed-shell inputs.
972 if (orbitalsA.hasUnrestrictedOrbitals() ||
973 orbitalsB.hasUnrestrictedOrbitals()) {
974 throw std::runtime_error(
975 "PrepareDimerGuess currently supports only restricted orbitals.");
976 }
977
978 if (orbitalsA.getSpin() != 1 || orbitalsB.getSpin() != 1) {
979 throw std::runtime_error(
980 "PrepareDimerGuess currently supports only closed-shell singlets.");
981 }
982
983 // constructing the direct product orbA x orbB
984 Index basisA = orbitalsA.getBasisSetSize();
985 Index basisB = orbitalsB.getBasisSetSize();
986
987 // In the restricted closed-shell case, getLumo() is the number of occupied
988 // spatial orbitals, which is exactly what this block-merging logic assumes.
989 Index occA = orbitalsA.getLumo();
990 Index occB = orbitalsB.getLumo();
991
992 mos_.eigenvectors() = Eigen::MatrixXd::Zero(basisA + basisB, basisA + basisB);
993
994 // AxB = | A 0 |
995 // | 0 B |
996 if (orbitalsA.getDFTbasisName() != orbitalsB.getDFTbasisName()) {
997 throw std::runtime_error("Basissets of Orbitals A and B differ " +
998 orbitalsA.getDFTbasisName() + ":" +
999 orbitalsB.getDFTbasisName());
1000 }
1001 this->SetupDftBasis(orbitalsA.getDFTbasisName());
1002
1003 if (orbitalsA.getECPName() != orbitalsB.getECPName()) {
1004 throw std::runtime_error("ECPs of Orbitals A and B differ " +
1005 orbitalsA.getECPName() + ":" +
1006 orbitalsB.getECPName());
1007 }
1008 this->setECPName(orbitalsA.getECPName());
1009
1010 // Keep occupation metadata fully consistent for a restricted singlet.
1011 this->setNumberOfOccupiedLevels(occA + occB);
1012 this->setNumberOfOccupiedLevelsBeta(occA + occB);
1013 this->setNumberOfAlphaElectrons(occA + occB);
1014 this->setNumberOfBetaElectrons(occA + occB);
1015 this->setChargeAndSpin(orbitalsA.getCharge() + orbitalsB.getCharge(), 1);
1016
1017 this->MOs().eigenvectors().block(0, 0, basisA, basisA) =
1018 orbitalsA.MOs().eigenvectors();
1019 this->MOs().eigenvectors().block(basisA, basisA, basisB, basisB) =
1020 orbitalsB.MOs().eigenvectors();
1021
1022 this->MOs().eigenvalues() = Eigen::VectorXd::Zero(basisA + basisB);
1023 this->MOs().eigenvalues().segment(0, basisA) = orbitalsA.MOs().eigenvalues();
1024 this->MOs().eigenvalues().segment(basisA, basisB) =
1025 orbitalsB.MOs().eigenvalues();
1026
1027 this->OrderMOsbyEnergy();
1028}
1029
1047 const Orbitals& orbitalsB) {
1048 Index basisA = orbitalsA.getBasisSetSize();
1049 Index basisB = orbitalsB.getBasisSetSize();
1050
1051 if (orbitalsA.getDFTbasisName() != orbitalsB.getDFTbasisName()) {
1052 throw std::runtime_error("Basissets of Orbitals A and B differ " +
1053 orbitalsA.getDFTbasisName() + ":" +
1054 orbitalsB.getDFTbasisName());
1055 }
1056 this->SetupDftBasis(orbitalsA.getDFTbasisName());
1057
1058 if (orbitalsA.getECPName() != orbitalsB.getECPName()) {
1059 throw std::runtime_error("ECPs of Orbitals A and B differ " +
1060 orbitalsA.getECPName() + ":" +
1061 orbitalsB.getECPName());
1062 }
1063 this->setECPName(orbitalsA.getECPName());
1064
1065 // A restricted monomer's own alpha and beta MOs are identical by
1066 // construction -- MOs_beta() only holds meaningful, independent data
1067 // for a monomer that is itself already unrestricted.
1068 const Eigen::MatrixXd& A_alpha = orbitalsA.MOs().eigenvectors();
1069 const Eigen::MatrixXd& A_beta = orbitalsA.hasUnrestrictedOrbitals()
1070 ? orbitalsA.MOs_beta().eigenvectors()
1071 : orbitalsA.MOs().eigenvectors();
1072 const Eigen::MatrixXd& B_alpha = orbitalsB.MOs().eigenvectors();
1073 const Eigen::MatrixXd& B_beta = orbitalsB.hasUnrestrictedOrbitals()
1074 ? orbitalsB.MOs_beta().eigenvectors()
1075 : orbitalsB.MOs().eigenvectors();
1076
1077 const Eigen::VectorXd& A_alpha_energies = orbitalsA.MOs().eigenvalues();
1078 const Eigen::VectorXd& A_beta_energies =
1079 orbitalsA.hasUnrestrictedOrbitals() ? orbitalsA.MOs_beta().eigenvalues()
1080 : orbitalsA.MOs().eigenvalues();
1081 const Eigen::VectorXd& B_alpha_energies = orbitalsB.MOs().eigenvalues();
1082 const Eigen::VectorXd& B_beta_energies =
1083 orbitalsB.hasUnrestrictedOrbitals() ? orbitalsB.MOs_beta().eigenvalues()
1084 : orbitalsB.MOs().eigenvalues();
1085
1086 mos_.eigenvectors() = Eigen::MatrixXd::Zero(basisA + basisB, basisA + basisB);
1087 mos_.eigenvectors().block(0, 0, basisA, basisA) = A_alpha;
1088 mos_.eigenvectors().block(basisA, basisA, basisB, basisB) = B_alpha;
1089 mos_.eigenvalues() = Eigen::VectorXd::Zero(basisA + basisB);
1090 mos_.eigenvalues().segment(0, basisA) = A_alpha_energies;
1091 mos_.eigenvalues().segment(basisA, basisB) = B_alpha_energies;
1092
1093 mos_beta_.eigenvectors() =
1094 Eigen::MatrixXd::Zero(basisA + basisB, basisA + basisB);
1095 mos_beta_.eigenvectors().block(0, 0, basisA, basisA) = A_beta;
1096 mos_beta_.eigenvectors().block(basisA, basisA, basisB, basisB) = B_beta;
1097 mos_beta_.eigenvalues() = Eigen::VectorXd::Zero(basisA + basisB);
1098 mos_beta_.eigenvalues().segment(0, basisA) = A_beta_energies;
1099 mos_beta_.eigenvalues().segment(basisA, basisB) = B_beta_energies;
1100
1101 Index alpha_electrons = orbitalsA.getLumoAlpha() + orbitalsB.getLumoAlpha();
1102 Index beta_electrons = orbitalsA.getLumoBeta() + orbitalsB.getLumoBeta();
1103 this->setNumberOfOccupiedLevels(alpha_electrons);
1104 this->setNumberOfOccupiedLevelsBeta(beta_electrons);
1105 this->setNumberOfAlphaElectrons(alpha_electrons);
1106 this->setNumberOfBetaElectrons(beta_electrons);
1107 // spin is the MULTIPLICITY (2S+1) convention throughout this codebase
1108 // (spin=1 is singlet, spin=2 is doublet, etc.) -- 2S itself is exactly
1109 // the alpha-beta electron-count difference, matching how DFTEngine's
1110 // own Initialize() derives num_alpha_electrons_/num_beta_electrons_
1111 // from a user-specified charge+spin pair in the first place.
1112 Index spin = (alpha_electrons - beta_electrons) + 1;
1113 this->setChargeAndSpin(orbitalsA.getCharge() + orbitalsB.getCharge(), spin);
1114
1115 this->OrderMOsbyEnergyAlpha();
1116 this->OrderMOsbyEnergyBeta();
1117}
1118
1119void Orbitals::WriteToCpt(const std::string& filename) const {
1120 std::lock_guard<std::recursive_mutex> lock(
1123 WriteToCpt(cpf);
1124}
1125
1127 CheckpointWriter writer = f.getWriter("/QMdata");
1128 WriteToCpt(writer);
1129 WriteBasisSetsToCpt(writer);
1130}
1131
1133 CheckpointWriter dftWriter = w.openChild("dft");
1134 dftbasis_.WriteToCpt(dftWriter);
1135 CheckpointWriter auxWriter = w.openChild("aux");
1136 auxbasis_.WriteToCpt(auxWriter);
1137}
1138
1140 w(votca::tools::ToolsVersionStr(), "XTPVersion");
1141 w(orbitals_version(), "version");
1142 w(occupied_levels_, "occupied_levels");
1143 w(occupied_levels_beta_, "occupied_levels_beta");
1144 w(number_alpha_electrons_, "number_alpha_electrons");
1145 w(number_beta_electrons_, "number_beta_electrons");
1146 w(total_charge_, "charge");
1147 w(total_spin_, "spin");
1148
1149 w(mos_, "mos");
1150 w(mos_beta_, "mos_beta");
1151 w(occupations_, "occupations");
1152 w(active_electrons_, "active_electrons");
1153 w(mos_embedding_, "mos_embedding");
1154 w(lmos_, "LMOs");
1155 w(lmos_energies_, "LMOs_energies");
1156 w(inactivedensity_, "inactivedensity");
1157 w(expandedMOs_, "TruncMOsFullBasis");
1158
1159 CheckpointWriter molgroup = w.openChild("qmmolecule");
1160 atoms_.WriteToCpt(molgroup);
1161
1162 w(qm_energy_, "qm_energy");
1163 w(forces_, "forces");
1164 w(qm_package_, "qm_package");
1165
1166 w(rpamin_, "rpamin");
1167 w(rpamax_, "rpamax");
1168 w(qpmin_, "qpmin");
1169 w(qpmax_, "qpmax");
1170 w(bse_vmin_, "bse_vmin");
1171 w(bse_cmax_, "bse_cmax");
1172 w(functionalname_, "XCFunctional");
1173 w(grid_quality_, "XC_grid_quality");
1174 w(ScaHFX_, "ScaHFX");
1175
1176 w(useTDA_, "useTDA");
1177 w(ECP_, "ECP");
1178
1179 w(rpa_inputenergies_, "RPA_inputenergies");
1180 w(QPpert_energies_, "QPpert_energies");
1181
1182 w(QPdiag_, "QPdiag");
1183
1184 w(BSE_singlet_, "BSE_singlet");
1185
1186 w(transition_dipoles_, "transition_dipoles");
1187
1188 w(BSE_triplet_, "BSE_triplet");
1189
1190 std::uint8_t tmp = use_Hqp_offdiag_ ? 1u : 0u;
1191 w(tmp, "use_Hqp_offdiag");
1192 std::uint8_t tmp_qsgw = is_qsgw_ ? 1u : 0u;
1193 w(tmp_qsgw, "is_qsgw");
1194
1195 w(BSE_singlet_energies_dynamic_, "BSE_singlet_dynamic");
1196
1197 w(BSE_triplet_energies_dynamic_, "BSE_triplet_dynamic");
1198
1199 w(CalcType_, "CalcType");
1200
1201 // spin GW
1202 w(rpa_inputenergies_alpha_, "RPA_inputenergies_alpha");
1203 w(rpa_inputenergies_beta_, "RPA_inputenergies_beta");
1204
1205 w(QPpert_energies_alpha_, "QPpert_energies_alpha");
1206 w(QPpert_energies_beta_, "QPpert_energies_beta");
1207
1208 w(QPdiag_alpha_, "QPdiag_alpha");
1209 w(QPdiag_beta_, "QPdiag_beta");
1210
1211 w(BSE_uks_, "BSE_uks");
1212 w(BSE_uks_energies_dynamic_, "BSE_uks_dynamic");
1213}
1214
1215void Orbitals::ReadFromCpt(const std::string& filename) {
1216 std::lock_guard<std::recursive_mutex> lock(
1219 ReadFromCpt(cpf);
1220}
1221
1223 CheckpointReader reader = f.getReader("/QMdata");
1224 ReadFromCpt(reader);
1225 ReadBasisSetsFromCpt(reader);
1226}
1227
1229 CheckpointReader dftReader = r.openChild("dft");
1230 dftbasis_.ReadFromCpt(dftReader);
1231 CheckpointReader auxReader = r.openChild("aux");
1232 auxbasis_.ReadFromCpt(auxReader);
1233}
1234
1236 r(occupied_levels_, "occupied_levels");
1237 r(number_alpha_electrons_, "number_alpha_electrons");
1238
1239 int version;
1240 r(version, "version");
1241
1242 auto r_optional = [&r](auto& obj, const std::string& key) {
1243 try {
1244 r(obj, key);
1245 } catch (std::runtime_error&) {
1246 // Older checkpoints may not contain this field.
1247 }
1248 };
1249
1250 // Read qmatoms
1251 CheckpointReader molgroup = r.openChild("qmmolecule");
1252 atoms_.ReadFromCpt(molgroup);
1253
1254 r(qm_energy_, "qm_energy");
1255 try {
1256 r(forces_, "forces");
1257 } catch (std::runtime_error& e) {
1258 ; // older checkpoint files won't have this field -- leave forces_
1259 // empty (hasForces() == false), same convention as LMOs below.
1260 }
1261 r(qm_package_, "qm_package");
1262 try {
1263 r(lmos_, "LMOs");
1264 r(lmos_energies_, "LMOs_energies");
1265 } catch (std::runtime_error& e) {
1266 ;
1267 }
1268
1269 r(version, "version");
1270 r(mos_, "mos");
1271 r(mos_embedding_, "mos_embedding");
1272 r(active_electrons_, "active_electrons");
1273 r(inactivedensity_, "inactivedensity");
1274 r(CalcType_, "CalcType");
1275 r(expandedMOs_, "TruncMOsFullBasis");
1276
1277 // spin info available from version 6 or higher
1278 if (version > 5) {
1279 r(occupied_levels_beta_, "occupied_levels_beta");
1280 r(number_beta_electrons_, "number_beta_electrons");
1281 r(total_charge_, "charge");
1282 r(total_spin_, "spin");
1283 r(mos_beta_, "mos_beta");
1284 r(occupations_, "occupations");
1285 }
1286
1287 if (version < 3) {
1288 // clang-format off
1289 std::array<Index, 49> votcaOrder_old = {
1290 0, // s
1291 0, -1, 1, // p
1292 0, -1, 1, -2, 2, // d
1293 0, -1, 1, -2, 2, -3, 3, // f
1294 0, -1, 1, -2, 2, -3, 3, -4, 4, // g
1295 0, -1, 1, -2, 2, -3, 3, -4, 4,-5,5, // h
1296 0, -1, 1, -2, 2, -3, 3, -4, 4,-5,5,-6,6 // i
1297 };
1298 // clang-format on
1299
1300 std::array<Index, 49> multiplier;
1301 multiplier.fill(1);
1302 OrbReorder ord(votcaOrder_old, multiplier);
1303 ord.reorderOrbitals(mos_.eigenvectors(), this->getDftBasis());
1304 }
1305
1306 if (version < 5) { // we need to construct the basissets, NB. can only be
1307 // done after reading the atoms.
1308 std::string dft_basis_name;
1309 std::string aux_basis_name;
1310 r(dft_basis_name, "dftbasis");
1311 r(aux_basis_name, "auxbasis");
1312 this->SetupDftBasis(dft_basis_name);
1313 this->SetupAuxBasis(aux_basis_name);
1314 }
1315
1316 r(rpamin_, "rpamin");
1317 r(rpamax_, "rpamax");
1318 r(qpmin_, "qpmin");
1319 r(qpmax_, "qpmax");
1320 r(bse_vmin_, "bse_vmin");
1321 r(bse_cmax_, "bse_cmax");
1323 try {
1324 r(functionalname_, "XCFunctional");
1325 r(grid_quality_, "XC_grid_quality");
1326 } catch (std::runtime_error& e) {
1327 grid_quality_ = "medium";
1328 }
1329 r(ScaHFX_, "ScaHFX");
1330 r(useTDA_, "useTDA");
1331 r(ECP_, "ECP");
1332
1333 r(rpa_inputenergies_, "RPA_inputenergies");
1334
1335 r(QPpert_energies_, "QPpert_energies");
1336 r(QPdiag_, "QPdiag");
1337
1338 // Optional unrestricted GW data
1339 r_optional(rpa_inputenergies_alpha_, "RPA_inputenergies_alpha");
1340 r_optional(rpa_inputenergies_beta_, "RPA_inputenergies_beta");
1341
1342 r_optional(QPpert_energies_alpha_, "QPpert_energies_alpha");
1343 r_optional(QPpert_energies_beta_, "QPpert_energies_beta");
1344
1345 r_optional(QPdiag_alpha_, "QPdiag_alpha");
1346 r_optional(QPdiag_beta_, "QPdiag_beta");
1347 r_optional(BSE_uks_, "BSE_uks");
1348 r_optional(BSE_uks_energies_dynamic_, "BSE_uks_dynamic");
1349 r(BSE_singlet_, "BSE_singlet");
1350
1351 r(transition_dipoles_, "transition_dipoles");
1352
1353 r(BSE_triplet_, "BSE_triplet");
1354
1355 std::uint8_t tmp = 0;
1356 r(tmp, "use_Hqp_offdiag");
1357 use_Hqp_offdiag_ = (tmp != 0);
1358 std::uint8_t tmp_qsgw = 0;
1359 if (version > 8) {
1360 r(tmp_qsgw, "is_qsgw");
1361 }
1362 is_qsgw_ = (tmp_qsgw != 0);
1363
1364 if (version > 1) {
1365 r(BSE_singlet_energies_dynamic_, "BSE_singlet_dynamic");
1366
1367 r(BSE_triplet_energies_dynamic_, "BSE_triplet_dynamic");
1368 }
1369 // Backward compatibility for older restricted checkpoints that may not
1370 // contain explicit beta-electron information.
1372 if (number_alpha_electrons_ == 0) {
1374 }
1375 if (number_beta_electrons_ == 0) {
1377 }
1378 }
1379}
1380
1381/*********************************************
1382 * Extensions Spin-DFT
1383 **********************************************/
1384
1385// Spin-resolved ground-state density matrices.
1386//
1387// The returned pair is (P^alpha, P^beta) with
1388//
1389// P^sigma = C_occ^sigma (C_occ^sigma)^T
1390//
1391// for unrestricted calculations. In the restricted branch the same spatial MO
1392// coefficients are reused and distributed over alpha/beta occupations according
1393// to the stored electron counts.
1395 const {
1396 if (!hasMOs()) {
1397 throw std::runtime_error("Orbitals file does not contain MO coefficients");
1398 }
1399
1400 std::array<Eigen::MatrixXd, 2> result;
1401 result[0] = Eigen::MatrixXd::Zero(mos_.eigenvectors().rows(),
1402 mos_.eigenvectors().rows());
1403 result[1] = Eigen::MatrixXd::Zero(mos_.eigenvectors().rows(),
1404 mos_.eigenvectors().rows());
1405
1407 if (occupied_levels_ > 0) {
1408 const Eigen::MatrixXd occ_a =
1409 mos_.eigenvectors().leftCols(occupied_levels_);
1410 result[0] = occ_a * occ_a.transpose();
1411 }
1412 if (occupied_levels_beta_ > 0) {
1413 const Eigen::MatrixXd occ_b =
1414 mos_beta_.eigenvectors().leftCols(occupied_levels_beta_);
1415 result[1] = occ_b * occ_b.transpose();
1416 }
1417 return result;
1418 }
1419
1420 // Restricted branch:
1421 // Prefer explicit alpha/beta electron counts, but fall back to the legacy
1422 // occupied_levels_ convention when those counts were never set.
1425
1426 if (n_alpha == 0 && n_beta == 0 && occupied_levels_ > 0) {
1427 n_alpha = occupied_levels_;
1428 n_beta = occupied_levels_;
1429 }
1430
1431 const Index n_docc = std::min(n_alpha, n_beta);
1432 const Index n_socc_alpha = n_alpha - n_docc;
1433 const Index n_socc_beta = n_beta - n_docc;
1434
1435 if (n_docc > 0) {
1436 const Eigen::MatrixXd docc = mos_.eigenvectors().leftCols(n_docc);
1437 const Eigen::MatrixXd d_docc = docc * docc.transpose();
1438 result[0] += d_docc;
1439 result[1] += d_docc;
1440 }
1441
1442 if (n_socc_alpha > 0) {
1443 const Eigen::MatrixXd socc_a =
1444 mos_.eigenvectors().middleCols(n_docc, n_socc_alpha);
1445 result[0] += socc_a * socc_a.transpose();
1446 }
1447
1448 if (n_socc_beta > 0) {
1449 const Eigen::MatrixXd socc_b =
1450 mos_.eigenvectors().middleCols(n_docc + n_socc_alpha, n_socc_beta);
1451 result[1] += socc_b * socc_b.transpose();
1452 }
1453
1454 return result;
1455}
1456
1457} // namespace xtp
1458} // namespace votca
const Eigen::VectorXd & eigenvalues() const
Definition eigensystem.h:30
const Eigen::MatrixXd & eigenvectors() const
Definition eigensystem.h:33
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
void setCenter(const Eigen::Vector3d &r)
Definition aomatrix.h:94
const std::array< Eigen::MatrixXd, 3 > & Matrix() const
Definition aomatrix.h:92
void Fill(const AOBasis &aobasis) final
void Load(const std::string &name)
Definition basisset.cc:149
CheckpointReader getReader()
CheckpointWriter getWriter()
CheckpointReader openChild(const std::string &childName) const
CheckpointWriter openChild(const std::string &childName) const
void reorderOrbitals(Eigen::MatrixXd &moCoefficients, const AOBasis &basis)
Definition orbreorder.cc:74
tools::EigenSystem QPdiag_alpha_
Definition orbitals.h:875
Index number_beta_electrons_
Definition orbitals.h:796
void CalcCoupledTransition_Dipoles()
Definition orbitals.cc:774
bool hasQPdiag() const
Definition orbitals.h:464
Index getCharge() const
Return the stored total charge.
Definition orbitals.h:254
Eigen::MatrixXd occupations_
Definition orbitals.h:804
Eigen::VectorXd rpa_inputenergies_
Definition orbitals.h:850
Index getHomo() const
Return the default HOMO index used by spin-agnostic callers.
Definition orbitals.h:107
Eigen::VectorXd BSE_uks_energies_dynamic_
Definition orbitals.h:879
Eigen::MatrixXd TransitionDensityMatrix(const QMState &state) const
Definition orbitals.cc:331
double getDFTTotalEnergy() const
Return the stored total DFT energy.
Definition orbitals.h:292
Index getSpin() const
Return the stored spin multiplicity.
Definition orbitals.h:252
Eigen::MatrixXd CalculateQParticleAORepresentation() const
Transform quasiparticle amplitudes into the AO representation.
Definition orbitals.cc:265
std::array< Eigen::MatrixXd, 2 > DensityMatrixExcitedState_R(const QMState &state) const
Definition orbitals.cc:551
const tools::EigenSystem & MOs_beta() const
Return read-only access to beta-spin molecular orbitals.
Definition orbitals.h:202
bool hasUnrestrictedOrbitals() const
Report whether separate beta-spin orbitals are present.
Definition orbitals.h:695
Eigen::VectorXd lmos_energies_
Definition orbitals.h:809
tools::EigenSystem BSE_singlet_
Definition orbitals.h:857
tools::EigenSystem QPdiag_beta_
Definition orbitals.h:876
void OrderMOsbyEnergyBeta()
Same as OrderMOsbyEnergyAlpha, but for the BETA channel (mos_beta_).
Definition orbitals.cc:947
void PrepareDimerGuess(const Orbitals &orbitalsA, const Orbitals &orbitalsB)
Guess for a dimer based on monomer orbitals.
Definition orbitals.cc:966
tools::EigenSystem mos_beta_
Definition orbitals.h:803
std::string grid_quality_
Definition orbitals.h:847
bool hasMOs() const
Report whether alpha/restricted molecular orbitals are available.
Definition orbitals.h:185
double getTotalStateEnergy(const QMState &state) const
Return the absolute total energy of the requested state in Hartree.
Definition orbitals.cc:690
Eigen::VectorXd BSE_triplet_energies_dynamic_
Definition orbitals.h:863
void ReadBasisSetsFromCpt(CheckpointReader &r)
Deserialize attached AO basis sets from an already-open checkpoint reader.
Definition orbitals.cc:1228
std::array< Eigen::MatrixXd, 2 > DensityMatrixGroundStateSpinResolved() const
Definition orbitals.cc:1394
Eigen::MatrixXd QPpert_energies_beta_
Definition orbitals.h:873
void WriteBasisSetsToCpt(CheckpointWriter &w) const
Definition orbitals.cc:1132
std::vector< Index > CheckDegeneracy(Index level, double energy_difference) const
Definition orbitals.cc:56
Eigen::MatrixXd inactivedensity_
Definition orbitals.h:811
std::string getCalculationType() const
Return the stored calculation-type tag.
Definition orbitals.h:242
std::array< Eigen::MatrixXd, 3 > CalcFreeTransition_Dipoles() const
Definition orbitals.cc:756
Eigen::MatrixXd forces_
Definition orbitals.h:825
Eigen::MatrixXd CalcAuxMat_cc(const Eigen::VectorXd &coeffs) const
Definition orbitals.cc:599
Eigen::MatrixXd expandedMOs_
Definition orbitals.h:812
void setNumberOfAlphaElectrons(Index electrons)
Store the total number of alpha electrons.
Definition orbitals.h:139
Eigen::VectorXd Oscillatorstrengths() const
Definition orbitals.cc:657
std::vector< Eigen::Vector3d > transition_dipoles_
Definition orbitals.h:858
static constexpr int orbitals_version()
Definition orbitals.h:887
std::string functionalname_
Definition orbitals.h:846
Eigen::MatrixXd lmos_
Definition orbitals.h:808
Eigen::MatrixXd DensityMatrixWithoutGS(const QMState &state) const
Definition orbitals.cc:121
Eigen::MatrixXd DensityMatrixFull(const QMState &state) const
Definition orbitals.cc:150
Eigen::MatrixXd CalcAuxMat_vv(const Eigen::VectorXd &coeffs) const
Definition orbitals.cc:593
void PrepareDimerGuessMixedSpin(const Orbitals &orbitalsA, const Orbitals &orbitalsB)
Guess for a dimer of two monomers with independently ARBITRARY charge and spin, built by combining ea...
Definition orbitals.cc:1046
void SetupAuxBasis(std::string aux_basis_name)
Definition orbitals.cc:108
std::string qm_package_
Definition orbitals.h:848
Index getLumoAlpha() const
Return the alpha-spin LUMO index inferred from the stored electron counts.
Definition orbitals.h:75
void setNumberOfBetaElectrons(Index electrons)
Store the total number of beta electrons.
Definition orbitals.h:144
tools::EigenSystem BSE_triplet_
Definition orbitals.h:859
void setBSEindices(Index vmin, Index cmax)
Definition orbitals.h:409
const Eigen::MatrixXd & getInactiveDensity() const
Return the inactive-region density matrix used in embedding workflows.
Definition orbitals.h:685
std::vector< Index > SortEnergies()
Definition orbitals.cc:81
Index getHomoBeta() const
Return the beta-spin HOMO index.
Definition orbitals.h:101
std::array< Eigen::MatrixXd, 2 > DensityMatrixExcitedState_AR(const QMState &state) const
Definition orbitals.cc:605
double getExcitedStateEnergy(const QMState &state) const
Return the excitation energy of the requested state in Hartree.
Definition orbitals.cc:699
void setECPName(const std::string &ECP)
Store the effective core potential label.
Definition orbitals.h:170
void setNumberOfOccupiedLevels(Index occupied_levels)
Definition orbitals.h:122
Index occupied_levels_beta_
Definition orbitals.h:794
Orbitals()
Construct an empty orbital container with default metadata.
Definition orbitals.cc:46
tools::EigenSystem mos_
Definition orbitals.h:802
void OrderMOsbyEnergyAlpha()
Definition orbitals.cc:935
tools::EigenSystem QPdiag_
Definition orbitals.h:855
Eigen::VectorXd rpa_inputenergies_beta_
Definition orbitals.h:870
Eigen::MatrixXd DensityMatrixKSstate(const QMState &state) const
Build the AO density matrix for a single KS orbital state.
Definition orbitals.cc:232
Eigen::MatrixXd DensityMatrixQuasiParticle(const QMState &state) const
Build the AO density matrix for a quasiparticle state.
Definition orbitals.cc:280
Index getBasisSetSize() const
Return the number of AO basis functions in the DFT basis.
Definition orbitals.h:72
std::vector< Index > SortEnergiesBeta()
Definition orbitals.cc:925
Eigen::MatrixXd QPpert_energies_alpha_
Definition orbitals.h:872
Eigen::MatrixXd BSEOrbitals() const
Definition orbitals.cc:250
Eigen::VectorXd BSE_singlet_energies_dynamic_
Definition orbitals.h:862
Index getGWAmin() const
Return the lower GW orbital index.
Definition orbitals.h:375
const tools::EigenSystem & MOs() const
Return read-only access to alpha/restricted molecular orbitals.
Definition orbitals.h:192
Eigen::MatrixXd DensityMatrixGroundState() const
Build the ground-state AO density matrix from the stored orbitals.
Definition orbitals.cc:220
std::string CalcType_
Definition orbitals.h:800
Index getLumoBeta() const
Definition orbitals.h:87
const QMMolecule & QMAtoms() const
Return read-only access to the molecular geometry.
Definition orbitals.h:262
tools::EigenSystem mos_embedding_
Definition orbitals.h:806
Eigen::VectorXd rpa_inputenergies_alpha_
Definition orbitals.h:869
std::vector< Index > SortEnergiesAlpha()
Definition orbitals.cc:913
void ReadFromCpt(const std::string &filename)
Read the orbital container from a checkpoint file on disk.
Definition orbitals.cc:1215
tools::EigenSystem BSE_uks_
Definition orbitals.h:878
void setNumberOfOccupiedLevelsBeta(Index occupied_levels_beta)
Store the number of occupied beta-spin orbitals.
Definition orbitals.h:134
void setChargeAndSpin(Index charge, Index spin)
Definition orbitals.h:246
Eigen::VectorXd QPpert_energies_
Definition orbitals.h:852
void WriteToCpt(const std::string &filename) const
Write the orbital container to a checkpoint file on disk.
Definition orbitals.cc:1119
const std::string & getECPName() const
Return the effective core potential label.
Definition orbitals.h:167
const std::string & getDFTbasisName() const
Return the DFT basis-set name.
Definition orbitals.h:318
void SetupDftBasis(std::string basis_name)
Build and attach the DFT AO basis from the stored molecular geometry.
Definition orbitals.cc:99
std::array< Eigen::MatrixXd, 2 > DensityMatrixExcitedState(const QMState &state) const
Build separate hole and electron AO densities for an excited state.
Definition orbitals.cc:429
std::string ECP_
Definition orbitals.h:797
Index number_alpha_electrons_
Definition orbitals.h:795
Eigen::Vector3d CalcElDipole(const QMState &state) const
Compute the electronic dipole moment associated with a state density.
Definition orbitals.cc:303
Index getHomoAlpha() const
Return the alpha-spin HOMO index.
Definition orbitals.h:83
const AOBasis & getDftBasis() const
Return the DFT AO basis, throwing if it has not been initialized.
Definition orbitals.h:328
QMMolecule atoms_
Definition orbitals.h:814
Index getLumo() const
Return the default LUMO index used by spin-agnostic callers.
Definition orbitals.h:105
container for QM atoms
Definition qmatom.h:37
std::string ToLongString() const
Definition qmstate.cc:72
bool isKSState() const
Definition qmstate.h:92
bool isPQPState() const
Definition qmstate.h:94
bool isExciton() const
Definition qmstate.h:73
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
bool isTransition() const
Definition qmstate.h:156
const QMStateType & Type() const
Definition qmstate.h:154
const std::string & ToolsVersionStr()
Definition version.cc:33
std::recursive_mutex & Hdf5Mutex()
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