votca 2026-dev
Loading...
Searching...
No Matches
qmregion.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// Local VOTCA includes
21#include "votca/xtp/qmregion.h"
23#include "votca/xtp/aomatrix.h"
24#include "votca/xtp/basisset.h"
25#include "votca/xtp/citations.h"
28#include "votca/xtp/dftengine.h"
33#include "votca/xtp/gwbse.h"
36#include "votca/xtp/qmstate.h"
38#include "votca/xtp/vxc_grid.h"
39
40#include <algorithm>
41#include <iomanip>
42#include <sstream>
43#include <stdexcept>
44
45namespace votca {
46namespace xtp {
47
49 if (this->id_ != 0) {
50 throw std::runtime_error(
51 this->identify() +
52 " must always be region 0. Currently only one qm region is possible.");
53 }
54
55 initstate_ = prop.get("state").as<QMState>();
56 /*if (initstate_.Type() == QMStateType::Hole ||
57 initstate_.Type() == QMStateType::Electron) {
58 throw std::runtime_error(
59 "Charged QM Regions are not implemented currently");
60 }*/
61 if (initstate_.Type().isExciton() || initstate_.Type().isGWState()) {
62
63 do_gwbse_ = true;
64 gwbseoptions_ = prop.get("gwbse");
65 if (prop.exists("statetracker")) {
66 tools::Property filter = prop.get("statetracker");
67 statetracker_.setLogger(&log_);
68 statetracker_.Initialize(filter);
69 statetracker_.setInitialState(initstate_);
70 statetracker_.PrintInfo();
71 } else {
72 throw std::runtime_error(
73 "No statetracker options for excited states found");
74 }
75 }
76
77 if (initstate_.Type().isKSState()) {
78
79 if (prop.exists("statetracker")) {
80 tools::Property filter = prop.get("statetracker");
81 statetracker_.setLogger(&log_);
82 statetracker_.Initialize(filter);
83 statetracker_.setInitialState(initstate_);
84 statetracker_.PrintInfo();
85 } else {
86 throw std::runtime_error("No statetracker options for KS states found");
87 }
88 }
89
91 prop.get("grid_for_potential").as<std::string>();
92 DeltaE_ = prop.get("tolerance_energy").as<double>();
93 DeltaD_ = prop.get("tolerance_density").as<double>();
94 DeltaDmax_ = prop.get("tolerance_density_max").as<double>();
95
96 dftoptions_ = prop.get("dftpackage");
97 localize_options_ = prop.get("localize");
98
99 if (prop.get("dftpackage.xtpdft.dft_in_dft.activeatoms")
100 .as<std::string>()
101 .size() != 0) {
102 do_localize_ = true;
103 do_dft_in_dft_ = true;
104 }
105
106 if (prop.exists("environment_screening")) {
107 const tools::Property env = prop.get("environment_screening");
108 screening_ = env.ifExistsReturnElseReturnDefault<bool>("enabled", true);
109 }
110 if (screening_) {
111 const tools::Property env = prop.get("environment_screening");
112 // Excitations are what the screening is for. A charged DFT run would
113 // put the extra charge's polarization into the ground-state loop and
114 // again into W; a plain ground-state or KS-level run has no GW-BSE to
115 // screen.
116 if (initstate_.Type() == QMStateType::Electron ||
117 initstate_.Type() == QMStateType::Hole) {
118 throw std::runtime_error(
119 "environment_screening with state " + initstate_.ToString() +
120 ": the charged DFT state would be polarized in the QM/MM loop and "
121 "screened again in GW. Run the neutral molecule and ask for the "
122 "quasiparticle level instead: state pqpN (or dqpN) with N the "
123 "HOMO or LUMO index.");
124 }
125 if (initstate_.Type() == QMStateType::ExcitonUKS) {
126 throw std::runtime_error(
127 "environment_screening is implemented for closed-shell GW-BSE "
128 "only; the unrestricted exciton state " +
129 initstate_.ToString() + " is not supported.");
130 }
131 if (!do_gwbse_) {
132 throw std::runtime_error(
133 "environment_screening needs a GW-BSE state (an exciton or a "
134 "quasiparticle level); " +
135 initstate_.ToString() + " has no GW-BSE calculation to screen.");
136 }
138 env.ifExistsReturnElseReturnDefault<bool>("include_kreac", true);
140 env.ifExistsReturnElseReturnDefault<double>("shell_dielectric", 4.0);
142 env.ifExistsReturnElseReturnDefault<double>("site_width", 0.5);
143 if (!(screening_site_width_ >= 0.0)) {
144 throw std::runtime_error(
145 "environment_screening: site_width must be >= 0 (0 for point "
146 "sites), got " +
147 std::to_string(screening_site_width_) + ".");
148 }
149 std::string shells =
150 env.ifExistsReturnElseReturnDefault<std::string>("shell_regions", "");
151 std::replace(shells.begin(), shells.end(), ',', ' ');
152 std::istringstream in(shells);
153 Index id;
154 while (in >> id) {
155 screening_shell_regions_.push_back(id);
156 }
158 << " Environment screening on: ground-state QM/MM loop, then one "
159 "screened GW-BSE run. K_reac "
160 << (screening_include_kreac_ ? "included" : "excluded")
161 << ", shell dielectric " << screening_shell_dielectric_
162 << ", site width " << screening_site_width_ << " alpha^(1/3)"
163 << std::flush;
164 }
165}
166
168 citations::Cite("Baumeier:2014");
169 citations::Cite("Tirimbo:2020b");
170 citations::Cite("Tirimbo:2024a");
171 if (orb_.isOpenShell()) {
172 throw std::runtime_error("GWBSE not implemented for open-shell systems");
173 }
174 if (do_dft_in_dft_) {
175 Index active_electrons = orb_.getNumOfActiveElectrons();
176
177 if (active_electrons % 2 != 0) {
178 throw std::runtime_error(
179 "DFT-in-DFT embedded GWBSE currently supports only closed-shell "
180 "active spaces with an even number of electrons");
181 }
182
183 orb_.MOs() = orb_.getEmbeddedMOs();
184
185 const Index active_occ = active_electrons / 2;
186
187 // Restricted closed-shell rewrite of the embedded orbital object
188 orb_.setNumberOfOccupiedLevels(active_occ);
189 orb_.setNumberOfOccupiedLevelsBeta(active_occ);
190 orb_.setNumberOfAlphaElectrons(active_occ);
191 orb_.setNumberOfBetaElectrons(active_occ);
192 orb_.setChargeAndSpin(orb_.getCharge(), 1);
193 }
194}
195
196double QMRegion::StateEnergy(const QMState& state) const {
197 if (state.Type().isExciton()) {
198 return orb_.getExcitedStateEnergy(state);
199 }
200 // quasiparticle: adding an electron to an unoccupied level, removing one
201 // from an occupied level
202 if (state.StateIdx() > orb_.getHomo()) {
203 return orb_.getExcitedStateEnergy(state);
204 }
205 return -orb_.getExcitedStateEnergy(state);
206}
207
209 const std::vector<std::unique_ptr<Region> >& regions) const {
214 bool have_damp = false;
215 PolarRegion Polardummy(0, log_);
216 for (const std::unique_ptr<Region>& reg : regions) {
217 if (reg->identify() != Polardummy.identify()) {
218 continue;
219 }
220 const PolarRegion& polar = dynamic_cast<const PolarRegion&>(*reg);
221 const bool shell =
222 std::find(screening_shell_regions_.begin(),
224 polar.getId()) != screening_shell_regions_.end();
225 if (shell) {
226 if (polar.hasChargeResponse()) {
227 throw std::runtime_error(
228 "environment_screening: shell region " +
229 std::to_string(polar.getId()) +
230 " has charge-response segments; a shell responds with "
231 "alpha/epsilon per site, which has no induced charges. Use "
232 "Thole segments there.");
233 }
234 env.shell_segments.insert(env.shell_segments.end(), polar.begin(),
235 polar.end());
236 continue;
237 }
238 if (have_damp && polar.ExpDamp() != env.exp_damp) {
239 throw std::runtime_error(
240 "environment_screening: the explicit polar regions respond as one "
241 "coupled Thole system and must share exp_damp; found " +
242 std::to_string(env.exp_damp) + " and " +
243 std::to_string(polar.ExpDamp()) + ".");
244 }
245 env.exp_damp = polar.ExpDamp();
246 have_damp = true;
247 env.explicit_segments.insert(env.explicit_segments.end(), polar.begin(),
248 polar.end());
249 }
251 const bool is_polar = std::any_of(
252 regions.begin(), regions.end(),
253 [&](const std::unique_ptr<Region>& reg) {
254 return reg->getId() == id && reg->identify() == Polardummy.identify();
255 });
256 if (!is_polar) {
257 throw std::runtime_error("environment_screening: shell region " +
258 std::to_string(id) +
259 " is not a polar region of this job.");
260 }
261 }
262 if (env.empty()) {
264 << TimeStamp()
265 << " WARNING: environment_screening is on but there is no polar "
266 "region; GW-BSE runs unscreened."
267 << std::flush;
268 }
269 return env;
270}
271
273 // The auxiliary basis GWBSE::Initialize will pick: the DFT run's own
274 // (dftpackage.auxbasisset), else gwbse.auxbasisset, else aux-<basis>.
275 if (dftoptions_.exists("auxbasisset") &&
276 !dftoptions_.get("auxbasisset").as<std::string>().empty()) {
277 return dftoptions_.get("auxbasisset").as<std::string>();
278 }
279 if (gwbseoptions_.exists("auxbasisset") &&
280 !gwbseoptions_.get("auxbasisset").as<std::string>().empty()) {
281 return gwbseoptions_.get("auxbasisset").as<std::string>();
282 }
283 return "aux-" + dftoptions_.get("basisset").as<std::string>();
284}
285
287 const std::vector<std::unique_ptr<Region> >& regions) const {
288 if (!screening_) {
289 return;
290 }
291 // R depends on the QM atoms, the auxiliary basis on them and the polar
292 // sites -- none of which the QM/MM loop changes -- so it is checked
293 // here, before the loop, rather than after the ground state has
294 // converged. Failing now costs seconds; failing in EvaluateScreenedGWBSE
295 // costs the whole loop.
297 if (env.empty()) {
298 return;
299 }
300 const std::string auxname = ScreeningAuxBasisName();
301 BasisSet bs;
302 bs.Load(auxname);
303 AOBasis auxbasis;
304 auxbasis.Fill(bs, orb_.QMAtoms());
306 << TimeStamp() << " Checking the screening environment (" << auxname
307 << ", " << auxbasis.AOBasisSize() << " functions)" << std::flush;
309 auxbasis, orb_.QMAtoms(), env, EnvironmentScreening::Metric(auxbasis));
310 if (!check.ok()) {
311 throw std::runtime_error(
312 "environment_screening: 1 + R is not positive definite (lowest "
313 "eigenvalue of R " +
314 std::to_string(check.lowest) +
315 "): the environment would screen some charge fluctuation of the QM "
316 "region by more than the fluctuation itself. Diagnosis:\n" +
317 check.report);
318 }
320 << TimeStamp() << " Screening environment is stable: lowest eigenvalue "
321 << "of R " << check.lowest << " (must be > -1)" << std::flush;
322 XTP_LOG(Log::info, log_) << check.report << std::flush;
323}
324
326 std::vector<std::unique_ptr<Region> >& regions) {
327 if (!screening_) {
328 return;
329 }
330 if (orb_.getCalculationType() == "Truncated") {
331 throw std::runtime_error(
332 "environment_screening is not implemented for truncated active "
333 "regions.");
334 }
335
337
339 << TimeStamp() << " Running environment-screened GW-BSE" << std::flush;
340 const double e_dft = orb_.getDFTTotalEnergy();
342 GWBSE gwbse(orb_);
343 gwbse.setLogger(&log_);
345 gwbse.setScreeningEnvironment(env);
346 gwbse.Evaluate();
347 const QMState state = statetracker_.CalcStateAndUpdate(orb_);
348 const double energy = e_dft + StateEnergy(state);
350 << TimeStamp() << " Screened state " << state.ToString() << ": "
351 << std::setprecision(10) << StateEnergy(state) * tools::conv::hrt2ev
352 << " eV, region energy " << energy << " Hartree" << std::flush;
353 E_hist_.push_back(energy);
354 screened_state_ = state;
355 screened_done_ = true;
356}
357
359 if (!screened_done_) {
360 return;
361 }
362 // Site energies as -j read stores them: the energy of the state relative
363 // to the neutral ground state of the same environment, in eV. One
364 // screened job provides all of them, from one GW(-BSE) run on top of
365 // the converged ground state:
366 // h: E_h - E_n = -eps_HOMO e: E_e - E_n = eps_LUMO
367 // s/t: Omega of the tracked exciton, if the job's state is one.
368 // Quasiparticle energies are the diagonalized ones (DQP) if available.
369 const Index homo = orb_.getHomo();
370 auto qp = [&](Index level) {
371 const std::string kind = orb_.hasQPdiag() ? "dqp" : "pqp";
372 return orb_.getExcitedStateEnergy(QMState(kind + std::to_string(level)));
373 };
374 tools::Property& out = prop.add("screened_site_energies", "");
375 out.add("h", std::to_string(-qp(homo) * tools::conv::hrt2ev));
376 out.add("e", std::to_string(qp(homo + 1) * tools::conv::hrt2ev));
378 out.add("s",
380 } else if (screened_state_.Type() == QMStateType::Triplet) {
381 out.add("t",
383 }
384 out.add("state", screened_state_.ToString());
385}
386
387// helper function to hand the grid changable over to Ewald
388std::vector<Eigen::Vector3d> QMRegion::copyEwaldGrid() {
389
390 std::vector<const Eigen::Vector3d*> src = ewaldgrid_.getGridpoints();
391 std::vector<Eigen::Vector3d> dst(src.size());
392 std::transform(src.begin(), src.end(), dst.begin(),
393 [](const Eigen::Vector3d* v) { return *v; });
394 return dst;
395}
396
398
399 // purpose: prepare a grid integration object that can
400 // - hand over the grid to Ewald
401 // - after Ewald calculates the potential at the grid points, be put into
402 // xtpdft
403 //
404 // The grid name is xtpdft's own integration_grid, NOT grid_for_potential.
405 // That is a constraint, not a preference: what comes back from Ewald is a
406 // potential VALUE at each quadrature point, and DFTEngine forms <rho|phi>
407 // on the grid it rebuilds from grid_name_, which is this same key. A
408 // different quality here gives a different number of boxes and a
409 // different number of points per box, and the size checks on the
410 // DFTEngine side would throw. grid_for_potential governs the opposite
411 // direction -- the QM density's influence outward on classical regions,
412 // in ApplyQMFieldToPolarSegments -- and has no business here.
413 if (orb_.QMAtoms().size() == 0) {
414 throw std::runtime_error(
415 "QMRegion::PrepareEwaldPotentialGrid: the region holds no atoms. The "
416 "grid is built over the QM molecule, so this must run after the "
417 "region has been filled.");
418 }
419 std::string dftbasis_name = dftoptions_.get("basisset").as<std::string>();
420 std::string grid_name =
421 dftoptions_.get("xtpdft.integration_grid").as<std::string>();
422 QMMolecule mol = orb_.QMAtoms();
423 BasisSet bs;
424 bs.Load(dftbasis_name);
425 AOBasis dftbasis;
426 dftbasis.Fill(bs, mol);
427 ewaldgrid_.GridSetup(grid_name, mol, dftbasis);
429 << TimeStamp() << " Constructed Grid for Integration of Ewald Potential"
430 << std::flush;
431 // Vxc_Potential<Vxc_Grid> vxc(grid);
432 ewald_grid_ready_ = true;
433 return;
434}
435
437 if (!E_hist_.filled()) {
438 return false;
439 }
440
441 const double Echange = E_hist_.getDiff();
442 const Eigen::MatrixXd diff_ao = Dmat_hist_.getDiff();
443 const double n = double(diff_ao.cols());
444 // The density change is measured in the Loewdin-orthonormalised basis,
445 // S^1/2 dD S^1/2. In the AO basis, a near-linearly dependent basis lets
446 // density-matrix elements move a lot along near-redundant directions while
447 // the density itself hardly changes (C60/def2-TZVP: max change ~170 times
448 // the RMS), which made the max criterion decide the convergence alone.
449 const bool orthonormal = overlap_sqrt_.rows() == diff_ao.rows();
450 const Eigen::MatrixXd diff =
451 orthonormal ? Eigen::MatrixXd(overlap_sqrt_ * diff_ao * overlap_sqrt_)
452 : diff_ao;
453 const double Dchange = diff.norm() / n;
454 const double Dmax = diff.cwiseAbs().maxCoeff();
455 std::string info = "not converged";
456 bool converged = false;
457 if (Dchange < DeltaD_ && Dmax < DeltaDmax_ && std::abs(Echange) < DeltaE_) {
458 info = "converged";
459 converged = true;
460 }
461 std::ostringstream measure;
462 if (orthonormal) {
463 measure << " (orthonormal basis; AO basis: RMS " << diff_ao.norm() / n
464 << " Max " << diff_ao.cwiseAbs().maxCoeff() << ")";
465 } else {
466 measure << " (AO basis)";
467 }
469 << " Region:" << this->identify() << " " << this->getId() << " is "
470 << info << " deltaE=" << Echange << " RMS Dmat=" << Dchange
471 << " MaxDmat=" << Dmax << measure.str() << std::flush;
472 return converged;
473}
474
475void QMRegion::Evaluate(std::vector<std::unique_ptr<Region> >& regions) {
476
477 std::vector<double> interact_energies = ApplyInfluenceOfOtherRegions(regions);
478 double e_ext =
479 std::accumulate(interact_energies.begin(), interact_energies.end(), 0.0);
481 << TimeStamp()
482 << " Calculated interaction potentials with other regions. E[hrt]= "
483 << e_ext << std::flush;
484 XTP_LOG(Log::info, log_) << "Writing inputs" << std::flush;
485
486 Index crg = 0;
487 if (initstate_.Type() == QMStateType::Electron) {
488 crg = -1;
489 } else if (initstate_.Type() == QMStateType::Hole) {
490 crg = +1;
491 }
492 qmpackage_->setCharge(crg);
493 qmpackage_->setRunDir(workdir_);
494 qmpackage_->WriteInputFile(orb_);
495
496 // Attach the periodic background to xtpdft as a potential sampled on
497 // the integration grid. This used to be one of two routes -- the other
498 // handed over multipole moments and k-vectors for the engine to build
499 // its own AO matrices from -- but nothing ever set that one up, so it
500 // has been removed. See the git history of this file if it is ever
501 // wanted back.
502 if (ewald_grid_ready_) {
503 if (qmpackage_->getPackageName() != "xtp") {
504 throw std::runtime_error("QMEwald can only run with XTP as qmpackage.");
505 }
506 qmpackage_->setEwaldgrid(ewaldgrid_);
507 qmpackage_->setEwaldNuclearEnergy(ewald_nuclear_energy_);
508 }
509
510 // From the second inter-region iteration on, only the environment has
511 // changed: start the SCF from the previous iteration's orbitals.
512 const bool warm_start = E_hist_.filled() && orb_.hasMOs();
513 qmpackage_->setWarmStart(warm_start);
514 // From the second inter-region iteration on, the inner SCF only needs to
515 // be converged a factor 10 beyond what the outer loop resolves: its
516 // energy error is second order in the density error, and its density
517 // error stays below the outer density tolerance. Not with GW-BSE, which
518 // takes the orbitals as they are; capped at 1e-5 in both.
519 if (warm_start && !do_gwbse_) {
520 qmpackage_->setSCFToleranceFloor(std::min(0.1 * DeltaE_, 1e-5),
521 std::min(0.1 * DeltaD_, 1e-5));
522 }
523 // Basis sets and geometry stay the same over the inter-region iterations:
524 // keep the RI integrals and the Ewald potential matrix for the next one.
525 // Not with GW-BSE, whose own three-centre integrals would come on top of
526 // the cached ones in memory. The cache lives here because Reset()
527 // recreates the QM package every iteration.
528 if (!do_gwbse_) {
529 if (!setup_cache_) {
530 setup_cache_ = std::make_shared<DFTSetupCache>();
531 }
532 qmpackage_->setSetupCache(setup_cache_);
533 }
534
535 XTP_LOG(Log::error, log_) << "Running DFT calculation" << std::flush;
536 bool run_success = qmpackage_->Run();
537 if (!run_success) {
538 info_ = false;
539 errormsg_ = "DFT-run failed";
540 return;
541 }
542
543 bool Logfile_parse = qmpackage_->ParseLogFile(orb_);
544 if (!Logfile_parse) {
545 info_ = false;
546 errormsg_ = "Parsing DFT logfile failed.";
547 return;
548 }
549 bool Orbfile_parse = qmpackage_->ParseMOsFile(orb_);
550 if (!Orbfile_parse) {
551 info_ = false;
552 errormsg_ = "Parsing DFT orbfile failed.";
553 return;
554 }
555 if (do_localize_) {
556 if (orb_.isOpenShell()) {
557 throw std::runtime_error(
558 "MO localization not implemented for open-shell systems");
559 }
561 pml.computePML(orb_);
562 }
563 QMMolecule originalmol = orb_.QMAtoms();
564 if (do_dft_in_dft_) {
565 if (orb_.isOpenShell()) {
566 throw std::runtime_error(
567 "DFT-in-DFT not implemented for open-shell systems");
568 }
569 // this only works with XTPDFT, so locally override global qmpackage_
570 std::unique_ptr<QMPackage> xtpdft =
571 std::unique_ptr<QMPackage>(QMPackageFactory().Create("xtp"));
572 xtpdft->setLog(&log_);
573 xtpdft->Initialize(dftoptions_);
574 xtpdft->setRunDir(workdir_);
575 Index charge = 0;
576 if (initstate_.Type() == QMStateType::Electron) {
577 charge = -1;
578 } else if (initstate_.Type() == QMStateType::Hole) {
579 charge = +1;
580 }
581 xtpdft->setCharge(charge);
582
583 std::vector<double> energies = std::vector<double>(regions.size(), 0.0);
584 for (std::unique_ptr<Region>& reg : regions) {
585 Index id = reg->getId();
586 if (id == this->getId()) {
587 continue;
588 }
589
590 StaticRegion Staticdummy(0, log_);
591 PolarRegion Polardummy(0, log_);
593 << TimeStamp() << " Evaluating interaction between "
594 << this->identify() << " " << this->getId() << " and "
595 << reg->identify() << " " << reg->getId() << std::flush;
596 if (reg->identify() == Staticdummy.identify()) {
597 StaticRegion* staticregion = dynamic_cast<StaticRegion*>(reg.get());
598 xtpdft->AddRegion(*staticregion);
599 } else if (reg->identify() == Polardummy.identify()) {
600 PolarRegion* polarregion = dynamic_cast<PolarRegion*>(reg.get());
601 xtpdft->AddRegion(*polarregion);
602 } else {
603 throw std::runtime_error(
604 "Interaction of regions with types:" + this->identify() + " and " +
605 reg->identify() + " not implemented");
606 }
607 }
608
609 e_ext = std::accumulate(interact_energies.begin(), interact_energies.end(),
610 0.0);
612 << TimeStamp()
613 << " Calculated interaction potentials with other regions. E[hrt]= "
614 << e_ext << std::flush;
615 XTP_LOG(Log::info, log_) << "Writing inputs" << std::flush;
616 xtpdft->WriteInputFile(orb_);
617 bool active_run = xtpdft->RunActiveRegion();
618 if (!active_run) {
619 throw std::runtime_error("\n DFT in DFT embedding failed. Stopping!");
620 }
621 Logfile_parse = xtpdft->ParseLogFile(orb_);
622 if (!Logfile_parse) {
623 throw std::runtime_error("\n Parsing DFT logfile failed. Stopping!");
624 }
625 }
626
627 QMState state = QMState("groundstate");
628 double energy = orb_.getDFTTotalEnergy();
629
630 if (initstate_.Type().isKSState()) {
631 state = statetracker_.CalcStateAndUpdate(orb_);
632 // if unoccupied, add QP level energy
633 if (state.StateIdx() > orb_.getHomo()) {
634 energy += orb_.getExcitedStateEnergy(state);
635 } else {
636 // if unoccupied, subtract QP level energy
637 energy -= orb_.getExcitedStateEnergy(state);
638 }
639 }
640
641 // With environment screening the loop converges the ground state only;
642 // GW-BSE runs once afterwards (EvaluateScreenedGWBSE).
643 if (do_gwbse_ && !screening_) {
645 GWBSE gwbse(orb_);
646 gwbse.setLogger(&log_);
648 gwbse.Evaluate();
649 state = statetracker_.CalcStateAndUpdate(orb_);
650 energy += StateEnergy(state);
651 }
652 // If truncation was enabled then rewrite full basis/aux-basis, MOs in full
653 // basis and full QMAtoms
654 if (orb_.getCalculationType() == "Truncated") {
655 orb_.MOs().eigenvectors() = orb_.getTruncMOsFullBasis();
656 orb_.QMAtoms().clearAtoms();
657 orb_.QMAtoms() = originalmol;
658 orb_.SetupDftBasis(orb_.getDftBasis().Name());
659 orb_.SetupAuxBasis(orb_.getAuxBasis().Name());
660 }
661 E_hist_.push_back(energy);
662
663 Dmat_hist_.push_back(orb_.DensityMatrixFull(state));
665 return;
666}
667
669 if (overlap_sqrt_.rows() == Dmat_hist_.back().rows()) {
670 return;
671 }
672 overlap_sqrt_.resize(0, 0);
673 if (orb_.getBasisSetSize() != Dmat_hist_.back().rows() ||
674 orb_.getDFTbasisName().empty()) {
675 return; // no matching basis to orthonormalise with: AO measure
676 }
677 AOOverlap overlap;
678 try {
679 overlap.Fill(orb_.getDftBasis());
680 } catch (const std::runtime_error&) {
681 return;
682 }
683 if (overlap.Matrix().rows() != Dmat_hist_.back().rows()) {
684 return;
685 }
686 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(overlap.Matrix());
687 overlap_sqrt_ = es.eigenvectors() *
688 es.eigenvalues().cwiseMax(0.0).cwiseSqrt().asDiagonal() *
689 es.eigenvectors().transpose();
690}
691
693 if (orb_.QMAtoms().size() == 0) {
694 orb_.QMAtoms() = mol;
695 } else {
696 orb_.QMAtoms().AddContainer(mol);
697 }
698 size_++;
699}
700
701double QMRegion::charge() const {
702 double charge = 0.0;
703 if (!do_gwbse_) {
704 Index nuccharge = 0;
705 for (const QMAtom& a : orb_.QMAtoms()) {
706 nuccharge += a.getNuccharge();
707 }
708
709 Index electrons = orb_.getNumberOfAlphaElectrons();
710 if (orb_.isOpenShell()) {
711 electrons += orb_.getNumberOfBetaElectrons();
712 } else {
713 electrons *= 2;
714 }
715
716 charge = double(nuccharge - electrons);
717 } else {
718 QMState state = statetracker_.InitialState();
719 if (state.Type().isExciton()) {
720 charge = 0.0;
721 } else if (state.Type().isSingleParticleState() ||
722 state.Type().isKSState()) {
723 if (state.StateIdx() <= orb_.getHomo()) {
724 charge = +1.0;
725 } else {
726 charge = -1.0;
727 }
728 }
729 }
730 return charge;
731}
732
734 prop.add("E_total", std::to_string(E_hist_.back() * tools::conv::hrt2ev));
735 if (do_gwbse_) {
736 prop.add("Initial_State", statetracker_.InitialState().ToString());
737 prop.add("Final_State", statetracker_.CalcState(orb_).ToString());
738 }
739}
740
742
743 std::string dft_package_name = dftoptions_.get("name").as<std::string>();
744 qmpackage_ =
745 std::unique_ptr<QMPackage>(QMPackageFactory().Create(dft_package_name));
746 qmpackage_->setLog(&log_);
747 qmpackage_->Initialize(dftoptions_);
748 Index charge = 0;
749 if (initstate_.Type() == QMStateType::Electron) {
750 charge = -1;
751 } else if (initstate_.Type() == QMStateType::Hole) {
752 charge = +1;
753 }
754 qmpackage_->setCharge(charge);
755 return;
756}
758 throw std::runtime_error(
759 "QMRegion-QMRegion interaction is not implemented yet.");
760 return 0.0;
761}
763 // Charge-response segments put Gaussian induced charges and dipoles into
764 // the QM Hamiltonian (DFTEngine, AOGaussianSites). Other packages get
765 // the environment as point charges (ORCA: permanent charges and split
766 // dipoles) and would silently miss the induced charges, while the
767 // environment still responds to the QM density.
768 if (region.hasChargeResponse() && qmpackage_->getPackageName() != "xtp") {
769 throw std::runtime_error(
770 "polar region " + std::to_string(region.getId()) +
771 " has charge-response segments, which only the xtp DFT package "
772 "supports (dftpackage name " +
773 qmpackage_->getPackageName() + "). Use xtp or Thole segments.");
774 }
775 qmpackage_->AddRegion(region);
776 return 0.0;
777}
779 qmpackage_->AddRegion(region);
780 return 0.0;
781}
782
784 writer.WriteContainer(orb_.QMAtoms());
785}
786
787void QMRegion::AddNucleiFields(std::vector<PolarSegment>& segments,
788 const StaticSegment& seg) const {
790#pragma omp parallel for
791 for (Index i = 0; i < Index(segments.size()); ++i) {
792 if (segments[i].hasChargeResponse()) {
793 continue; // Gaussian sites, see ApplyQMFieldToPolarSegments
794 }
795 e.ApplyStaticField<StaticSegment, Estatic::noE_V>(seg, segments[i]);
796 }
797}
798
800 std::vector<PolarSegment>& segments) const {
801
802 Vxc_Grid grid;
803 AOBasis basis =
804 orb_.getDftBasis(); // grid needs a basis in scope all the time
805 grid.GridSetup(grid_accuracy_for_ext_interaction_, orb_.QMAtoms(), basis);
806 DensityIntegration<Vxc_Grid> numint(grid);
807
808 // With environment screening the environment is polarized by the
809 // ground state only: the excitation's own polarization is in W.
810 QMState state = QMState("groundstate");
811 if ((do_gwbse_ && !screening_) || initstate_.Type().isKSState()) {
812 state = statetracker_.CalcState(orb_);
813 }
814 Eigen::MatrixXd dmat = orb_.DensityMatrixFull(state);
815 double Ngrid = numint.IntegrateDensity(dmat);
816 AOOverlap overlap;
817 overlap.Fill(basis);
818 double N_comp = dmat.cwiseProduct(overlap.Matrix()).sum();
819 if (std::abs(Ngrid - N_comp) > 0.001) {
820 XTP_LOG(Log::error, log_) << "=======================" << std::flush;
822 << "WARNING: Calculated Densities at Numerical Grid, Number of "
823 "electrons "
824 << Ngrid << " is far away from the the real value " << N_comp
825 << ", you should increase the accuracy of the integration grid."
826 << std::flush;
827 }
828#pragma omp parallel for
829 for (Index i = 0; i < Index(segments.size()); ++i) {
830 PolarSegment& seg = segments[i];
831 if (seg.hasChargeResponse()) {
832 continue;
833 }
834 for (PolarSite& site : seg) {
835 site.V_noE() += numint.IntegrateField(site.getPos());
836 }
837 }
838
839 // Charge-response sites are Gaussians: the potential and field of the QM
840 // region they see are averaged over their Gaussian, with the same
841 // three-centre integrals that put their induced charges and dipoles into
842 // the QM Hamiltonian (DFTEngine), so the coupling is symmetric.
843 std::vector<PolarSite*> cr_sites;
844 for (PolarSegment& seg : segments) {
845 if (seg.hasChargeResponse()) {
846 for (PolarSite& site : seg) {
847 cr_sites.push_back(&site);
848 }
849 }
850 }
851 if (!cr_sites.empty()) {
852 std::vector<Eigen::Vector3d> points;
853 std::vector<double> widths;
854 for (const PolarSite* site : cr_sites) {
855 points.push_back(site->getPos());
856 widths.push_back(site->CRWidth());
857 }
858 // electrons: the density is -dmat
859 const Eigen::MatrixXd electrons =
860 AOGaussianSites::PotentialAndGradientAt(basis, dmat, points, widths);
861 for (Index k = 0; k < Index(cr_sites.size()); ++k) {
862 PolarSite& site = *cr_sites[std::size_t(k)];
863 site.Phi_noE() -= electrons(k, 0);
864 site.V_noE() -= electrons.block<1, 3>(k, 1).transpose();
865 for (const QMAtom& atom : orb_.QMAtoms()) {
866 const Eigen::Vector3d r = site.getPos() - atom.getPos();
867 const GaussianKernel kern(r.norm(), site.CRWidth());
868 const double Z = double(atom.getNuccharge());
869 site.Phi_noE() += Z * kern.B0;
870 site.V_noE() -= Z * kern.B1 * r; // grad phi = -field
871 }
872 }
873 }
874
875 StaticSegment seg(orb_.QMAtoms().getType(), orb_.QMAtoms().getId());
876 for (const QMAtom& atom : orb_.QMAtoms()) {
877 seg.push_back(StaticSite(atom, double(atom.getNuccharge())));
878 }
879 AddNucleiFields(segments, seg);
880}
881
883 w(id_, "id");
884 w(identify(), "type");
885 w(size_, "size");
886 w(do_gwbse_, "GWBSE");
887 w(initstate_.ToString(), "initial_state");
889 CheckpointWriter v = w.openChild("QMdata");
890 orb_.WriteToCpt(v);
891 orb_.WriteBasisSetsToCpt(v);
892
893 CheckpointWriter v2 = w.openChild("E-hist");
894 E_hist_.WriteToCpt(v2);
895
896 CheckpointWriter v3 = w.openChild("D-hist");
897 Dmat_hist_.WriteToCpt(v3);
898 if (do_gwbse_) {
899 CheckpointWriter v4 = w.openChild("statefilter");
900 statetracker_.WriteToCpt(v4);
901 }
902}
903
905 r(id_, "id");
906 r(size_, "size");
907 r(do_gwbse_, "GWBSE");
908 std::string state;
909 r(state, "initial_state");
910 initstate_.FromString(state);
912 CheckpointReader rr = r.openChild("QMdata");
913 orb_.ReadFromCpt(rr);
914 orb_.ReadBasisSetsFromCpt(rr);
915
916 CheckpointReader rr2 = r.openChild("E-hist");
917 E_hist_.ReadFromCpt(rr2);
918
919 CheckpointReader rr3 = r.openChild("D-hist");
920 Dmat_hist_.ReadFromCpt(rr3);
921 if (do_gwbse_) {
922 CheckpointReader rr4 = r.openChild("statefilter");
923 statetracker_.ReadFromCpt(rr4);
924 }
925}
926
928 // First contact with an EwaldRegion is what decides this region takes its
929 // environment as a potential on a grid, so the grid is built here rather
930 // than in Initialize. See the declaration for why eagerly would be wrong.
931 if (!ewald_grid_ready_) {
933 }
934
935 // ONCE PER JOB, not once per iteration. phi is the potential of a FROZEN
936 // background evaluated at FIXED points: the registry is converged before
937 // the job starts and nothing re-polarizes it, the foreground registration
938 // is made once by JobTopology, and the grid follows the QM geometry,
939 // which does not move inside the inter-region SCF loop. The QM density
940 // enters nowhere in it. So re-evaluating would give bit-identical numbers
941 // at the cost of a full lattice sum over every quadrature point, every
942 // iteration. The values stay where they were written -- in ewaldgrid_'s
943 // own boxes and in ewald_nuclear_energy_, both members of this region
944 // that Reset() does not touch.
945 //
946 // This stops being true the moment the background is allowed to respond
947 // to the foreground. If that ever changes, this is the guard to remove.
949 return 0.0;
950 }
951
952 // ELECTRONS. The potential on the integration grid, which the DFT
953 // engine integrates against the density into H0. The nuclei (below) are
954 // appended to the same call: every call recomputes the reciprocal-space
955 // structure factors of the whole background, whatever the point count.
956 std::vector<Eigen::Vector3d> points = copyEwaldGrid();
957 const Index n_grid = Index(points.size());
958 for (const QMAtom& atom : orb_.QMAtoms()) {
959 points.push_back(atom.getPos());
960 }
961 const Eigen::VectorXd phi_all = region.PotentialAt(points);
962 const Eigen::VectorXd phi = phi_all.head(n_grid);
963 const Eigen::VectorXd phi_nuclei = phi_all.tail(phi_all.size() - n_grid);
964
965 // Vxc_Grid::getGridpoints() walks grid_boxes_ in order and each box's
966 // own points in order, so the flat vector maps back by giving each box
967 // its share in turn. Checked rather than assumed: a mispairing here is
968 // a wrong Hamiltonian with nothing to notice it by.
969 Index offset = 0;
970 for (Index i = 0; i < ewaldgrid_.getBoxesSize(); ++i) {
971 GridBox& box = ewaldgrid_[i];
972 const Index n_box = Index(box.getGridPoints().size());
973 std::vector<double>& values = box.getPotentialValues();
974 values.resize(std::size_t(n_box));
975 for (Index p = 0; p < n_box; ++p) {
976 values[std::size_t(p)] = phi[offset + p];
977 }
978 offset += n_box;
979 }
980 if (offset != phi.size()) {
981 throw std::runtime_error(
982 "QMRegion::InteractwithEwaldRegion: the grid holds " +
983 std::to_string(offset) + " points across its boxes but " +
984 std::to_string(phi.size()) +
985 " potential values were evaluated. getGridpoints() and the box "
986 "walk above disagree about the point ordering.");
987 }
988
989 // NUCLEI. The grid potential reaches the electron density only; the
990 // nuclei sit in the same potential and have no other route in. Handed
991 // to the engine as a scalar to add to E0, NOT returned from here --
992 // QMRegion::Evaluate accumulates the Interactwith* return values into
993 // e_ext, logs it, and drops it on the floor.
995 Index a = 0;
996 for (const QMAtom& atom : orb_.QMAtoms()) {
997 ewald_nuclear_energy_ += double(atom.getNuccharge()) * phi_nuclei[a];
998 ++a;
999 }
1000
1002
1003 // 0.0, matching this class's own InteractwithPolarRegion and
1004 // InteractwithStaticRegion: the environment enters the Hamiltonian, and
1005 // its energy comes out in the DFT total rather than here.
1006 return 0.0;
1007}
1008
1009} // namespace xtp
1010} // namespace votca
void WriteContainer(T &container)
Definition pdbwriter.h:102
class to manage program options with xml serialization functionality
Definition property.h:55
Property & add(const std::string &key, const std::string &value)
add a new property to structure
Definition property.cc:108
Property & get(const std::string &key)
get existing property
Definition property.cc:79
bool exists(const std::string &key) const
check whether property exists
Definition property.cc:122
T as() const
return value as type
Definition property.h:283
T ifExistsReturnElseReturnDefault(const std::string &key, T defaultvalue) const
Definition property.h:332
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index AOBasisSize() const
Definition aobasis.h:46
void Fill(const BasisSet &bs, const QMMolecule &atoms)
Definition aobasis.cc:85
static Eigen::MatrixXd PotentialAndGradientAt(const AOBasis &basis, const Eigen::MatrixXd &D, const std::vector< Eigen::Vector3d > &points, const std::vector< double > &widths)
void Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
Definition aomatrix.h:52
void push_back(const T &atom)
void Load(const std::string &name)
Definition basisset.cc:149
CheckpointReader openChild(const std::string &childName) const
CheckpointWriter openChild(const std::string &childName) const
Eigen::Vector3d IntegrateField(const Eigen::Vector3d &rvector) const
double IntegrateDensity(const Eigen::MatrixXd &density_matrix)
static Eigen::MatrixXd Metric(const AOBasis &auxbasis)
T for an auxiliary basis on its own: the metric TCMatrix_gwbse::Fill folds into the three-centre inte...
static ScreeningCheck Check(const AOBasis &auxbasis, const QMMolecule &atoms, const ScreeningEnvironment &env, const Eigen::MatrixXd &T, Index n_modes=3)
Builds R for env and reports whether 1 + R is positive definite, and why not.
The periodic Ewald background, as a Region.
Definition ewaldregion.h:74
Eigen::VectorXd PotentialAt(const std::vector< Eigen::Vector3d > &points) const
Electronic excitations from GW-BSE.
Definition gwbse.h:59
void setScreeningEnvironment(const ScreeningEnvironment &env)
Definition gwbse.h:71
void Initialize(tools::Property &options)
Definition gwbse.cc:65
void setLogger(Logger *pLog)
Definition gwbse.h:66
const std::vector< Eigen::Vector3d > & getGridPoints() const
Definition gridbox.h:55
std::vector< double > & getPotentialValues()
Definition gridbox.h:58
std::vector< T >::iterator begin()
Definition mmregion.h:60
std::vector< T >::iterator end()
Definition mmregion.h:61
void computePML(Orbitals &orbitals)
bool hasChargeResponse() const
std::string identify() const override
Definition polarregion.h:52
double ExpDamp() const
Definition polarregion.h:66
Class to represent Atom/Site in electrostatic+polarization.
Definition polarsite.h:36
double CRWidth() const
Definition polarsite.h:107
const Eigen::Vector3d & V_noE() const
Definition polarsite.h:72
double Phi_noE() const
Definition polarsite.h:80
container for QM atoms
Definition qmatom.h:37
void AppendResult(tools::Property &prop) const override
Definition qmregion.cc:733
StateTracker statetracker_
Definition qmregion.h:180
hist< Eigen::MatrixXd > Dmat_hist_
Definition qmregion.h:148
void PrepareEwaldPotentialGrid()
Definition qmregion.cc:397
std::string grid_accuracy_for_ext_interaction_
Definition qmregion.h:145
std::string identify() const override
Definition qmregion.h:82
hist< double > E_hist_
Definition qmregion.h:147
void AddNucleiFields(std::vector< PolarSegment > &segments, const StaticSegment &seg) const
Definition qmregion.cc:787
std::shared_ptr< DFTSetupCache > setup_cache_
Definition qmregion.h:143
std::unique_ptr< QMPackage > qmpackage_
Definition qmregion.h:140
bool screening_include_kreac_
Definition qmregion.h:163
void push_back(const QMMolecule &mol)
Definition qmregion.cc:692
void WriteToCpt(CheckpointWriter &w) const override
Definition qmregion.cc:882
double InteractwithStaticRegion(const StaticRegion &region) override
Definition qmregion.cc:778
void EvaluateScreenedGWBSE(std::vector< std::unique_ptr< Region > > &regions)
Definition qmregion.cc:325
void AppendScreenedSiteEnergies(tools::Property &prop) const
Definition qmregion.cc:358
std::string ScreeningAuxBasisName() const
Definition qmregion.cc:272
double ewald_nuclear_energy_
Definition qmregion.h:240
double InteractwithPolarRegion(const PolarRegion &region) override
Definition qmregion.cc:762
std::string workdir_
Definition qmregion.h:139
void Reset() override
Definition qmregion.cc:741
QMState screened_state_
Definition qmregion.h:168
QMRegion(Index id, Logger &log, std::string workdir)
Definition qmregion.h:49
double StateEnergy(const QMState &state) const
Definition qmregion.cc:196
void WritePDB(csg::PDBWriter &writer) const override
Definition qmregion.cc:783
double InteractwithEwaldRegion(const EwaldRegion &region) override
Definition qmregion.cc:927
void ReadFromCpt(CheckpointReader &r) override
Definition qmregion.cc:904
ScreeningEnvironment BuildScreeningEnvironment(const std::vector< std::unique_ptr< Region > > &regions) const
Definition qmregion.cc:208
bool Converged() const override
Definition qmregion.cc:436
void Evaluate(std::vector< std::unique_ptr< Region > > &regions) override
Definition qmregion.cc:475
tools::Property localize_options_
Definition qmregion.h:178
std::vector< Eigen::Vector3d > copyEwaldGrid()
Definition qmregion.cc:388
void PrepareOrbitalsForGWBSE()
Definition qmregion.cc:167
double InteractwithQMRegion(const QMRegion &region) override
Definition qmregion.cc:757
void ApplyQMFieldToPolarSegments(std::vector< PolarSegment > &segments) const
Definition qmregion.cc:799
bool ewald_potential_evaluated_
Definition qmregion.h:237
Eigen::MatrixXd overlap_sqrt_
Definition qmregion.h:151
double screening_shell_dielectric_
Definition qmregion.h:165
void Initialize(const tools::Property &prop) override
Definition qmregion.cc:48
std::vector< Index > screening_shell_regions_
Definition qmregion.h:164
double charge() const override
Definition qmregion.cc:701
double screening_site_width_
Definition qmregion.h:166
void CheckScreeningEnvironment(const std::vector< std::unique_ptr< Region > > &regions) const
Definition qmregion.cc:286
tools::Property dftoptions_
Definition qmregion.h:176
tools::Property gwbseoptions_
Definition qmregion.h:177
bool isKSState() const
Definition qmstate.h:92
bool isSingleParticleState() const
Definition qmstate.h:83
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
const QMStateType & Type() const
Definition qmstate.h:154
std::vector< double > ApplyInfluenceOfOtherRegions(std::vector< std::unique_ptr< Region > > &regions)
Definition region.cc:33
Index getId() const
Definition region.h:80
std::string errormsg_
Definition region.h:92
Logger & log_
Definition region.h:106
std::string identify() const override
Class to represent Atom/Site in electrostatic.
Definition staticsite.h:37
const Eigen::Vector3d & getPos() const
Definition staticsite.h:80
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
void GridSetup(const std::string &type, const QMMolecule &atoms, const AOBasis &basis)
Definition vxc_grid.cc:230
Mediates interaction between polar and static sites.
#define XTP_LOG(level, log)
Definition logger.h:40
const double hrt2ev
Definition constants.h:53
void Cite(const std::string &key)
marks a reference as used (thread safe; unknown keys are printed as such)
Definition citations.cc:289
Charge transport classes.
Definition ERIs.h:28
ClassicalSegment< PolarSite > PolarSegment
ClassicalSegment< StaticSite > StaticSegment
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
Interaction of two normalized Gaussian charge distributions exp(-r^2/s_i^2) and exp(-r^2/s_j^2): erf(...
Outcome of EnvironmentScreening::Check: whether 1 + R is positive definite, and a human-readable acco...
The polarizable environment a GW-BSE calculation is screened by: what a QM/MM job hands to GWBSE (GWB...
std::vector< PolarSegment > explicit_segments
std::vector< PolarSegment > shell_segments