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
251 if (!hasQPdiag()) {
252 throw std::runtime_error("Orbitals file does not contain QP coefficients");
253 }
254 return mos_.eigenvectors().middleCols(qpmin_, qpmax_ - qpmin_ + 1) *
255 QPdiag_.eigenvectors();
256}
257
258// Determine QuasiParticle Density Matrix
259// Quasiparticle projector in the AO basis,
260//
261// P^QP_(mu,nu) = Lambda_(mu,n) Lambda_(nu,n),
262//
263// where Lambda is the AO representation of the quasiparticle eigenvectors in
264// the selected GW subspace.
266 const QMState& state) const {
267 if (this->hasUnrestrictedOrbitals()) {
268 throw std::runtime_error(
269 "DensityMatrixQuasiParticle not implemented for open-shell systems.");
270 }
271 if (state.Type() != QMStateType::DQPstate) {
272 throw std::runtime_error("State:" + state.ToString() +
273 " is not a quasiparticle state");
274 }
275 Eigen::MatrixXd lambda = CalculateQParticleAORepresentation();
276 Eigen::MatrixXd dmatQP = lambda.col(state.StateIdx() - qpmin_) *
277 lambda.col(state.StateIdx() - qpmin_).transpose();
278 return dmatQP;
279}
280
281// Electric dipole moment relative to the molecular origin,
282//
283// mu = sum_A Z_A (R_A - R_0) - Tr[P M],
284//
285// where M contains the AO dipole integrals for x, y, and z and P is the full
286// state density matrix. Transition densities omit the nuclear term and return
287// only the electronic transition dipole.
288Eigen::Vector3d Orbitals::CalcElDipole(const QMState& state) const {
289 Eigen::Vector3d nuclei_dip = Eigen::Vector3d::Zero();
290 if (!state.isTransition()) {
291 for (const QMAtom& atom : atoms_) {
292 nuclei_dip += (atom.getPos() - atoms_.getPos()) * atom.getNuccharge();
293 }
294 }
295 AOBasis basis = getDftBasis();
296 AODipole dipole;
297 dipole.setCenter(atoms_.getPos());
298 dipole.Fill(basis);
299
300 Eigen::MatrixXd dmat = this->DensityMatrixFull(state);
301 Eigen::Vector3d electronic_dip;
302 for (Index i = 0; i < 3; ++i) {
303 electronic_dip(i) = dmat.cwiseProduct(dipole.Matrix()[i]).sum();
304 }
305 return nuclei_dip - electronic_dip;
306}
307
308// AO transition density for a singlet BSE excitation.
309//
310// In TDA form this evaluates
311//
312// gamma_(mu,nu) = sqrt(2) sum_(v,c) A_(v,c) C_(mu,v) C_(nu,c),
313//
314// and for full BSE the resonant and anti-resonant amplitudes are added before
315// the AO transformation.
316Eigen::MatrixXd Orbitals::TransitionDensityMatrix(const QMState& state) const {
317
318 if (state.Type() == QMStateType::ExcitonUKS) {
319 if (!this->hasUnrestrictedOrbitals()) {
320 throw std::runtime_error(
321 "ExcitonUKS transition density requested, but no unrestricted "
322 "orbitals are present.");
323 }
324
325 const Eigen::MatrixXd& BSECoefs = BSE_uks_.eigenvectors();
326 if (BSECoefs.cols() < state.StateIdx() + 1 || BSECoefs.rows() < 2) {
327 throw std::runtime_error(
328 "Orbitals object has no information about state:" + state.ToString());
329 }
330
331 Eigen::VectorXd coeffs = BSECoefs.col(state.StateIdx());
332 if (!useTDA_) {
333 coeffs += BSE_uks_.eigenvectors2().col(state.StateIdx());
334 }
335
336 const Index alpha_vtotal = getHomoAlpha() - bse_vmin_ + 1;
337 const Index alpha_ctotal = bse_cmax_ - getLumoAlpha() + 1;
338 const Index beta_vtotal = getHomoBeta() - bse_vmin_ + 1;
339 const Index beta_ctotal = bse_cmax_ - getLumoBeta() + 1;
340
341 const Index alpha_size = alpha_vtotal * alpha_ctotal;
342 const Index beta_size = beta_vtotal * beta_ctotal;
343
344 if (coeffs.size() != alpha_size + beta_size) {
345 throw std::runtime_error(
346 "ExcitonUKS coefficient vector size does not match expected "
347 "alpha+beta BSE space dimensions.");
348 }
349
350 Eigen::MatrixXd tdm = Eigen::MatrixXd::Zero(mos_.eigenvectors().rows(),
351 mos_.eigenvectors().rows());
352
353 if (alpha_size > 0) {
354 const Eigen::VectorXd coeffs_alpha = coeffs.head(alpha_size);
355 const auto occ_alpha =
356 mos_.eigenvectors().middleCols(bse_vmin_, alpha_vtotal);
357 const auto virt_alpha =
358 mos_.eigenvectors().middleCols(getLumoAlpha(), alpha_ctotal);
359 const Eigen::Map<const Eigen::MatrixXd> mat_alpha(
360 coeffs_alpha.data(), alpha_ctotal, alpha_vtotal);
361
362 tdm += occ_alpha * mat_alpha.transpose() * virt_alpha.transpose();
363 }
364
365 if (beta_size > 0) {
366 const Eigen::VectorXd coeffs_beta = coeffs.tail(beta_size);
367 const auto occ_beta =
368 mos_beta_.eigenvectors().middleCols(bse_vmin_, beta_vtotal);
369 const auto virt_beta =
370 mos_beta_.eigenvectors().middleCols(getLumoBeta(), beta_ctotal);
371 const Eigen::Map<const Eigen::MatrixXd> mat_beta(
372 coeffs_beta.data(), beta_ctotal, beta_vtotal);
373
374 tdm += occ_beta * mat_beta.transpose() * virt_beta.transpose();
375 }
376
377 return tdm;
378 }
379
380 if (this->hasUnrestrictedOrbitals()) {
381 throw std::runtime_error(
382 "TransitionDensityMatrix not implemented for open-shell systems "
383 "except ExcitonUKS.");
384 }
385 if (state.Type() != QMStateType::Singlet) {
386 throw std::runtime_error(
387 "Spin type not known for transition density matrix. Available only for "
388 "singlet");
389 }
390 const Eigen::MatrixXd& BSECoefs = BSE_singlet_.eigenvectors();
391 if (BSECoefs.cols() < state.StateIdx() + 1 || BSECoefs.rows() < 2) {
392 throw std::runtime_error("Orbitals object has no information about state:" +
393 state.ToString());
394 }
395
396 Eigen::VectorXd coeffs = BSECoefs.col(state.StateIdx());
397
398 if (!useTDA_) {
399 coeffs += BSE_singlet_.eigenvectors2().col(state.StateIdx());
400 }
401 coeffs *= std::sqrt(2.0);
402 auto occlevels = mos_.eigenvectors().middleCols(bse_vmin_, bse_vtotal_);
403 auto virtlevels = mos_.eigenvectors().middleCols(bse_cmin_, bse_ctotal_);
404 Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), bse_ctotal_,
406
407 return occlevels * mat.transpose() * virtlevels.transpose();
408}
409
410// Spin-summed excited-state density correction split into hole and electron
411// blocks. For full BSE the anti-resonant contribution is subtracted from the
412// resonant part, yielding the usual RPA/BSE excited-state density.
413std::array<Eigen::MatrixXd, 2> Orbitals::DensityMatrixExcitedState(
414 const QMState& state) const {
415
416 if (state.Type() == QMStateType::ExcitonUKS) {
417 if (!this->hasUnrestrictedOrbitals()) {
418 throw std::runtime_error(
419 "ExcitonUKS excited-state density requested, but no unrestricted "
420 "orbitals are present.");
421 }
422
423 const Eigen::MatrixXd& X = BSE_uks_.eigenvectors();
424 if (X.cols() < state.StateIdx() + 1 || X.rows() < 2) {
425 throw std::runtime_error(
426 "Orbitals object has no information about state:" + state.ToString());
427 }
428
429 const Index alpha_vtotal = getHomoAlpha() - bse_vmin_ + 1;
430 const Index alpha_ctotal = bse_cmax_ - getLumoAlpha() + 1;
431 const Index beta_vtotal = getHomoBeta() - bse_vmin_ + 1;
432 const Index beta_ctotal = bse_cmax_ - getLumoBeta() + 1;
433
434 const Index alpha_size = alpha_vtotal * alpha_ctotal;
435 const Index beta_size = beta_vtotal * beta_ctotal;
436
437 if (X.rows() != alpha_size + beta_size) {
438 throw std::runtime_error(
439 "ExcitonUKS coefficient vector size does not match expected "
440 "alpha+beta BSE space dimensions.");
441 }
442
443 const Index nao = mos_.eigenvectors().rows();
444 std::array<Eigen::MatrixXd, 2> dmat;
445 dmat[0] = Eigen::MatrixXd::Zero(nao, nao); // hole
446 dmat[1] = Eigen::MatrixXd::Zero(nao, nao); // electron
447
448 auto add_resonant = [&](const Eigen::VectorXd& coeffs, bool alpha_spin) {
449 if (coeffs.size() == 0) {
450 return;
451 }
452
453 const Index vtotal = alpha_spin ? alpha_vtotal : beta_vtotal;
454 const Index ctotal = alpha_spin ? alpha_ctotal : beta_ctotal;
455
456 const auto& mos_spin =
457 alpha_spin ? mos_.eigenvectors() : mos_beta_.eigenvectors();
458 const Index lumo = alpha_spin ? getLumoAlpha() : getLumoBeta();
459
460 const auto occlevels = mos_spin.middleCols(bse_vmin_, vtotal);
461 const auto virtlevels = mos_spin.middleCols(lumo, ctotal);
462
463 const Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), ctotal,
464 vtotal);
465
466 dmat[0] += occlevels * (mat.transpose() * mat) * occlevels.transpose();
467 dmat[1] += virtlevels * (mat * mat.transpose()) * virtlevels.transpose();
468 };
469
470 auto subtract_antiresonant = [&](const Eigen::VectorXd& coeffs,
471 bool alpha_spin) {
472 if (coeffs.size() == 0) {
473 return;
474 }
475
476 const Index vtotal = alpha_spin ? alpha_vtotal : beta_vtotal;
477 const Index ctotal = alpha_spin ? alpha_ctotal : beta_ctotal;
478
479 const auto& mos_spin =
480 alpha_spin ? mos_.eigenvectors() : mos_beta_.eigenvectors();
481 const Index lumo = alpha_spin ? getLumoAlpha() : getLumoBeta();
482
483 const auto occlevels = mos_spin.middleCols(bse_vmin_, vtotal);
484 const auto virtlevels = mos_spin.middleCols(lumo, ctotal);
485
486 const Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), ctotal,
487 vtotal);
488
489 // Full-BSE anti-resonant correction:
490 // hole -= virt * (B B^T) * virt^T
491 // electron -= occ * (B^T B) * occ^T
492 dmat[0] -= virtlevels * (mat * mat.transpose()) * virtlevels.transpose();
493 dmat[1] -= occlevels * (mat.transpose() * mat) * occlevels.transpose();
494 };
495
496 const Eigen::VectorXd Xstate = X.col(state.StateIdx());
497 add_resonant(Xstate.head(alpha_size), true);
498 add_resonant(Xstate.tail(beta_size), false);
499
500 if (!useTDA_) {
501 const Eigen::MatrixXd& Y = BSE_uks_.eigenvectors2();
502 if (Y.cols() < state.StateIdx() + 1 || Y.rows() != X.rows()) {
503 throw std::runtime_error(
504 "Orbitals object has inconsistent anti-resonant ExcitonUKS "
505 "coefficients for state:" +
506 state.ToString());
507 }
508
509 const Eigen::VectorXd Ystate = Y.col(state.StateIdx());
510 subtract_antiresonant(Ystate.head(alpha_size), true);
511 subtract_antiresonant(Ystate.tail(beta_size), false);
512 }
513
514 return dmat;
515 }
516
517 if (this->hasUnrestrictedOrbitals()) {
518 throw std::runtime_error(
519 "DensityMatrixExcitedState not implemented for open-shell systems "
520 "except ExcitonUKS.");
521 }
522
523 std::array<Eigen::MatrixXd, 2> dmat = DensityMatrixExcitedState_R(state);
524 if (!useTDA_) {
525 std::array<Eigen::MatrixXd, 2> dmat_AR =
527 dmat[0] -= dmat_AR[0];
528 dmat[1] -= dmat_AR[1];
529 }
530 return dmat;
531}
532
533// Excited state density matrix
534
535std::array<Eigen::MatrixXd, 2> Orbitals::DensityMatrixExcitedState_R(
536 const QMState& state) const {
537 if (!state.Type().isExciton()) {
538 throw std::runtime_error(
539 "Spin type not known for density matrix. Available are singlet and "
540 "triplet");
541 }
542
543 const Eigen::MatrixXd& BSECoefs = (state.Type() == QMStateType::Singlet)
544 ? BSE_singlet_.eigenvectors()
545 : BSE_triplet_.eigenvectors();
546 if (BSECoefs.cols() < state.StateIdx() + 1 || BSECoefs.rows() < 2) {
547 throw std::runtime_error("Orbitals object has no information about state:" +
548 state.ToString());
549 }
550 /******
551 *
552 * Density matrix for GW-BSE based excitations
553 *
554 * - electron contribution
555 * D_ab = \sum{vc} \sum{c'} A_{vc}A_{vc'} mo_a(c)mo_b(c')
556 *
557 * - hole contribution
558 * D_ab = \sum{vc} \sum{v'} A_{vc}A_{v'c} mo_a(v)mo_b(v')
559 *
560 */
561
562 Eigen::VectorXd coeffs = BSECoefs.col(state.StateIdx());
563
564 std::array<Eigen::MatrixXd, 2> dmatEX;
565 // hole part as matrix products
566 Eigen::MatrixXd occlevels =
567 mos_.eigenvectors().middleCols(bse_vmin_, bse_vtotal_);
568 dmatEX[0] = occlevels * CalcAuxMat_vv(coeffs) * occlevels.transpose();
569
570 // electron part as matrix products
571 Eigen::MatrixXd virtlevels =
572 mos_.eigenvectors().middleCols(bse_cmin_, bse_ctotal_);
573 dmatEX[1] = virtlevels * CalcAuxMat_cc(coeffs) * virtlevels.transpose();
574
575 return dmatEX;
576}
577
578Eigen::MatrixXd Orbitals::CalcAuxMat_vv(const Eigen::VectorXd& coeffs) const {
579 const Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), bse_ctotal_,
581 return mat.transpose() * mat;
582}
583
584Eigen::MatrixXd Orbitals::CalcAuxMat_cc(const Eigen::VectorXd& coeffs) const {
585 const Eigen::Map<const Eigen::MatrixXd> mat(coeffs.data(), bse_ctotal_,
587 return mat * mat.transpose();
588}
589
590std::array<Eigen::MatrixXd, 2> Orbitals::DensityMatrixExcitedState_AR(
591 const QMState& state) const {
592
593 if (!state.Type().isExciton()) {
594 throw std::runtime_error(
595 "Spin type not known for density matrix. Available are singlet and "
596 "triplet");
597 }
598
599 const Eigen::MatrixXd& BSECoefs_AR = (state.Type() == QMStateType::Singlet)
600 ? BSE_singlet_.eigenvectors2()
601 : BSE_triplet_.eigenvectors2();
602 if (BSECoefs_AR.cols() < state.StateIdx() + 1 || BSECoefs_AR.rows() < 2) {
603 throw std::runtime_error("Orbitals object has no information about state:" +
604 state.ToString());
605 }
606 /******
607 *
608 * Density matrix for GW-BSE based excitations
609 *
610 * - electron contribution
611 * D_ab = \sum{vc} \sum{v'} B_{vc}B_{v'c} mo_a(v)mo_b(v')
612 *
613 * - hole contribution
614 * D_ab = \sum{vc} \sum{c'} B_{vc}B_{vc'} mo_a(c)mo_b(c')
615 *
616 *
617 * more efficient:
618 *
619 * - electron contribution
620 * D_ab = \sum{v} \sum{v'} mo_a(v)mo_b(v') [ \sum{c} B_{vc}B_{v'c} ]
621 * = \sum{v} \sum{v'} mo_a(v)mo_b(v') B_{vv'}
622 *
623 * - hole contribution
624 * D_ab = \sum{c} \sum{c'} mo_a(c)mo_b(c') [ \sum{v} B_{vc}B_{vc'} ]
625 * = \sum{c} \sum{c'} mo_a(c)mo_b(c') B_{cc'}
626 *
627 */
628
629 Eigen::VectorXd coeffs = BSECoefs_AR.col(state.StateIdx());
630
631 std::array<Eigen::MatrixXd, 2> dmatAR;
632 Eigen::MatrixXd virtlevels =
633 mos_.eigenvectors().middleCols(bse_cmin_, bse_ctotal_);
634 dmatAR[0] = virtlevels * CalcAuxMat_cc(coeffs) * virtlevels.transpose();
635 // electron part as matrix products
636 Eigen::MatrixXd occlevels =
637 mos_.eigenvectors().middleCols(bse_vmin_, bse_vtotal_);
638 dmatAR[1] = occlevels * CalcAuxMat_vv(coeffs) * occlevels.transpose();
639
640 return dmatAR;
641}
642
646
647Eigen::VectorXd Orbitals::Oscillatorstrengths(const QMStateType& type) const {
648
649 const tools::EigenSystem* es = nullptr;
650
651 if (type == QMStateType::Singlet) {
652 es = &BSE_singlet_;
653 } else if (type == QMStateType::ExcitonUKS) {
654 es = &BSE_uks_;
655 } else {
656 throw std::runtime_error(
657 "Orbitals::Oscillatorstrengths only implemented for "
658 "restricted singlets and combined UKS excitons.");
659 }
660
661 Index size = Index(transition_dipoles_.size());
662 if (size > es->eigenvalues().size()) {
663 size = es->eigenvalues().size();
664 }
665
666 Eigen::VectorXd oscs = Eigen::VectorXd::Zero(size);
667 for (Index i = 0; i < size; ++i) {
668 // f = (2/3) * Omega * |d|^2
669 // Omega must be in Hartree, d in e*bohr
670 oscs(i) =
671 transition_dipoles_[i].squaredNorm() * 2.0 / 3.0 * es->eigenvalues()(i);
672 }
673 return oscs;
674}
675
676double Orbitals::getTotalStateEnergy(const QMState& state) const {
677 double total_energy = getDFTTotalEnergy();
678 if (state.Type() == QMStateType::Gstate) {
679 return total_energy;
680 }
681 total_energy += getExcitedStateEnergy(state);
682 return total_energy;
683}
684
685double Orbitals::getExcitedStateEnergy(const QMState& state) const {
686
687 double omega = 0.0;
688 if (state.isTransition()) {
689 throw std::runtime_error(
690 "Total Energy does not exist for transition state");
691 }
692
693 if (state.Type() == QMStateType::Singlet) {
694 if (BSE_singlet_.eigenvalues().size() < state.StateIdx() + 1) {
695 throw std::runtime_error("Orbitals::getTotalEnergy You want " +
696 state.ToString() +
697 " which has not been calculated");
698 }
699 omega = BSE_singlet_.eigenvalues()[state.StateIdx()];
700 } else if (state.Type() == QMStateType::Triplet) {
701 if (BSE_triplet_.eigenvalues().size() < state.StateIdx() + 1) {
702 throw std::runtime_error("Orbitals::getTotalEnergy You want " +
703 state.ToString() +
704 " which has not been calculated");
705 }
706 omega = BSE_triplet_.eigenvalues()[state.StateIdx()];
707 } else if (state.Type() == QMStateType::DQPstate) {
708 if (QPdiag_.eigenvalues().size() < state.StateIdx() + 1 - getGWAmin()) {
709 throw std::runtime_error("Orbitals::getTotalEnergy You want " +
710 state.ToString() +
711 " which has not been calculated");
712 }
713 return QPdiag_.eigenvalues()[state.StateIdx() - getGWAmin()];
714 } else if (state.Type() == QMStateType::KSstate) {
715 if (mos_.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 return mos_.eigenvalues()[state.StateIdx()];
721 } else if (state.Type() == QMStateType::PQPstate) {
722 if (this->QPpert_energies_.rows() < 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 QPpert_energies_(state.StateIdx() - getGWAmin(), 3);
728 } else if (state.Type() == QMStateType::LMOstate) {
729 if (lmos_energies_.size() < state.StateIdx() + 1) {
730 throw std::runtime_error(
731 "Orbitals::getTotalEnergy You want " + state.ToString() +
732 " which is a LMO for virtual orbitals. Not implemented.");
733 }
734 return lmos_energies_(state.StateIdx());
735 } else {
736 throw std::runtime_error(
737 "GetTotalEnergy only knows states:singlet,triplet,KS,DQP,PQP,LMOs");
738 }
739 return omega; // e.g. hartree
740}
741
742std::array<Eigen::MatrixXd, 3> Orbitals::CalcFreeTransition_Dipoles() const {
743 const Eigen::MatrixXd& dft_orbitals = mos_.eigenvectors();
744 AOBasis basis = getDftBasis();
745 // Testing electric dipole AOMatrix
746 AODipole dft_dipole;
747 dft_dipole.Fill(basis);
748
749 // now transition dipole elements for free interlevel transitions
750 std::array<Eigen::MatrixXd, 3> interlevel_dipoles;
751
752 Eigen::MatrixXd empty = dft_orbitals.middleCols(bse_cmin_, bse_ctotal_);
753 Eigen::MatrixXd occ = dft_orbitals.middleCols(bse_vmin_, bse_vtotal_);
754 for (Index i = 0; i < 3; i++) {
755 interlevel_dipoles[i] = empty.transpose() * dft_dipole.Matrix()[i] * occ;
756 }
757 return interlevel_dipoles;
758}
759
763
765
766 transition_dipoles_.clear();
767
768 if (type == QMStateType::Singlet) {
769 std::array<Eigen::MatrixXd, 3> interlevel_dipoles =
771
772 Index numofstates = BSE_singlet_.eigenvalues().size();
773 transition_dipoles_.reserve(numofstates);
774
775 const double sqrt2 = std::sqrt(2.0);
776
777 for (Index i_exc = 0; i_exc < numofstates; ++i_exc) {
778 Eigen::VectorXd coeffs = BSE_singlet_.eigenvectors().col(i_exc);
779 if (!useTDA_) {
780 coeffs += BSE_singlet_.eigenvectors2().col(i_exc);
781 }
782
783 Eigen::Map<Eigen::MatrixXd> mat(coeffs.data(), bse_ctotal_, bse_vtotal_);
784
785 Eigen::Vector3d tdipole = Eigen::Vector3d::Zero();
786 for (Index i = 0; i < 3; ++i) {
787 tdipole[i] = mat.cwiseProduct(interlevel_dipoles[i]).sum();
788 }
789
790 // Restricted singlet:
791 // the excitation is the spin-adapted combination of alpha and beta
792 // determinants, so the total transition dipole picks up sqrt(2).
793 transition_dipoles_.push_back(-sqrt2 * tdipole);
794 }
795 return;
796 }
797
798 if (type == QMStateType::ExcitonUKS) {
800 throw std::runtime_error(
801 "Orbitals::CalcCoupledTransition_Dipoles(ExcitonUKS) requires "
802 "unrestricted alpha/beta orbitals.");
803 }
804
805 const Index alpha_homo = getHomoAlpha();
806 const Index beta_homo = getHomoBeta();
807
808 const Index alpha_vtotal = alpha_homo - bse_vmin_ + 1;
809 const Index alpha_ctotal = bse_cmax_ - (alpha_homo + 1) + 1;
810 const Index alpha_size = alpha_vtotal * alpha_ctotal;
811
812 const Index beta_vtotal = beta_homo - bse_vmin_ + 1;
813 const Index beta_ctotal = bse_cmax_ - (beta_homo + 1) + 1;
814 const Index beta_size = beta_vtotal * beta_ctotal;
815
816 if (alpha_vtotal < 0 || alpha_ctotal < 0 || beta_vtotal < 0 ||
817 beta_ctotal < 0) {
818 throw std::runtime_error(
819 "Orbitals::CalcCoupledTransition_Dipoles(ExcitonUKS): "
820 "invalid UKS BSE window.");
821 }
822
823 AOBasis basis = getDftBasis();
824 AODipole dft_dipole;
825 dft_dipole.Fill(basis);
826
827 std::array<Eigen::MatrixXd, 3> interlevel_dipoles_alpha;
828 std::array<Eigen::MatrixXd, 3> interlevel_dipoles_beta;
829
830 const Eigen::MatrixXd virt_alpha =
831 mos_.eigenvectors().middleCols(alpha_homo + 1, alpha_ctotal);
832 const Eigen::MatrixXd occ_alpha =
833 mos_.eigenvectors().middleCols(bse_vmin_, alpha_vtotal);
834
835 const Eigen::MatrixXd virt_beta =
836 mos_beta_.eigenvectors().middleCols(beta_homo + 1, beta_ctotal);
837 const Eigen::MatrixXd occ_beta =
838 mos_beta_.eigenvectors().middleCols(bse_vmin_, beta_vtotal);
839
840 for (Index i = 0; i < 3; ++i) {
841 interlevel_dipoles_alpha[i] =
842 virt_alpha.transpose() * dft_dipole.Matrix()[i] * occ_alpha;
843 interlevel_dipoles_beta[i] =
844 virt_beta.transpose() * dft_dipole.Matrix()[i] * occ_beta;
845 }
846
847 const Index numofstates = BSE_uks_.eigenvalues().size();
848 transition_dipoles_.reserve(numofstates);
849
850 for (Index i_exc = 0; i_exc < numofstates; ++i_exc) {
851 Eigen::VectorXd coeffs_x = BSE_uks_.eigenvectors().col(i_exc);
852 Eigen::VectorXd coeffs = coeffs_x;
853 if (!useTDA_) {
854 coeffs += BSE_uks_.eigenvectors2().col(i_exc);
855 }
856
857 if (coeffs.size() != alpha_size + beta_size) {
858 throw std::runtime_error(
859 "Orbitals::CalcCoupledTransition_Dipoles(ExcitonUKS): "
860 "exciton vector size does not match alpha+beta sector sizes.");
861 }
862
863 Eigen::Map<const Eigen::MatrixXd> mat_alpha(coeffs.data(), alpha_ctotal,
864 alpha_vtotal);
865 Eigen::Map<const Eigen::MatrixXd> mat_beta(coeffs.data() + alpha_size,
866 beta_ctotal, beta_vtotal);
867
868 Eigen::Vector3d tdipole = Eigen::Vector3d::Zero();
869 for (Index i = 0; i < 3; ++i) {
870 tdipole[i] = mat_alpha.cwiseProduct(interlevel_dipoles_alpha[i]).sum() +
871 mat_beta.cwiseProduct(interlevel_dipoles_beta[i]).sum();
872 }
873
874 // Combined UKS exciton:
875 // alpha and beta sectors are already explicit components of the
876 // eigenvector, so there is NO extra sqrt(2) factor here.
877 transition_dipoles_.push_back(-tdipole);
878 }
879 return;
880 }
881
882 throw std::runtime_error(
883 "Orbitals::CalcCoupledTransition_Dipoles only implemented for "
884 "restricted singlets and combined UKS excitons.");
885}
886
888 std::vector<Index> sort_index = SortEnergies();
889 tools::EigenSystem MO_copy = mos_;
890 Index size = mos_.eigenvalues().size();
891 for (Index i = 0; i < size; ++i) {
892 mos_.eigenvalues()(i) = MO_copy.eigenvalues()(sort_index[i]);
893 }
894 for (Index i = 0; i < size; ++i) {
895 mos_.eigenvectors().col(i) = MO_copy.eigenvectors().col(sort_index[i]);
896 }
897}
898
899std::vector<Index> Orbitals::SortEnergiesAlpha() {
900 // Same logic as SortEnergies, but deliberately WITHOUT its
901 // hasUnrestrictedOrbitals() guard -- this is meant to be called on an
902 // unrestricted system, sorting the alpha channel only.
903 std::vector<Index> index = std::vector<Index>(mos_.eigenvalues().size());
904 std::iota(index.begin(), index.end(), 0);
905 std::stable_sort(index.begin(), index.end(), [this](Index i1, Index i2) {
906 return this->MOs().eigenvalues()[i1] < this->MOs().eigenvalues()[i2];
907 });
908 return index;
909}
910
911std::vector<Index> Orbitals::SortEnergiesBeta() {
912 std::vector<Index> index = std::vector<Index>(mos_beta_.eigenvalues().size());
913 std::iota(index.begin(), index.end(), 0);
914 std::stable_sort(index.begin(), index.end(), [this](Index i1, Index i2) {
915 return this->MOs_beta().eigenvalues()[i1] <
916 this->MOs_beta().eigenvalues()[i2];
917 });
918 return index;
919}
920
922 std::vector<Index> sort_index = SortEnergiesAlpha();
923 tools::EigenSystem MO_copy = mos_;
924 Index size = mos_.eigenvalues().size();
925 for (Index i = 0; i < size; ++i) {
926 mos_.eigenvalues()(i) = MO_copy.eigenvalues()(sort_index[i]);
927 }
928 for (Index i = 0; i < size; ++i) {
929 mos_.eigenvectors().col(i) = MO_copy.eigenvectors().col(sort_index[i]);
930 }
931}
932
934 std::vector<Index> sort_index = SortEnergiesBeta();
936 Index size = mos_beta_.eigenvalues().size();
937 for (Index i = 0; i < size; ++i) {
938 mos_beta_.eigenvalues()(i) = MO_copy.eigenvalues()(sort_index[i]);
939 }
940 for (Index i = 0; i < size; ++i) {
941 mos_beta_.eigenvectors().col(i) = MO_copy.eigenvectors().col(sort_index[i]);
942 }
943}
944
953 const Orbitals& orbitalsB) {
954
955 // This routine constructs a block-diagonal guess only from the restricted
956 // MO coefficient matrices mos_. It therefore only makes sense for
957 // restricted closed-shell inputs.
958 if (orbitalsA.hasUnrestrictedOrbitals() ||
959 orbitalsB.hasUnrestrictedOrbitals()) {
960 throw std::runtime_error(
961 "PrepareDimerGuess currently supports only restricted orbitals.");
962 }
963
964 if (orbitalsA.getSpin() != 1 || orbitalsB.getSpin() != 1) {
965 throw std::runtime_error(
966 "PrepareDimerGuess currently supports only closed-shell singlets.");
967 }
968
969 // constructing the direct product orbA x orbB
970 Index basisA = orbitalsA.getBasisSetSize();
971 Index basisB = orbitalsB.getBasisSetSize();
972
973 // In the restricted closed-shell case, getLumo() is the number of occupied
974 // spatial orbitals, which is exactly what this block-merging logic assumes.
975 Index occA = orbitalsA.getLumo();
976 Index occB = orbitalsB.getLumo();
977
978 mos_.eigenvectors() = Eigen::MatrixXd::Zero(basisA + basisB, basisA + basisB);
979
980 // AxB = | A 0 |
981 // | 0 B |
982 if (orbitalsA.getDFTbasisName() != orbitalsB.getDFTbasisName()) {
983 throw std::runtime_error("Basissets of Orbitals A and B differ " +
984 orbitalsA.getDFTbasisName() + ":" +
985 orbitalsB.getDFTbasisName());
986 }
987 this->SetupDftBasis(orbitalsA.getDFTbasisName());
988
989 if (orbitalsA.getECPName() != orbitalsB.getECPName()) {
990 throw std::runtime_error("ECPs of Orbitals A and B differ " +
991 orbitalsA.getECPName() + ":" +
992 orbitalsB.getECPName());
993 }
994 this->setECPName(orbitalsA.getECPName());
995
996 // Keep occupation metadata fully consistent for a restricted singlet.
997 this->setNumberOfOccupiedLevels(occA + occB);
998 this->setNumberOfOccupiedLevelsBeta(occA + occB);
999 this->setNumberOfAlphaElectrons(occA + occB);
1000 this->setNumberOfBetaElectrons(occA + occB);
1001 this->setChargeAndSpin(orbitalsA.getCharge() + orbitalsB.getCharge(), 1);
1002
1003 this->MOs().eigenvectors().block(0, 0, basisA, basisA) =
1004 orbitalsA.MOs().eigenvectors();
1005 this->MOs().eigenvectors().block(basisA, basisA, basisB, basisB) =
1006 orbitalsB.MOs().eigenvectors();
1007
1008 this->MOs().eigenvalues() = Eigen::VectorXd::Zero(basisA + basisB);
1009 this->MOs().eigenvalues().segment(0, basisA) = orbitalsA.MOs().eigenvalues();
1010 this->MOs().eigenvalues().segment(basisA, basisB) =
1011 orbitalsB.MOs().eigenvalues();
1012
1013 this->OrderMOsbyEnergy();
1014}
1015
1033 const Orbitals& orbitalsB) {
1034 Index basisA = orbitalsA.getBasisSetSize();
1035 Index basisB = orbitalsB.getBasisSetSize();
1036
1037 if (orbitalsA.getDFTbasisName() != orbitalsB.getDFTbasisName()) {
1038 throw std::runtime_error("Basissets of Orbitals A and B differ " +
1039 orbitalsA.getDFTbasisName() + ":" +
1040 orbitalsB.getDFTbasisName());
1041 }
1042 this->SetupDftBasis(orbitalsA.getDFTbasisName());
1043
1044 if (orbitalsA.getECPName() != orbitalsB.getECPName()) {
1045 throw std::runtime_error("ECPs of Orbitals A and B differ " +
1046 orbitalsA.getECPName() + ":" +
1047 orbitalsB.getECPName());
1048 }
1049 this->setECPName(orbitalsA.getECPName());
1050
1051 // A restricted monomer's own alpha and beta MOs are identical by
1052 // construction -- MOs_beta() only holds meaningful, independent data
1053 // for a monomer that is itself already unrestricted.
1054 const Eigen::MatrixXd& A_alpha = orbitalsA.MOs().eigenvectors();
1055 const Eigen::MatrixXd& A_beta = orbitalsA.hasUnrestrictedOrbitals()
1056 ? orbitalsA.MOs_beta().eigenvectors()
1057 : orbitalsA.MOs().eigenvectors();
1058 const Eigen::MatrixXd& B_alpha = orbitalsB.MOs().eigenvectors();
1059 const Eigen::MatrixXd& B_beta = orbitalsB.hasUnrestrictedOrbitals()
1060 ? orbitalsB.MOs_beta().eigenvectors()
1061 : orbitalsB.MOs().eigenvectors();
1062
1063 const Eigen::VectorXd& A_alpha_energies = orbitalsA.MOs().eigenvalues();
1064 const Eigen::VectorXd& A_beta_energies =
1065 orbitalsA.hasUnrestrictedOrbitals() ? orbitalsA.MOs_beta().eigenvalues()
1066 : orbitalsA.MOs().eigenvalues();
1067 const Eigen::VectorXd& B_alpha_energies = orbitalsB.MOs().eigenvalues();
1068 const Eigen::VectorXd& B_beta_energies =
1069 orbitalsB.hasUnrestrictedOrbitals() ? orbitalsB.MOs_beta().eigenvalues()
1070 : orbitalsB.MOs().eigenvalues();
1071
1072 mos_.eigenvectors() = Eigen::MatrixXd::Zero(basisA + basisB, basisA + basisB);
1073 mos_.eigenvectors().block(0, 0, basisA, basisA) = A_alpha;
1074 mos_.eigenvectors().block(basisA, basisA, basisB, basisB) = B_alpha;
1075 mos_.eigenvalues() = Eigen::VectorXd::Zero(basisA + basisB);
1076 mos_.eigenvalues().segment(0, basisA) = A_alpha_energies;
1077 mos_.eigenvalues().segment(basisA, basisB) = B_alpha_energies;
1078
1079 mos_beta_.eigenvectors() =
1080 Eigen::MatrixXd::Zero(basisA + basisB, basisA + basisB);
1081 mos_beta_.eigenvectors().block(0, 0, basisA, basisA) = A_beta;
1082 mos_beta_.eigenvectors().block(basisA, basisA, basisB, basisB) = B_beta;
1083 mos_beta_.eigenvalues() = Eigen::VectorXd::Zero(basisA + basisB);
1084 mos_beta_.eigenvalues().segment(0, basisA) = A_beta_energies;
1085 mos_beta_.eigenvalues().segment(basisA, basisB) = B_beta_energies;
1086
1087 Index alpha_electrons = orbitalsA.getLumoAlpha() + orbitalsB.getLumoAlpha();
1088 Index beta_electrons = orbitalsA.getLumoBeta() + orbitalsB.getLumoBeta();
1089 this->setNumberOfOccupiedLevels(alpha_electrons);
1090 this->setNumberOfOccupiedLevelsBeta(beta_electrons);
1091 this->setNumberOfAlphaElectrons(alpha_electrons);
1092 this->setNumberOfBetaElectrons(beta_electrons);
1093 // spin is the MULTIPLICITY (2S+1) convention throughout this codebase
1094 // (spin=1 is singlet, spin=2 is doublet, etc.) -- 2S itself is exactly
1095 // the alpha-beta electron-count difference, matching how DFTEngine's
1096 // own Initialize() derives num_alpha_electrons_/num_beta_electrons_
1097 // from a user-specified charge+spin pair in the first place.
1098 Index spin = (alpha_electrons - beta_electrons) + 1;
1099 this->setChargeAndSpin(orbitalsA.getCharge() + orbitalsB.getCharge(), spin);
1100
1101 this->OrderMOsbyEnergyAlpha();
1102 this->OrderMOsbyEnergyBeta();
1103}
1104
1105void Orbitals::WriteToCpt(const std::string& filename) const {
1106 std::lock_guard<std::recursive_mutex> lock(
1109 WriteToCpt(cpf);
1110}
1111
1113 CheckpointWriter writer = f.getWriter("/QMdata");
1114 WriteToCpt(writer);
1115 WriteBasisSetsToCpt(writer);
1116}
1117
1119 CheckpointWriter dftWriter = w.openChild("dft");
1120 dftbasis_.WriteToCpt(dftWriter);
1121 CheckpointWriter auxWriter = w.openChild("aux");
1122 auxbasis_.WriteToCpt(auxWriter);
1123}
1124
1126 w(votca::tools::ToolsVersionStr(), "XTPVersion");
1127 w(orbitals_version(), "version");
1128 w(occupied_levels_, "occupied_levels");
1129 w(occupied_levels_beta_, "occupied_levels_beta");
1130 w(number_alpha_electrons_, "number_alpha_electrons");
1131 w(number_beta_electrons_, "number_beta_electrons");
1132 w(total_charge_, "charge");
1133 w(total_spin_, "spin");
1134
1135 w(mos_, "mos");
1136 w(mos_beta_, "mos_beta");
1137 w(occupations_, "occupations");
1138 w(active_electrons_, "active_electrons");
1139 w(mos_embedding_, "mos_embedding");
1140 w(lmos_, "LMOs");
1141 w(lmos_energies_, "LMOs_energies");
1142 w(inactivedensity_, "inactivedensity");
1143 w(expandedMOs_, "TruncMOsFullBasis");
1144
1145 CheckpointWriter molgroup = w.openChild("qmmolecule");
1146 atoms_.WriteToCpt(molgroup);
1147
1148 w(qm_energy_, "qm_energy");
1149 w(forces_, "forces");
1150 w(qm_package_, "qm_package");
1151
1152 w(rpamin_, "rpamin");
1153 w(rpamax_, "rpamax");
1154 w(qpmin_, "qpmin");
1155 w(qpmax_, "qpmax");
1156 w(bse_vmin_, "bse_vmin");
1157 w(bse_cmax_, "bse_cmax");
1158 w(functionalname_, "XCFunctional");
1159 w(grid_quality_, "XC_grid_quality");
1160 w(ScaHFX_, "ScaHFX");
1161
1162 w(useTDA_, "useTDA");
1163 w(ECP_, "ECP");
1164
1165 w(rpa_inputenergies_, "RPA_inputenergies");
1166 w(QPpert_energies_, "QPpert_energies");
1167
1168 w(QPdiag_, "QPdiag");
1169
1170 w(BSE_singlet_, "BSE_singlet");
1171
1172 w(transition_dipoles_, "transition_dipoles");
1173
1174 w(BSE_triplet_, "BSE_triplet");
1175
1176 std::uint8_t tmp = use_Hqp_offdiag_ ? 1u : 0u;
1177 w(tmp, "use_Hqp_offdiag");
1178 std::uint8_t tmp_qsgw = is_qsgw_ ? 1u : 0u;
1179 w(tmp_qsgw, "is_qsgw");
1180
1181 w(BSE_singlet_energies_dynamic_, "BSE_singlet_dynamic");
1182
1183 w(BSE_triplet_energies_dynamic_, "BSE_triplet_dynamic");
1184
1185 w(CalcType_, "CalcType");
1186
1187 // spin GW
1188 w(rpa_inputenergies_alpha_, "RPA_inputenergies_alpha");
1189 w(rpa_inputenergies_beta_, "RPA_inputenergies_beta");
1190
1191 w(QPpert_energies_alpha_, "QPpert_energies_alpha");
1192 w(QPpert_energies_beta_, "QPpert_energies_beta");
1193
1194 w(QPdiag_alpha_, "QPdiag_alpha");
1195 w(QPdiag_beta_, "QPdiag_beta");
1196
1197 w(BSE_uks_, "BSE_uks");
1198 w(BSE_uks_energies_dynamic_, "BSE_uks_dynamic");
1199}
1200
1201void Orbitals::ReadFromCpt(const std::string& filename) {
1202 std::lock_guard<std::recursive_mutex> lock(
1205 ReadFromCpt(cpf);
1206}
1207
1209 CheckpointReader reader = f.getReader("/QMdata");
1210 ReadFromCpt(reader);
1211 ReadBasisSetsFromCpt(reader);
1212}
1213
1215 CheckpointReader dftReader = r.openChild("dft");
1216 dftbasis_.ReadFromCpt(dftReader);
1217 CheckpointReader auxReader = r.openChild("aux");
1218 auxbasis_.ReadFromCpt(auxReader);
1219}
1220
1222 r(occupied_levels_, "occupied_levels");
1223 r(number_alpha_electrons_, "number_alpha_electrons");
1224
1225 int version;
1226 r(version, "version");
1227
1228 auto r_optional = [&r](auto& obj, const std::string& key) {
1229 try {
1230 r(obj, key);
1231 } catch (std::runtime_error&) {
1232 // Older checkpoints may not contain this field.
1233 }
1234 };
1235
1236 // Read qmatoms
1237 CheckpointReader molgroup = r.openChild("qmmolecule");
1238 atoms_.ReadFromCpt(molgroup);
1239
1240 r(qm_energy_, "qm_energy");
1241 try {
1242 r(forces_, "forces");
1243 } catch (std::runtime_error& e) {
1244 ; // older checkpoint files won't have this field -- leave forces_
1245 // empty (hasForces() == false), same convention as LMOs below.
1246 }
1247 r(qm_package_, "qm_package");
1248 try {
1249 r(lmos_, "LMOs");
1250 r(lmos_energies_, "LMOs_energies");
1251 } catch (std::runtime_error& e) {
1252 ;
1253 }
1254
1255 r(version, "version");
1256 r(mos_, "mos");
1257 r(mos_embedding_, "mos_embedding");
1258 r(active_electrons_, "active_electrons");
1259 r(inactivedensity_, "inactivedensity");
1260 r(CalcType_, "CalcType");
1261 r(expandedMOs_, "TruncMOsFullBasis");
1262
1263 // spin info available from version 6 or higher
1264 if (version > 5) {
1265 r(occupied_levels_beta_, "occupied_levels_beta");
1266 r(number_beta_electrons_, "number_beta_electrons");
1267 r(total_charge_, "charge");
1268 r(total_spin_, "spin");
1269 r(mos_beta_, "mos_beta");
1270 r(occupations_, "occupations");
1271 }
1272
1273 if (version < 3) {
1274 // clang-format off
1275 std::array<Index, 49> votcaOrder_old = {
1276 0, // s
1277 0, -1, 1, // p
1278 0, -1, 1, -2, 2, // d
1279 0, -1, 1, -2, 2, -3, 3, // f
1280 0, -1, 1, -2, 2, -3, 3, -4, 4, // g
1281 0, -1, 1, -2, 2, -3, 3, -4, 4,-5,5, // h
1282 0, -1, 1, -2, 2, -3, 3, -4, 4,-5,5,-6,6 // i
1283 };
1284 // clang-format on
1285
1286 std::array<Index, 49> multiplier;
1287 multiplier.fill(1);
1288 OrbReorder ord(votcaOrder_old, multiplier);
1289 ord.reorderOrbitals(mos_.eigenvectors(), this->getDftBasis());
1290 }
1291
1292 if (version < 5) { // we need to construct the basissets, NB. can only be
1293 // done after reading the atoms.
1294 std::string dft_basis_name;
1295 std::string aux_basis_name;
1296 r(dft_basis_name, "dftbasis");
1297 r(aux_basis_name, "auxbasis");
1298 this->SetupDftBasis(dft_basis_name);
1299 this->SetupAuxBasis(aux_basis_name);
1300 }
1301
1302 r(rpamin_, "rpamin");
1303 r(rpamax_, "rpamax");
1304 r(qpmin_, "qpmin");
1305 r(qpmax_, "qpmax");
1306 r(bse_vmin_, "bse_vmin");
1307 r(bse_cmax_, "bse_cmax");
1309 try {
1310 r(functionalname_, "XCFunctional");
1311 r(grid_quality_, "XC_grid_quality");
1312 } catch (std::runtime_error& e) {
1313 grid_quality_ = "medium";
1314 }
1315 r(ScaHFX_, "ScaHFX");
1316 r(useTDA_, "useTDA");
1317 r(ECP_, "ECP");
1318
1319 r(rpa_inputenergies_, "RPA_inputenergies");
1320
1321 r(QPpert_energies_, "QPpert_energies");
1322 r(QPdiag_, "QPdiag");
1323
1324 // Optional unrestricted GW data
1325 r_optional(rpa_inputenergies_alpha_, "RPA_inputenergies_alpha");
1326 r_optional(rpa_inputenergies_beta_, "RPA_inputenergies_beta");
1327
1328 r_optional(QPpert_energies_alpha_, "QPpert_energies_alpha");
1329 r_optional(QPpert_energies_beta_, "QPpert_energies_beta");
1330
1331 r_optional(QPdiag_alpha_, "QPdiag_alpha");
1332 r_optional(QPdiag_beta_, "QPdiag_beta");
1333 r_optional(BSE_uks_, "BSE_uks");
1334 r_optional(BSE_uks_energies_dynamic_, "BSE_uks_dynamic");
1335 r(BSE_singlet_, "BSE_singlet");
1336
1337 r(transition_dipoles_, "transition_dipoles");
1338
1339 r(BSE_triplet_, "BSE_triplet");
1340
1341 std::uint8_t tmp = 0;
1342 r(tmp, "use_Hqp_offdiag");
1343 use_Hqp_offdiag_ = (tmp != 0);
1344 std::uint8_t tmp_qsgw = 0;
1345 if (version > 8) {
1346 r(tmp_qsgw, "is_qsgw");
1347 }
1348 is_qsgw_ = (tmp_qsgw != 0);
1349
1350 if (version > 1) {
1351 r(BSE_singlet_energies_dynamic_, "BSE_singlet_dynamic");
1352
1353 r(BSE_triplet_energies_dynamic_, "BSE_triplet_dynamic");
1354 }
1355 // Backward compatibility for older restricted checkpoints that may not
1356 // contain explicit beta-electron information.
1358 if (number_alpha_electrons_ == 0) {
1360 }
1361 if (number_beta_electrons_ == 0) {
1363 }
1364 }
1365}
1366
1367/*********************************************
1368 * Extensions Spin-DFT
1369 **********************************************/
1370
1371// Spin-resolved ground-state density matrices.
1372//
1373// The returned pair is (P^alpha, P^beta) with
1374//
1375// P^sigma = C_occ^sigma (C_occ^sigma)^T
1376//
1377// for unrestricted calculations. In the restricted branch the same spatial MO
1378// coefficients are reused and distributed over alpha/beta occupations according
1379// to the stored electron counts.
1381 const {
1382 if (!hasMOs()) {
1383 throw std::runtime_error("Orbitals file does not contain MO coefficients");
1384 }
1385
1386 std::array<Eigen::MatrixXd, 2> result;
1387 result[0] = Eigen::MatrixXd::Zero(mos_.eigenvectors().rows(),
1388 mos_.eigenvectors().rows());
1389 result[1] = Eigen::MatrixXd::Zero(mos_.eigenvectors().rows(),
1390 mos_.eigenvectors().rows());
1391
1393 if (occupied_levels_ > 0) {
1394 const Eigen::MatrixXd occ_a =
1395 mos_.eigenvectors().leftCols(occupied_levels_);
1396 result[0] = occ_a * occ_a.transpose();
1397 }
1398 if (occupied_levels_beta_ > 0) {
1399 const Eigen::MatrixXd occ_b =
1400 mos_beta_.eigenvectors().leftCols(occupied_levels_beta_);
1401 result[1] = occ_b * occ_b.transpose();
1402 }
1403 return result;
1404 }
1405
1406 // Restricted branch:
1407 // Prefer explicit alpha/beta electron counts, but fall back to the legacy
1408 // occupied_levels_ convention when those counts were never set.
1411
1412 if (n_alpha == 0 && n_beta == 0 && occupied_levels_ > 0) {
1413 n_alpha = occupied_levels_;
1414 n_beta = occupied_levels_;
1415 }
1416
1417 const Index n_docc = std::min(n_alpha, n_beta);
1418 const Index n_socc_alpha = n_alpha - n_docc;
1419 const Index n_socc_beta = n_beta - n_docc;
1420
1421 if (n_docc > 0) {
1422 const Eigen::MatrixXd docc = mos_.eigenvectors().leftCols(n_docc);
1423 const Eigen::MatrixXd d_docc = docc * docc.transpose();
1424 result[0] += d_docc;
1425 result[1] += d_docc;
1426 }
1427
1428 if (n_socc_alpha > 0) {
1429 const Eigen::MatrixXd socc_a =
1430 mos_.eigenvectors().middleCols(n_docc, n_socc_alpha);
1431 result[0] += socc_a * socc_a.transpose();
1432 }
1433
1434 if (n_socc_beta > 0) {
1435 const Eigen::MatrixXd socc_b =
1436 mos_.eigenvectors().middleCols(n_docc + n_socc_alpha, n_socc_beta);
1437 result[1] += socc_b * socc_b.transpose();
1438 }
1439
1440 return result;
1441}
1442
1443} // namespace xtp
1444} // 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:882
Index number_beta_electrons_
Definition orbitals.h:801
void CalcCoupledTransition_Dipoles()
Definition orbitals.cc:760
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:809
Eigen::VectorXd rpa_inputenergies_
Definition orbitals.h:855
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:886
Eigen::MatrixXd TransitionDensityMatrix(const QMState &state) const
Definition orbitals.cc:316
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:250
std::array< Eigen::MatrixXd, 2 > DensityMatrixExcitedState_R(const QMState &state) const
Definition orbitals.cc:535
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:700
Eigen::VectorXd lmos_energies_
Definition orbitals.h:814
tools::EigenSystem BSE_singlet_
Definition orbitals.h:864
tools::EigenSystem QPdiag_beta_
Definition orbitals.h:883
void OrderMOsbyEnergyBeta()
Same as OrderMOsbyEnergyAlpha, but for the BETA channel (mos_beta_).
Definition orbitals.cc:933
void PrepareDimerGuess(const Orbitals &orbitalsA, const Orbitals &orbitalsB)
Guess for a dimer based on monomer orbitals.
Definition orbitals.cc:952
tools::EigenSystem mos_beta_
Definition orbitals.h:808
std::string grid_quality_
Definition orbitals.h:852
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:676
Eigen::VectorXd BSE_triplet_energies_dynamic_
Definition orbitals.h:870
void ReadBasisSetsFromCpt(CheckpointReader &r)
Deserialize attached AO basis sets from an already-open checkpoint reader.
Definition orbitals.cc:1214
std::array< Eigen::MatrixXd, 2 > DensityMatrixGroundStateSpinResolved() const
Definition orbitals.cc:1380
Eigen::MatrixXd QPpert_energies_beta_
Definition orbitals.h:880
void WriteBasisSetsToCpt(CheckpointWriter &w) const
Definition orbitals.cc:1118
std::vector< Index > CheckDegeneracy(Index level, double energy_difference) const
Definition orbitals.cc:56
Eigen::MatrixXd inactivedensity_
Definition orbitals.h:816
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:742
Eigen::MatrixXd forces_
Definition orbitals.h:830
Eigen::MatrixXd CalcAuxMat_cc(const Eigen::VectorXd &coeffs) const
Definition orbitals.cc:584
Eigen::MatrixXd expandedMOs_
Definition orbitals.h:817
void setNumberOfAlphaElectrons(Index electrons)
Store the total number of alpha electrons.
Definition orbitals.h:139
Eigen::VectorXd Oscillatorstrengths() const
Definition orbitals.cc:643
std::vector< Eigen::Vector3d > transition_dipoles_
Definition orbitals.h:865
static constexpr int orbitals_version()
Definition orbitals.h:894
std::string functionalname_
Definition orbitals.h:851
Eigen::MatrixXd lmos_
Definition orbitals.h:813
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:578
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:1032
void SetupAuxBasis(std::string aux_basis_name)
Definition orbitals.cc:108
std::string qm_package_
Definition orbitals.h:853
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:866
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:690
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:590
double getExcitedStateEnergy(const QMState &state) const
Return the excitation energy of the requested state in Hartree.
Definition orbitals.cc:685
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:799
Orbitals()
Construct an empty orbital container with default metadata.
Definition orbitals.cc:46
tools::EigenSystem mos_
Definition orbitals.h:807
void OrderMOsbyEnergyAlpha()
Definition orbitals.cc:921
tools::EigenSystem QPdiag_
Definition orbitals.h:860
Eigen::VectorXd rpa_inputenergies_beta_
Definition orbitals.h:877
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:265
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:911
Eigen::MatrixXd QPpert_energies_alpha_
Definition orbitals.h:879
Eigen::VectorXd BSE_singlet_energies_dynamic_
Definition orbitals.h:869
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:805
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:811
Eigen::VectorXd rpa_inputenergies_alpha_
Definition orbitals.h:876
std::vector< Index > SortEnergiesAlpha()
Definition orbitals.cc:899
void ReadFromCpt(const std::string &filename)
Read the orbital container from a checkpoint file on disk.
Definition orbitals.cc:1201
tools::EigenSystem BSE_uks_
Definition orbitals.h:885
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:857
void WriteToCpt(const std::string &filename) const
Write the orbital container to a checkpoint file on disk.
Definition orbitals.cc:1105
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:413
std::string ECP_
Definition orbitals.h:802
Index number_alpha_electrons_
Definition orbitals.h:800
Eigen::Vector3d CalcElDipole(const QMState &state) const
Compute the electronic dipole moment associated with a state density.
Definition orbitals.cc:288
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:819
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