votca 2026-dev
Loading...
Searching...
No Matches
qmmm.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2020 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20// Standard includes
21#include <algorithm>
22#include <array>
23#include <chrono>
24#include <cmath>
25#include <filesystem>
26#include <sstream>
27
28// Third party includes
29#include <numeric>
30#include <stdexcept>
31
32// Local VOTCA includes
37#include "votca/xtp/qmregion.h"
38
39// Local private VOTCA includes
40#include "qmmm.h"
41#include "votca/xtp/qmstate.h"
42
43namespace votca {
44namespace xtp {
45
47
48 print_regions_pdb_ = options.get(".print_regions_pdb").as<bool>();
49 max_iterations_ = options.get(".max_iterations").as<Index>();
50 regions_def_.second = options.get(".regions");
51 regions_def_.first = mapfile_;
52 use_gs_for_ex_ = options.get("io_jobfile.use_gs_for_ex").as<bool>();
53
54 states_ = options.get("io_jobfile.states").as<std::vector<QMState>>();
55 which_segments_ = options.get("io_jobfile.segments").as<std::string>();
56
57 bool groundstate_found = std::any_of(
58 states_.begin(), states_.end(),
59 [](const QMState& state) { return state.Type() == QMStateType::Gstate; });
60 // The n job is the reference -j read subtracts. A job with
61 // environment_screening converges that ground state itself and reports
62 // its site energies relative to it, and refuses state n anyway.
63 if (!groundstate_found && !hasEnvironmentScreening()) {
64 states_.push_back(QMState("n"));
65 }
66}
67
69 std::chrono::time_point<std::chrono::system_clock> start =
70 std::chrono::system_clock::now();
71
72 std::string qmmm_work_dir = "QMMM";
73 if (!this->hasQMRegion()) {
74 qmmm_work_dir = "MMMM";
75 }
76 std::string frame_dir =
77 "frame_" + boost::lexical_cast<std::string>(top.getStep());
78 std::string job_dir =
79 "job_" + std::to_string(job.getId()) + "_" + job.getTag();
80 std::filesystem::path arg_path;
81 std::string workdir =
82 (arg_path / qmmm_work_dir / frame_dir / job_dir).generic_string();
83 std::filesystem::create_directories(workdir);
85 Logger& pLog = Thread.getLogger();
86
87 JobTopology jobtop = JobTopology(job, pLog, workdir);
88 if (job.getInput().exists("restart")) {
89 std::string checkptfile = job.getInput().get("restart").as<std::string>();
90 XTP_LOG(Log::error, pLog)
91 << TimeStamp() << " Restart job from " << checkptfile << std::flush;
92 jobtop.ReadFromHdf5(checkptfile);
93 } else {
94 jobtop.BuildRegions(top, regions_def_);
95 }
96
98 std::string pdb_filename = "regions.pdb";
99 XTP_LOG(Log::error, pLog) << TimeStamp() << " Writing jobtopology to "
100 << (workdir + "/" + pdb_filename) << std::flush;
101 jobtop.WriteToPdb(workdir + "/" + pdb_filename);
102 }
103
104 // Embedded GW-BSE: its reaction field depends only on geometry and the
105 // auxiliary basis, so a bad environment is caught before the loop.
106 if (QMRegion* qmregion = dynamic_cast<QMRegion*>(jobtop.Regions()[0].get());
107 qmregion != nullptr && qmregion->EnvironmentScreeningEnabled()) {
108 try {
109 qmregion->CheckScreeningEnvironment(jobtop.Regions());
110 } catch (std::exception& e) {
111 XTP_LOG(Log::error, pLog)
112 << TimeStamp() << " Screening environment check failed: " << e.what()
113 << std::flush;
115 jres.setError(std::string("Screening environment check failed: ") +
116 e.what());
117 return jres;
118 }
119 }
120
121 Index no_static_regions = std::accumulate(
122 jobtop.begin(), jobtop.end(), 0, [](Index count, const auto& region) {
123 return count += Index(region->Converged());
124 });
125
126 bool no_top_scf = false;
127 if (jobtop.size() - no_static_regions < 2) {
128 XTP_LOG(Log::error, pLog)
129 << TimeStamp() << " Only " << jobtop.size() - no_static_regions
130 << " scf region is used. The remaining regions are static. So no "
131 "inter regions scf is required. "
132 << std::flush;
133 no_top_scf = true;
134 max_iterations_ = 1;
135 }
136 Index iteration = 0;
137 for (; iteration < max_iterations_; iteration++) {
138
139 XTP_LOG(Log::error, pLog)
140 << TimeStamp() << " --Inter Region SCF Iteration " << iteration + 1
141 << " of " << max_iterations_ << std::flush;
142
143 for (std::unique_ptr<Region>& region : jobtop) {
144 XTP_LOG(Log::error, pLog)
145 << TimeStamp() << " Evaluating " << region->identify() << " "
146 << region->getId() << std::flush;
147 region->Reset();
148 region->Evaluate(jobtop.Regions());
149 if (!region->Successful()) {
151 jres.setError(region->ErrorMsg());
152 return jres;
153 }
154 }
155
156 std::string checkpointfilename =
157 "checkpoint_iter_" + std::to_string(iteration + 1) + ".hdf5";
158 XTP_LOG(Log::error, pLog) << TimeStamp() << " Writing checkpoint to "
159 << checkpointfilename << std::flush;
160 try {
161 jobtop.WriteToHdf5(workdir + "/" + checkpointfilename);
162 } catch (std::exception& e) {
163 XTP_LOG(Log::error, pLog)
164 << TimeStamp()
165 << " Could not write checkpoint file due to exception: " << e.what()
166 << std::flush;
167 }
168
169 if (!no_top_scf) {
170 std::vector<bool> converged_regions;
171 for (std::unique_ptr<Region>& region : jobtop) {
172 converged_regions.push_back(region->Converged());
173 }
174
175 double etot = std::accumulate(
176 jobtop.begin(), jobtop.end(), 0.0,
177 [](double e, const auto& region) { return region->Etotal() + e; });
178
179 XTP_LOG(Log::error, pLog) << TimeStamp() << " --Total Energy all regions "
180 << etot << std::flush;
181
182 bool all_regions_converged =
183 std::all_of(converged_regions.begin(), converged_regions.end(),
184 [](bool i) { return i; });
185
186 if (all_regions_converged) {
187 XTP_LOG(Log::error, pLog)
188 << TimeStamp() << " Job converged after " << iteration + 1
189 << " iterations." << std::flush;
191 break;
192 }
193 if (iteration == max_iterations_ - 1) {
194 XTP_LOG(Log::error, pLog)
195 << TimeStamp() << " Job did not converge after " << iteration + 1
196 << " iterations.\n Writing results to jobfile." << std::flush;
198 jres.setError("Inter Region SCF did not converge in " +
199 std::to_string(max_iterations_) + " iterations.");
200 }
201 } else {
203 }
204 }
205
206 // Embedded GW-BSE: the loop above converged the ground state; the
207 // excitation is screened by the polar regions in one GW-BSE run.
208 QMRegion* screened_qmregion = nullptr;
209 double e_ground = 0.0;
210 if (jres.getStatus() == Job::JobStatus::COMPLETE) {
211 QMRegion* qmregion = dynamic_cast<QMRegion*>(jobtop.Regions()[0].get());
212 if (qmregion != nullptr && qmregion->EnvironmentScreeningEnabled()) {
213 // The converged ground state's total energy, before the screened
214 // run replaces the QM region's energy with that of the state.
215 for (const std::unique_ptr<Region>& reg : jobtop) {
216 e_ground += reg->Etotal();
217 }
218 screened_qmregion = qmregion;
219 try {
220 qmregion->EvaluateScreenedGWBSE(jobtop.Regions());
221 } catch (std::exception& e) {
222 XTP_LOG(Log::error, pLog)
223 << TimeStamp() << " Screened GW-BSE failed: " << e.what()
224 << std::flush;
226 jres.setError(std::string("Screened GW-BSE failed: ") + e.what());
227 return jres;
228 }
229 try {
230 jobtop.WriteToHdf5(workdir + "/checkpoint_screened.hdf5");
231 } catch (std::exception& e) {
232 XTP_LOG(Log::error, pLog)
233 << TimeStamp()
234 << " Could not write checkpoint file due to exception: " << e.what()
235 << std::flush;
236 }
237 }
238 }
239
240 tools::Property results;
241 tools::Property& jobresult = results.add("output", "");
242 tools::Property& regionsresults = jobresult.add("regions", "");
243 double etot = 0.0;
244 double charge = 0.0;
245 for (const std::unique_ptr<Region>& reg : jobtop) {
246 reg->AddResults(regionsresults);
247 etot += reg->Etotal();
248 charge += reg->charge();
249 }
250 std::chrono::time_point<std::chrono::system_clock> end =
251 std::chrono::system_clock::now();
252 std::chrono::duration<double> elapsed_time = end - start;
253 jobresult.add("E_tot", std::to_string(etot * tools::conv::hrt2ev));
254 jobresult.add("Compute_Time", std::to_string(Index(elapsed_time.count())));
255 jobresult.add("Total_Charge", std::to_string(charge));
256 if (!no_top_scf) {
257 jobresult.add("Iterations", std::to_string(iteration + 1));
258 }
259 if (screened_qmregion != nullptr) {
260 jobresult.add("E_ground", std::to_string(e_ground * tools::conv::hrt2ev));
261 screened_qmregion->AppendScreenedSiteEnergies(jobresult);
262 }
263 jres.setOutput(results);
264 return jres;
265}
266
268 for (const tools::Property& reg : regions_def_.second) {
269 if (reg.name() == "qmregion" && reg.exists("environment_screening")) {
270 const tools::Property& env = reg.get("environment_screening");
271 return env.ifExistsReturnElseReturnDefault<bool>("enabled", true);
272 }
273 }
274 return false;
275}
276
277bool QMMM::hasQMRegion() const {
278 Logger log;
279 QMRegion QMdummy(0, log, "");
280 return std::any_of(regions_def_.second.begin(), regions_def_.second.end(),
281 [&](const tools::Property& reg) {
282 return reg.name() == QMdummy.identify();
283 });
284}
285
286std::string QMMM::getFirstRegionName() const {
287 for (const auto& reg : regions_def_.second) {
288 if (reg.get("id").as<Index>() == 0) {
289 return reg.name();
290 }
291 }
292 throw std::runtime_error("region ids do not start at 0");
293 return "";
294}
295
296void QMMM::WriteJobFile(const Topology& top) {
297
298 std::cout << std::endl
299 << "... ... Writing job file " << jobfile_ << std::flush;
300
301 std::ofstream ofs;
302 ofs.open(jobfile_, std::ofstream::out);
303 if (!ofs.is_open()) {
304 throw std::runtime_error("\nERROR: bad file handle: " + jobfile_);
305 }
306
307 std::vector<Index> segments_to_write;
308 if (which_segments_ == "all") {
309 for (Index i = 0; i < Index(top.Segments().size()); ++i) {
310 segments_to_write.push_back(i);
311 }
312 } else {
313 segments_to_write = IndexParser().CreateIndexVector(which_segments_);
314 }
315
316 ofs << "<jobs>" << std::endl;
317 Index jobid = 0;
318 for (Index segID : segments_to_write) {
319 const Segment& seg = top.Segments()[segID];
320 for (const QMState& state : states_) {
321 Job job = createJob(seg, state, jobid);
322 job.ToStream(ofs);
323 jobid++;
324 }
325 }
326
327 ofs << "</jobs>" << std::endl;
328 ofs.close();
329 std::cout << std::endl
330 << "... ... In total " << jobid << " jobs" << std::flush;
331 return;
332}
333
334Job QMMM::createJob(const Segment& seg, const QMState& state,
335 Index jobid) const {
336 std::string marker = std::to_string(seg.getId()) + ":" + state.ToString();
337 std::string tag = seg.getType() + "_" + marker;
338
339 tools::Property Input;
340 tools::Property& pInput = Input.add("input", "");
341 pInput.add("site_energies", marker);
342 tools::Property& regions = pInput.add("regions", "");
343 tools::Property& region = regions.add(getFirstRegionName(), "");
344 region.add("id", "0");
345 if (hasQMRegion()) {
346 region.add("state", state.ToString());
347 }
348 // Quasiparticle levels are levels of the neutral molecule: its geometry,
349 // there being no qmcoords_pqp to look up.
350 if ((use_gs_for_ex_ && (state.Type() == QMStateType::Singlet ||
351 state.Type() == QMStateType::Triplet)) ||
352 state.Type().isGWState()) {
353 region.add("segments", std::to_string(seg.getId()) + ":n");
354 } else {
355 region.add("segments", marker);
356 }
357 Job job(jobid, tag, Input, Job::AVAILABLE);
358 return job;
359}
360
362
363 Index incomplete_jobs = 0;
364
365 Eigen::Matrix<double, Eigen::Dynamic, 5> energies =
366 Eigen::Matrix<double, Eigen::Dynamic, 5>::Zero(top.Segments().size(), 5);
367 Eigen::Matrix<bool, Eigen::Dynamic, 5> found =
368 Eigen::Matrix<bool, Eigen::Dynamic, 5>::Zero(top.Segments().size(), 5);
369 // Site energies reported directly by screened jobs (relative to their
370 // own ground state, Hartree), for e, h, s, t.
371 Eigen::Matrix<double, Eigen::Dynamic, 4> screened =
372 Eigen::Matrix<double, Eigen::Dynamic, 4>::Zero(top.Segments().size(), 4);
373 Eigen::Matrix<bool, Eigen::Dynamic, 4> screened_found =
374 Eigen::Matrix<bool, Eigen::Dynamic, 4>::Zero(top.Segments().size(), 4);
375
376 tools::Property xml;
378 for (tools::Property* job : xml.Select("jobs.job")) {
379
380 Index jobid = job->get("id").as<Index>();
381 if (!job->exists("status")) {
382 throw std::runtime_error(
383 "Jobfile is malformed. <status> tag missing for job " +
384 std::to_string(jobid));
385 }
386 if (job->get("status").as<std::string>() != "COMPLETE" ||
387 !job->exists("output")) {
388 incomplete_jobs++;
389 continue;
390 }
391
392 std::vector<std::string> split =
393 tools::Tokenizer(job->get("input.site_energies").as<std::string>(), ":")
394 .ToVector();
395
396 Index segid = std::stoi(split[0]);
397 if (segid < 0 || segid >= Index(top.Segments().size())) {
398 throw std::runtime_error("JobSegment id" + std::to_string(segid) +
399 " is not in topology for job " +
400 std::to_string(jobid));
401 }
402 QMState state;
403 try {
404 state.FromString(split[1]);
405 } catch (std::runtime_error& e) {
406 std::stringstream message;
407 message << e.what() << " for job " << jobid;
408 throw std::runtime_error(message.str());
409 }
410 if (job->exists("output.screened_site_energies")) {
411 const tools::Property& se = job->get("output.screened_site_energies");
412 const std::array<std::pair<const char*, QMStateType::statetype>, 4>
413 types = {{{"e", QMStateType::Electron},
414 {"h", QMStateType::Hole},
416 {"t", QMStateType::Triplet}}};
417 for (const auto& [tag, type] : types) {
418 if (!se.exists(tag)) {
419 continue;
420 }
421 const double value = se.get(tag).as<double>() * tools::conv::ev2hrt;
422 if (screened_found(segid, type)) {
423 // e and h come with every screened job of a segment (an s1 and a
424 // pqp job, say), from the same ground state: they must agree.
425 const double diff =
426 std::abs(value - screened(segid, type)) * tools::conv::hrt2ev;
427 if (diff > 1e-3) {
428 throw std::runtime_error(
429 "Screened jobs for segment " + std::to_string(segid) +
430 " disagree on its " + std::string(tag) + " site energy by " +
431 std::to_string(diff) +
432 " eV; were they run with different settings?");
433 }
434 continue;
435 }
436 screened(segid, type) = value;
437 screened_found(segid, type) = true;
438 }
439 // Its E_tot is not an n/e/h/s/t total energy to be differenced:
440 // the site energies above are already relative to the ground state.
441 continue;
442 }
443
444 const Index col = state.Type().Type();
445 if (col > QMStateType::Gstate) {
446 throw std::runtime_error(
447 "Job " + std::to_string(jobid) + " has state " + state.ToString() +
448 ", which -j read can only use from a job with environment_screening "
449 "(it reports its own site energies).");
450 }
451 double energy = job->get("output.E_tot").as<double>() * tools::conv::ev2hrt;
452 if (found(segid, col) != 0) {
453 throw std::runtime_error("There are two entries in jobfile for segment " +
454 std::to_string(segid) +
455 " state:" + state.ToString());
456 }
457
458 energies(segid, col) = energy;
459 found(segid, col) = true;
460 }
461
462 Eigen::Matrix<Index, 1, 5> found_states = found.colwise().count();
463 std::cout << std::endl;
464 for (Index i = 0; i < 5; i++) {
465 if (found_states(i) > 0) {
466 QMStateType type(static_cast<QMStateType::statetype>(i));
467 std::cout << "Found " << found_states(i) << " states of type "
468 << type.ToString() << std::endl;
469 }
470 }
471 if (incomplete_jobs > 0) {
472 std::cout << incomplete_jobs << " incomplete jobs found." << std::endl;
473 }
474
475 Eigen::Matrix<Index, 1, 4> screened_states = screened_found.colwise().count();
476 for (Index i = 0; i < 4; i++) {
477 if (screened_states(i) > 0) {
478 QMStateType type(static_cast<QMStateType::statetype>(i));
479 std::cout << "Found " << screened_states(i) << " screened site energies "
480 << "of type " << type.ToString() << std::endl;
481 }
482 }
483
484 Index overridden = 0;
485 for (Segment& seg : top.Segments()) {
486 Index segid = seg.getId();
487 for (Index i = 0; i < 4; i++) {
488 QMStateType type(static_cast<QMStateType::statetype>(i));
489 if (screened_found(segid, i)) {
490 // A screened job's own value is the one to use: it is relative to
491 // the ground state of the very same run.
492 if (found(segid, i) && found(segid, 4)) {
493 ++overridden;
494 }
495 seg.setEMpoles(type, screened(segid, i));
496 } else if (found(segid, i) && found(segid, 4)) {
497 double energy = energies(segid, i) - energies(segid, 4);
498 seg.setEMpoles(type, energy);
499 }
500 }
501 }
502 if (overridden > 0) {
503 std::cout << overridden
504 << " site energies were available both from n-referenced and "
505 "from screened jobs; the screened values were used."
506 << std::endl;
507 }
508}
509
510} // namespace xtp
511} // namespace votca
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
std::string & name()
name of property
Definition property.h:159
std::vector< Property * > Select(const std::string &filter)
select property based on a filter
Definition property.cc:185
void LoadFromXML(std::string filename)
Definition property.cc:238
Framework for threaded execution.
Definition thread.h:35
break string into words
Definition tokenizer.h:72
std::vector< T > ToVector()
store all words in a vector of type T, does type conversion.
Definition tokenizer.h:109
const std::string & getType() const
std::vector< Index > CreateIndexVector(const std::string &Ids) const
std::vector< std::unique_ptr< Region > >::iterator end()
Definition jobtopology.h:63
void ReadFromHdf5(std::string filename)
const std::vector< std::unique_ptr< Region > > & Regions() const
Definition jobtopology.h:67
void WriteToPdb(std::string filename) const
void BuildRegions(const Topology &top, std::pair< std::string, tools::Property > options)
std::vector< std::unique_ptr< Region > >::iterator begin()
Definition jobtopology.h:60
void setError(std::string error)
Definition job.h:67
void setOutput(std::string output)
Definition job.h:50
void setStatus(JobStatus stat)
Definition job.h:49
JobStatus getStatus() const
Definition job.h:59
tools::Property & getInput()
Definition job.h:87
void ToStream(std::ofstream &ofs) const
Definition job.cc:121
std::string getTag() const
Definition job.h:86
Index getId() const
Definition job.h:85
Logger is used for thread-safe output of messages.
Definition logger.h:164
typename std::vector< Job >::value_type Job
std::string which_segments_
Definition qmmm.h:61
Index max_iterations_
Definition qmmm.h:57
bool print_regions_pdb_
Definition qmmm.h:58
void WriteJobFile(const Topology &top)
Definition qmmm.cc:296
std::string getFirstRegionName() const
Definition qmmm.cc:286
bool hasQMRegion() const
Definition qmmm.cc:277
std::vector< QMState > states_
Definition qmmm.h:60
bool hasEnvironmentScreening() const
Definition qmmm.cc:267
Job::JobResult EvalJob(const Topology &top, Job &job, QMThread &Thread)
Definition qmmm.cc:68
void ParseSpecificOptions(const tools::Property &user_options)
Definition qmmm.cc:46
std::pair< std::string, tools::Property > regions_def_
Definition qmmm.h:55
void ReadJobFile(Topology &top)
Definition qmmm.cc:361
bool use_gs_for_ex_
Definition qmmm.h:59
Job createJob(const Segment &seg, const QMState &state, Index jobid) const
Definition qmmm.cc:334
void EvaluateScreenedGWBSE(std::vector< std::unique_ptr< Region > > &regions)
Definition qmregion.cc:311
void AppendScreenedSiteEnergies(tools::Property &prop) const
Definition qmregion.cc:344
bool EnvironmentScreeningEnabled() const
Embedded GW-BSE, screened by the polar regions (see EnvironmentScreening, GWBSE::setScreeningEnvironm...
Definition qmregion.h:109
statetype Type() const
Definition qmstate.h:53
bool isGWState() const
Definition qmstate.h:88
std::string ToString() const
Definition qmstate.cc:35
Identifier for QMstates. Strings like S1 are converted into enum +zero indexed int.
Definition qmstate.h:135
void FromString(const std::string &statestring)
Definition qmstate.cc:212
std::string ToString() const
Definition qmstate.cc:155
const QMStateType & Type() const
Definition qmstate.h:154
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
Container for segments and box and atoms.
Definition topology.h:41
Index getStep() const
Definition topology.h:75
std::vector< Segment > & Segments()
Definition topology.h:58
#define XTP_LOG(level, log)
Definition logger.h:40
const double ev2hrt
Definition constants.h:54
const double hrt2ev
Definition constants.h:53
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