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