votca 2026-dev
Loading...
Searching...
No Matches
kmclifetime.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2026 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 <chrono>
22#include <cmath>
23#include <fstream>
24#include <iomanip>
25
26// VOTCA includes
29
30// Local VOTCA includes
31#include "votca/xtp/eigen.h"
32#include "votca/xtp/kmcrates.h"
36#include "votca/xtp/topology.h"
37
38// Local private VOTCA includes
39#include "kmclifetime.h"
40
41namespace votca {
42namespace xtp {
43
45 carriertype_ = QMStateType(options.get(".carriertype").as<std::string>());
46 if (!carriertype_.isKMCState()) {
47 throw std::runtime_error("kmclifetime cannot be run for state: " +
48 carriertype_.ToLongString());
49 }
50 temperature_ = options.get(".temperature").as<double>() * tools::conv::kB *
52 injection_pattern_ = options.get(".injectionpattern").as<std::string>();
53 ignore_types_ = options.get(".ignoresegments").as<std::string>();
54 if (options.get(".injectionmethod").as<std::string>() != "random") {
55 throw std::runtime_error("kmclifetime: injectionmethod must be random");
56 }
57 lifetimefile_ = options.get(".lifetimefile").as<std::string>();
58 insertions_ = options.get(".numberofinsertions").as<Index>();
59 settings_.seed = options.get(".seed").as<std::uint64_t>();
60 settings_.carriers = options.get(".numberofcarriers").as<Index>();
61 settings_.runs = options.get(".runs").as<Index>();
62 settings_.decays = insertions_;
63 settings_.max_wall_time = options.get(".maxrealtime").as<double>();
64 exact_ = options.get(".exact").as<bool>();
65 waitingtime_ = options.get(".waitingtime").as<std::string>();
66 trajectoryfile_ = options.get(".trajectoryfile").as<std::string>();
67 settings_.keep_records = !trajectoryfile_.empty();
68 ratefile_ = options.get(".ratefile").as<std::string>();
69 occfile_ = options.get(".occfile").as<std::string>();
70 probfile_ = options.get(".decayprobfile").as<std::string>();
71 resultfile_ = options.get(".resultfile").as<std::string>();
72
73 const tools::Property& energy = options.get(".carrierenergy");
74 carrierenergy_ = energy.get("run").as<bool>();
75 energyfile_ = energy.get("outputfile").as<std::string>();
76 alpha_ = energy.get("alpha").as<double>();
77 if (carrierenergy_) {
78 settings_.energy_hops = energy.get("outputsteps").as<Index>();
79 }
80 log_.setReportLevel(Log::current_level);
81 log_.setMultithreading(true);
82 log_.setCommonPreface("\n ...");
83}
84
85std::vector<double> KMCLifetime::ReadDecayRates(const ToFGraph& graph) const {
88 std::vector<tools::Property*> sites = xml.Select("lifetimes.site");
89 const Index n = graph.NumberOfSites();
90 std::vector<double> rates(n, -1.0);
91 for (tools::Property* prop : sites) {
92 Index id = prop->getAttribute<Index>("id");
93 if (id < 0 || id >= n) {
94 throw std::runtime_error("kmclifetime: site " + std::to_string(id) +
95 " in " + lifetimefile_ +
96 " is not in the state file");
97 }
98 if (rates[id] >= 0.0) {
99 throw std::runtime_error("kmclifetime: site " + std::to_string(id) +
100 " appears twice in " + lifetimefile_);
101 }
102 double lifetime = prop->as<double>();
103 if (!(lifetime > 0.0) || !std::isfinite(lifetime)) {
104 throw std::runtime_error("kmclifetime: the lifetime of site " +
105 std::to_string(id) + " must be positive");
106 }
107 rates[id] = 1.0 / lifetime;
108 }
109 for (Index i = 0; i < n; ++i) {
110 if (rates[i] < 0.0) {
111 if (!graph.getSite(i).ignored) {
112 throw std::runtime_error("kmclifetime: no lifetime for site " +
113 std::to_string(i) + " in " + lifetimefile_);
114 }
115 rates[i] = 0.0;
116 }
117 }
118 return rates;
119}
120
122 XTP_LOG(Log::error, log_) << "\n-----------------------------------"
123 "\n KMC WITH FINITE LIFETIMES"
124 "\n-----------------------------------"
125 << std::flush;
127 carriertype_, temperature_, Eigen::Vector3d::Zero());
128 XTP_LOG(Log::error, log_) << graph.Summary() << std::flush;
129 std::vector<double> decay = ReadDecayRates(graph);
130 if (!ratefile_.empty()) {
131 WriteRates(top);
132 }
133 double nm = tools::conv::bohr2nm;
134
135 std::unique_ptr<LifetimeSimulation::ExactResult> exact;
136 if (exact_) {
137 exact = std::make_unique<LifetimeSimulation::ExactResult>(
138 LifetimeSimulation::Exact(graph, decay));
139 XTP_LOG(Log::error, log_) << "Exact for one " << carriertype_.ToLongString()
140 << " (" << exact->solver << ", backward error "
141 << exact->backward_error << "):" << std::flush;
143 << std::scientific << " lifetime " << exact->lifetime
144 << " s, diffusion length sqrt(<R^2>) "
145 << std::sqrt(exact->squared_displacement) * nm
146 << " nm, per direction sqrt(<R_a^2>) "
147 << (exact->squared_components.cwiseSqrt() * nm).transpose() << " nm"
148 << std::flush;
149 if (!exact->converged) {
151 << "Warning: the iterative solver did not converge" << std::flush;
152 }
153 if (settings_.carriers > 1) {
155 << " (with " << settings_.carriers
156 << " carriers the KMC includes site exclusion, the exact values "
157 "do not)"
158 << std::flush;
159 }
160 }
161
162 std::unique_ptr<LifetimeSimulation::Result> result;
163 if (insertions_ > 0) {
164 StaticRates rates(graph);
165 std::unique_ptr<WaitingTimeStrategy> strategy =
167 LifetimeSimulation simulation(graph, decay, settings_, rates, *strategy);
169 << settings_.carriers << " " << carriertype_.ToLongString()
170 << "(s) at a time, " << insertions_ << " decays in " << settings_.runs
171 << " run(s), " << OPENMP::getMaxThreads() << " thread(s), waiting "
172 << "times: " << strategy->Name() << std::flush;
173 auto start = std::chrono::steady_clock::now();
174 result =
175 std::make_unique<LifetimeSimulation::Result>(simulation.Simulate());
176 double seconds =
177 std::chrono::duration<double>(std::chrono::steady_clock::now() - start)
178 .count();
179 const LifetimeSimulation::Result& r = *result;
181 << std::setprecision(6) << r.decays << " decays, " << r.hop_events
182 << " hops, " << r.null_events << " attempts into occupied sites, "
183 << r.simulated_time << " s simulated, " << seconds << " s"
184 << std::flush;
185 if (r.wall_time_stops > 0) {
187 << "Warning: " << r.wall_time_stops
188 << " run(s) stopped by maxrealtime" << std::flush;
189 }
190 double LD = std::sqrt(r.squared_displacement);
192 << std::scientific << "Average lifetime: " << r.lifetime << " +- "
193 << r.lifetime_error << " s (standard error)\n"
194 << " hops per " << carriertype_.ToLongString() << ": " << r.hops
195 << "\n mean distance <|R|>: " << r.mean_distance * nm << " +- "
196 << r.mean_distance_error * nm << " nm"
197 << "\n diffusion length sqrt(<R^2>): " << LD * nm << " +- "
198 << r.squared_displacement_error / (2.0 * LD) * nm << " nm"
199 << "\n per direction sqrt(<R_a^2>): "
200 << (r.squared_components.cwiseSqrt() * nm).transpose() << " nm"
201 << "\n D = <R^2>/(6 <lifetime>): "
202 << r.squared_displacement / (6.0 * r.lifetime) * nm * nm << " nm^2/s"
203 << std::flush;
204 if (exact && settings_.carriers == 1) {
206 << " KMC - exact: lifetime "
207 << (r.lifetime - exact->lifetime) / r.lifetime_error
208 << " standard errors, <R^2> "
209 << (r.squared_displacement - exact->squared_displacement) /
211 << " standard errors" << std::flush;
212 }
213
214 if (!trajectoryfile_.empty()) {
215 std::ofstream out(trajectoryfile_);
216 out << "#Simtime [s]\t Insertion\t Carrier ID\t Lifetime[s]\tSteps\t "
217 "Decay segment\t x_travelled[nm]\t y_travelled[nm]\t "
218 "z_travelled[nm]\t run\n";
219 out << std::setprecision(10);
220 for (Index i = 0; i < Index(r.records.size()); ++i) {
221 const LifetimeSimulation::Record& d = r.records[i];
222 out << d.time << "\t" << i << "\t" << d.carrier << "\t" << d.lifetime
223 << "\t" << d.hops << "\t" << d.site << "\t"
224 << d.displacement.x() * nm << "\t" << d.displacement.y() * nm
225 << "\t" << d.displacement.z() * nm << "\t" << d.run << "\n";
226 }
228 << "Decays written to " << trajectoryfile_ << std::flush;
229 }
230 if (carrierenergy_) {
231 std::ofstream out(energyfile_);
232 out << "#Simtime [s]\tHops\tEnergy[eV]\tEnergy_a=" << alpha_
233 << "[eV] (run 0, mean over the carriers present)\n";
234 double avg = r.energy.empty() ? 0.0 : r.energy.front().energy;
235 for (const LifetimeSimulation::EnergySample& e : r.energy) {
236 avg = alpha_ * e.energy + (1.0 - alpha_) * avg;
237 out << e.time << "\t" << e.hops << "\t"
238 << e.energy * tools::conv::hrt2ev << "\t"
239 << avg * tools::conv::hrt2ev << "\n";
240 }
241 }
242 if (!occfile_.empty()) {
243 std::ofstream out(occfile_);
244 out << "#SiteID, Occupation prob at "
246 << "K for carrier:" << carriertype_.ToString() << "\n";
247 for (Index i = 0; i < graph.NumberOfSites(); ++i) {
248 out << i << "\t" << r.occupation[i] << "\n";
249 }
250 }
251 }
252 if (!probfile_.empty()) {
253 WriteDecayProbabilities(graph, decay, result.get(), exact.get());
254 }
255 if (!resultfile_.empty()) {
256 ResultFile out("kmclifetime: kinetic Monte Carlo with finite lifetimes");
257 const double to_m = nm * 1e-9;
258 const char* axes = "xyz";
259 out.Add("carriers", settings_.carriers);
260 if (result) {
261 const LifetimeSimulation::Result& r = *result;
262 out.Add("decays", r.decays);
263 out.Add("hop_events", r.hop_events);
264 out.Add("null_events", r.null_events);
265 out.Add("lifetime", r.lifetime, "s");
266 out.Add("lifetime_error", r.lifetime_error, "s");
267 out.Add("hops_per_carrier", r.hops, "-");
268 out.Add("mean_distance", r.mean_distance * to_m, "m");
269 out.Add("mean_distance_error", r.mean_distance_error * to_m, "m");
270 out.Add("msd", r.squared_displacement * to_m * to_m, "m^2");
271 out.Add("msd_error", r.squared_displacement_error * to_m * to_m, "m^2");
272 for (Index a = 0; a < 3; ++a) {
273 out.Add(std::string("msd_") + axes[a],
274 r.squared_components[a] * to_m * to_m, "m^2");
275 }
276 out.Add("diffusion_length", std::sqrt(r.squared_displacement) * to_m,
277 "m");
278 out.Add("diffusion_coefficient",
279 r.squared_displacement / (6.0 * r.lifetime) * to_m * to_m,
280 "m^2/s");
281 }
282 out.Add("exact_available", Index(exact != nullptr));
283 if (exact) {
284 out.Add("exact_lifetime", exact->lifetime, "s");
285 out.Add("exact_msd", exact->squared_displacement * to_m * to_m, "m^2");
286 for (Index a = 0; a < 3; ++a) {
287 out.Add(std::string("exact_msd_") + axes[a],
288 exact->squared_components[a] * to_m * to_m, "m^2");
289 }
290 out.Add("exact_diffusion_length",
291 std::sqrt(exact->squared_displacement) * to_m, "m");
292 out.Add("exact_backward_error", exact->backward_error, "-");
293 }
294 out.Write(resultfile_);
295 }
296 std::cout << log_;
297 return true;
298}
299
301 std::ofstream out(ratefile_);
302 out << "#PairID,SiteID1,SiteID2, ,rate12[1/s],rate21[1/s] at "
304 << "K for carrier:" << carriertype_.ToString() << "\n";
305 Rate_Engine engine(temperature_, Eigen::Vector3d::Zero());
306 for (const QMPair* pair : top.NBList()) {
307 Rate_Engine::PairRates rates = engine.Rate(*pair, carriertype_);
308 out << pair->getId() << " " << pair->Seg1()->getId() << " "
309 << pair->Seg2()->getId() << " " << rates.rate12 << " " << rates.rate21
310 << "\n";
311 }
312 XTP_LOG(Log::error, log_) << "Rates written to " << ratefile_ << std::flush;
313}
314
316 const ToFGraph& graph, const std::vector<double>& decay,
318 const LifetimeSimulation::ExactResult* exact) {
319 const Index n = graph.NumberOfSites();
320 Eigen::VectorXd in = Eigen::VectorXd::Zero(n);
321 for (Index i = 0; i < n; ++i) {
322 for (const ToFGraph::Edge& e : graph.OutEdges(i)) {
323 in[e.to] += e.rate;
324 }
325 }
326 std::ofstream out(probfile_);
327 out << "#SiteID, k/(k+outgoing rates), k/(k+incoming rates), fraction of "
328 "the KMC decays, exact decay probability (one carrier)\n";
329 for (Index i = 0; i < n; ++i) {
330 double k = decay[i];
331 double pout = (k > 0.0) ? k / (k + graph.EscapeRate(i)) : 0.0;
332 double pin = (k > 0.0) ? k / (k + in[i]) : 0.0;
333 out << i << " " << pout << " " << pin << " "
334 << (kmc != nullptr ? kmc->decay_fraction[i] : 0.0) << " "
335 << (exact != nullptr ? exact->decay_probability[i] : 0.0) << "\n";
336 }
338 << "Decay probabilities written to " << probfile_ << std::flush;
339}
340
341} // namespace xtp
342} // namespace votca
class to manage program options with xml serialization functionality
Definition property.h:55
Property & get(const std::string &key)
get existing property
Definition property.cc:79
T as() const
return value as type
Definition property.h:283
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
std::string trajectoryfile_
Definition kmclifetime.h:70
void WriteDecayProbabilities(const ToFGraph &graph, const std::vector< double > &decay, const LifetimeSimulation::Result *kmc, const LifetimeSimulation::ExactResult *exact)
std::vector< double > ReadDecayRates(const ToFGraph &graph) const
std::string lifetimefile_
Definition kmclifetime.h:69
void WriteRates(const Topology &top)
std::string injection_pattern_
Definition kmclifetime.h:61
std::string ignore_types_
Definition kmclifetime.h:62
bool Evaluate(Topology &top) override
LifetimeSimulation::Settings settings_
Definition kmclifetime.h:63
void ParseOptions(const tools::Property &options) override
Kinetic Monte Carlo of carriers (excitons) that hop and decay with a site-dependent rate,...
static ExactResult Exact(const ToFGraph &graph, const std::vector< double > &decay_rates, Index direct_limit=20000, double tolerance=1e-12)
PairRates Rate(const QMPair &pair, QMStateType carriertype) const
Small machine-readable result file of a calculator: one "key value unit" line per quantity (unit "-" ...
Definition resultfile.h:42
void Write(const std::string &filename) const
Definition resultfile.h:59
void Add(const std::string &key, double value, const std::string &unit)
Definition resultfile.h:49
the rates stored in the ToFGraph, constant in time
Definition kmcrates.h:80
Hopping graph for time-of-flight (ToF) transport.
Definition tofgraph.h:77
const Site & getSite(Index i) const
Definition tofgraph.h:151
Index NumberOfSites() const
Definition tofgraph.h:149
static Options BulkOptions(const std::string &injection_pattern, const std::string &ignore_types)
Definition tofgraph.cc:61
EdgeRange OutEdges(Index i) const
Definition tofgraph.h:153
double EscapeRate(Index i) const
sum of the rates of all out-edges
Definition tofgraph.h:163
std::string Summary() const
human readable summary
Definition tofgraph.cc:360
Container for segments and box and atoms.
Definition topology.h:41
QMNBList & NBList()
Definition topology.h:70
#define XTP_LOG(level, log)
Definition logger.h:40
const double ev2hrt
Definition constants.h:54
const double kB
Definition constants.h:40
const double bohr2nm
Definition constants.h:46
const double hrt2ev
Definition constants.h:53
Index getMaxThreads()
Definition eigen.h:145
Charge transport classes.
Definition ERIs.h:28
std::unique_ptr< WaitingTimeStrategy > MakeWaitingTimeStrategy(const std::string &name)
strategy by name: exponential, thinning, inversion
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
static Level current_level
Definition globals.h:30
Eigen::VectorXd occupation
occupation probability per site (sum: number of carriers)
Eigen::VectorXd decay_fraction
fraction of the decays per site
bool ignored
segment type in Options::ignore_types, no edges
Definition tofgraph.h:122