votca 2026-dev
Loading...
Searching...
No Matches
kmcmultiple.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 "kmcmultiple.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("kmcmultiple cannot be run for state: " +
48 carriertype_.ToLongString());
49 }
50 temperature_ = options.get(".temperature").as<double>() * tools::conv::kB *
52 field_Vm_ = options.get(".field").as<Eigen::Vector3d>();
54 injection_pattern_ = options.get(".injectionpattern").as<std::string>();
55 ignore_types_ = options.get(".ignoresegments").as<std::string>();
56 std::string method = options.get(".injectionmethod").as<std::string>();
57 if (method != "random") {
58 throw std::runtime_error("kmcmultiple: injectionmethod must be random");
59 }
60
61 settings_.seed = options.get(".seed").as<std::uint64_t>();
62 settings_.carriers = options.get(".numberofcarriers").as<Index>();
63 settings_.runs = options.get(".runs").as<Index>();
64 settings_.blocks = options.get(".blocks").as<Index>();
65 settings_.equilibration = options.get(".equilibration").as<double>();
66 settings_.limits.max_wall_time = options.get(".maxrealtime").as<double>();
67
68 runtime_ = options.get(".runtime").as<double>();
69 outputtime_ = options.get(".outputtime").as<double>();
70 steps_ = runtime_ > 100.0;
71 if (steps_) {
72 if (outputtime_ != std::floor(outputtime_)) {
73 throw std::runtime_error(
74 "kmcmultiple: runtime is a number of events (> 100), so outputtime "
75 "must be an integer number of hops too");
76 }
77 settings_.limits.max_events = Index(runtime_);
78 settings_.sample_hops = Index(outputtime_);
79 } else {
80 if (!(runtime_ > 0.0)) {
81 throw std::runtime_error("kmcmultiple: runtime must be positive");
82 }
83 settings_.limits.max_time = runtime_;
84 settings_.sample_interval = outputtime_;
85 }
86
87 waitingtime_ = options.get(".waitingtime").as<std::string>();
88 trajectoryfile_ = options.get(".trajectoryfile").as<std::string>();
89 timefile_ = options.get(".timefile").as<std::string>();
90 ratefile_ = options.get(".ratefile").as<std::string>();
91 occfile_ = options.get(".occfile").as<std::string>();
92 resultfile_ = options.get(".resultfile").as<std::string>();
93 exact_ = options.get(".exact").as<bool>();
94 if (outputtime_ == 0.0) {
95 trajectoryfile_.clear();
96 timefile_.clear();
97 }
98 log_.setReportLevel(Log::current_level);
99 log_.setMultithreading(true);
100 log_.setCommonPreface("\n ...");
101}
102
103double KMCMultiple::Charge() const {
105 return -1.0;
106 } else if (carriertype_ == QMStateType::Hole) {
107 return 1.0;
108 }
109 return 0.0;
110}
111
113 XTP_LOG(Log::error, log_) << "\n-----------------------------------"
114 "\n KMC FOR MULTIPLE CHARGES"
115 "\n-----------------------------------"
116 << std::flush;
119 ToFGraph graph(top, opt, carriertype_, temperature_, field_);
120 XTP_LOG(Log::error, log_) << graph.Summary() << std::flush;
121 if (!ratefile_.empty()) {
122 WriteRates(top);
123 }
124
125 double nm = tools::conv::bohr2nm;
126 double volume = top.BoxVolume() * nm * nm * nm;
128 << settings_.carriers << " " << carriertype_.ToLongString()
129 << "(s), density " << double(settings_.carriers) / volume << " nm^-3, "
130 << settings_.runs << " run(s), " << OPENMP::getMaxThreads()
131 << " thread(s)" << std::flush;
132 if (steps_) {
134 << "Each run stops after " << settings_.limits.max_events
135 << " KMC events (hops and attempts into occupied sites)" << std::flush;
136 } else {
137 XTP_LOG(Log::error, log_) << "Each run stops at " << runtime_
138 << " s simulated time" << std::flush;
139 }
140
141 StaticRates rates(graph);
142 std::unique_ptr<WaitingTimeStrategy> strategy =
144 BulkSimulation simulation(graph, settings_, rates, *strategy);
146 << "Waiting times: " << strategy->Name() << std::flush;
147 auto start = std::chrono::steady_clock::now();
148 BulkSimulation::Result result = simulation.Simulate();
149 double seconds =
150 std::chrono::duration<double>(std::chrono::steady_clock::now() - start)
151 .count();
152
153 double attempts = double(result.hops + result.null_events);
155 << std::setprecision(6) << result.hops << " hops, " << result.null_events
156 << " attempts into occupied sites ("
157 << 100.0 * double(result.null_events) / std::max(attempts, 1.0) << " %), "
158 << seconds << " s" << std::flush;
159 if (result.wall_time_stops > 0) {
160 XTP_LOG(Log::error, log_) << "Warning: " << result.wall_time_stops
161 << " run(s) stopped by maxrealtime" << std::flush;
162 }
163 const BulkSimulation::RunData& run0 = result.runs.front();
165 << "Observation window of run 0: " << run0.start << " s to " << run0.end
166 << " s, " << run0.blocks << " blocks of " << run0.block_length << " s"
167 << std::flush;
168
169 std::string error_kind = settings_.runs > 1
170 ? "standard error over runs"
171 : "standard error from the time blocks";
173 << std::scientific
174 << "Drift velocity (nm/s): " << (result.velocity * nm).transpose()
175 << "\n +- " << (result.velocity_error * nm).transpose() << " ("
176 << error_kind << ")" << std::flush;
177
178 // mobility along the field, mu = q v.F / |F|^2 (positive if the carriers
179 // drift along the force); m^2/Vs
180 const double F = field_Vm_.norm();
181 const double to_m = nm * 1e-9;
182 const bool driven = F > 0.0 && Charge() != 0.0;
183 const Eigen::Vector3d dir =
184 F > 0.0 ? Eigen::Vector3d(field_Vm_ / F) : Eigen::Vector3d::Zero();
185 auto mobility = [&](const Eigen::Vector3d& v) {
186 return Charge() * v.dot(dir) * to_m / F;
187 };
188 double mu = 0.0;
189 double mu_err = 0.0;
190 if (driven) {
191 mu = mobility(result.velocity);
192 mu_err =
193 std::sqrt((result.velocity_error.cwiseProduct(dir)).squaredNorm()) *
194 to_m / F;
196 << "Mobility along the field: " << mu * 1e4 << " +- " << mu_err * 1e4
197 << " cm^2/Vs (" << mu * 1e18 << " nm^2/Vs)" << std::flush;
198 }
199
200 Eigen::Matrix3d D = result.diffusion * nm * nm;
201 XTP_LOG(Log::error, log_) << "Diffusion tensor (nm^2/s):\n"
202 << D << std::flush;
203 if (settings_.runs > 1) {
204 XTP_LOG(Log::error, log_) << " +- (standard error over runs)\n"
205 << result.diffusion_error * nm * nm << std::flush;
206 }
207 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es(D);
208 for (Index i = 0; i < 3; ++i) {
210 << " eigenvalue " << es.eigenvalues()(i) << " nm^2/s, direction "
211 << es.eigenvectors().col(i).transpose() << std::flush;
212 }
213 const double kT = temperature_ * tools::conv::hrt2ev; // eV
214 auto einstein = [kT](const Eigen::Matrix3d& Dnm) {
215 return Dnm.trace() / 3.0 * 1e-18 / kT; // m^2/Vs
216 };
217 if (F == 0.0) {
219 << "Einstein mobility D/kT from the mean eigenvalue (isotropic "
220 "medium, nondegenerate carriers): "
221 << einstein(D) * 1e4 << " cm^2/Vs (" << einstein(D) * 1e18
222 << " nm^2/Vs)" << std::flush;
223 }
224
226 if (exact_) {
227 exact = BulkSimulation::Exact(graph);
228 if (!exact.available) {
230 << "Exact single-carrier values not available: " << exact.message
231 << std::flush;
232 } else {
233 Eigen::Matrix3d Dx = exact.diffusion * nm * nm;
235 << "Exact for one carrier (master equation, " << exact.solver << ", "
236 << exact.sites << " sites, backward error " << exact.backward_error
237 << "):\n drift velocity (nm/s): "
238 << (exact.velocity * nm).transpose() << "\n diffusion tensor "
239 << "(nm^2/s):\n"
240 << Dx << std::flush;
241 if (driven) {
243 << " mobility along the field: " << mobility(exact.velocity) * 1e4
244 << " cm^2/Vs" << std::flush;
245 } else if (F == 0.0) {
247 << " Einstein mobility: " << einstein(Dx) * 1e4 << " cm^2/Vs"
248 << std::flush;
249 }
250 if (!exact.converged) {
252 << " Warning: the linear solver did not converge" << std::flush;
253 }
254 if (settings_.carriers > 1) {
256 << " (with " << settings_.carriers
257 << " carriers the KMC includes site exclusion, the exact values "
258 "are for an isolated carrier)"
259 << std::flush;
260 } else if (driven && mu_err > 0.0) {
261 XTP_LOG(Log::error, log_) << " KMC - exact mobility: "
262 << (mu - mobility(exact.velocity)) / mu_err
263 << " standard errors" << std::flush;
264 }
265 }
266 }
267
268 if (!resultfile_.empty()) {
269 ResultFile out("kmcmultiple: bulk kinetic Monte Carlo");
270 out.Add("carriers", settings_.carriers);
271 out.Add("runs", settings_.runs);
272 out.Add("hops", result.hops);
273 out.Add("null_events", result.null_events);
274 out.Add("window_start", run0.start, "s");
275 out.Add("window_end", run0.end, "s");
276 const char* axes = "xyz";
277 for (Index a = 0; a < 3; ++a) {
278 out.Add(std::string("velocity_") + axes[a], result.velocity[a] * to_m,
279 "m/s");
280 out.Add(std::string("velocity_error_") + axes[a],
281 result.velocity_error[a] * to_m, "m/s");
282 }
283 if (driven) {
284 out.Add("mobility", mu * 1e4, "cm^2/Vs");
285 out.Add("mobility_error", mu_err * 1e4, "cm^2/Vs");
286 }
287 auto tensor = [&out, axes](const std::string& name,
288 const Eigen::Matrix3d& T) {
289 for (Index a = 0; a < 3; ++a) {
290 for (Index b = a; b < 3; ++b) {
291 out.Add(name + "_" + axes[a] + axes[b], T(a, b) * 1e-18, "m^2/s");
292 }
293 }
294 };
295 tensor("D", D);
296 if (settings_.runs > 1) {
297 tensor("D_error", result.diffusion_error * nm * nm);
298 }
299 if (F == 0.0) {
300 out.Add("einstein_mobility", einstein(D) * 1e4, "cm^2/Vs");
301 }
302 out.Add("exact_available", Index(exact.available));
303 if (exact.available) {
304 for (Index a = 0; a < 3; ++a) {
305 out.Add(std::string("exact_velocity_") + axes[a],
306 exact.velocity[a] * to_m, "m/s");
307 }
308 if (driven) {
309 out.Add("exact_mobility", mobility(exact.velocity) * 1e4, "cm^2/Vs");
310 }
311 Eigen::Matrix3d Dx = exact.diffusion * nm * nm;
312 tensor("exact_D", Dx);
313 if (F == 0.0) {
314 out.Add("exact_einstein_mobility", einstein(Dx) * 1e4, "cm^2/Vs");
315 }
316 }
317 out.Write(resultfile_);
318 }
319
320 if (!occfile_.empty()) {
321 WriteOccupation(graph, result);
322 }
323 if (!trajectoryfile_.empty()) {
324 WriteTrajectory(result);
325 }
326 if (!timefile_.empty()) {
327 WriteTimeFile(result);
328 }
329 std::cout << log_;
330 return true;
331}
332
334 std::ofstream out(ratefile_);
335 out << "#PairID,SiteID1,SiteID2, ,rate12[1/s],rate21[1/s] at "
337 << "K for carrier:" << carriertype_.ToString() << "\n";
339 for (const QMPair* pair : top.NBList()) {
340 Rate_Engine::PairRates rates = engine.Rate(*pair, carriertype_);
341 out << pair->getId() << " " << pair->Seg1()->getId() << " "
342 << pair->Seg2()->getId() << " " << rates.rate12 << " " << rates.rate21
343 << "\n";
344 }
345 XTP_LOG(Log::error, log_) << "Rates written to " << ratefile_ << std::flush;
346}
347
349 const BulkSimulation::Result& result) {
350 std::ofstream out(occfile_);
351 out << "#SiteID, Occupation prob at "
353 << "K for carrier:" << carriertype_.ToString() << "\n";
354 for (Index i = 0; i < graph.NumberOfSites(); ++i) {
355 out << i << "\t" << result.occupation[i] << "\n";
356 }
358 << "Occupation probabilities written to " << occfile_ << std::flush;
359}
360
362 std::ofstream out(trajectoryfile_);
363 out << "time[s]\tsteps";
364 for (Index c = 0; c < settings_.carriers; ++c) {
365 out << "\tcarrier" << c + 1 << " x\tcarrier" << c + 1 << " y\tcarrier"
366 << c + 1 << " z";
367 }
368 out << "\n";
369 double nm = tools::conv::bohr2nm;
370 for (const BulkSimulation::Sample& s : result.trajectory) {
371 out << s.time << "\t" << s.hops;
372 for (const Eigen::Vector3d& r : s.positions) {
373 out << "\t" << r.x() * nm << "\t" << r.y() * nm << "\t" << r.z() * nm;
374 }
375 out << "\n";
376 }
378 << "Trajectory of run 0 written to " << trajectoryfile_ << std::flush;
379}
380
382 std::ofstream out(timefile_);
383 out << "time[s]\tsteps\tenergy_per_carrier[eV]\tmobility[nm**2/Vs]\t"
384 "distance_fielddirection[nm]\tdistance_absolute[nm]\n";
385 if (result.trajectory.empty()) {
386 return;
387 }
388 double nm = tools::conv::bohr2nm;
389 double F = field_Vm_.norm();
390 const std::vector<Eigen::Vector3d>& r0 = result.trajectory.front().positions;
391 double t0 = result.trajectory.front().time;
392 for (const BulkSimulation::Sample& s : result.trajectory) {
393 Eigen::Vector3d dr = Eigen::Vector3d::Zero();
394 for (Index c = 0; c < Index(s.positions.size()); ++c) {
395 dr += s.positions[c] - r0[c];
396 }
397 dr *= nm / double(s.positions.size()); // nm, mean per carrier
398 double along = (F > 0.0) ? dr.dot(field_Vm_) / F : 0.0;
399 double mobility = 0.0;
400 if (F > 0.0 && s.time > t0) {
401 // nm/s over V/nm
402 mobility = Charge() * along / (s.time - t0) / (F * 1e-9);
403 }
404 out << s.time << "\t" << s.hops << "\t" << s.energy * tools::conv::hrt2ev
405 << "\t" << mobility << "\t" << along << "\t" << dr.norm() << "\n";
406 }
407}
408
409} // namespace xtp
410} // 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
Kinetic Monte Carlo of a fixed number of carriers with site exclusion in a periodic box (bulk transpo...
static ExactResult Exact(const ToFGraph &graph, Index direct_limit=20000, double tolerance=1e-12)
direct sparse LU up to direct_limit sites, BiCGSTAB with ILUT above
void WriteOccupation(const ToFGraph &graph, const BulkSimulation::Result &result)
void WriteRates(const Topology &top)
BulkSimulation::Settings settings_
Definition kmcmultiple.h:68
std::string trajectoryfile_
Definition kmcmultiple.h:73
std::string ignore_types_
Definition kmcmultiple.h:67
Eigen::Vector3d field_
Definition kmcmultiple.h:64
Eigen::Vector3d field_Vm_
Definition kmcmultiple.h:65
std::string injection_pattern_
Definition kmcmultiple.h:66
void ParseOptions(const tools::Property &options) override
void WriteTrajectory(const BulkSimulation::Result &result)
void WriteTimeFile(const BulkSimulation::Result &result)
bool Evaluate(Topology &top) override
double Charge() const
sign of the carrier charge (0 for neutral excitations)
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
Index NumberOfSites() const
Definition tofgraph.h:149
static Options BulkOptions(const std::string &injection_pattern, const std::string &ignore_types)
Definition tofgraph.cc:61
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
double BoxVolume() const
Definition topology.h:65
#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
const double nm2bohr
Definition constants.h:47
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::Matrix3d diffusion_error
standard error over runs, zero for one run
Eigen::VectorXd occupation
occupation probability per site, averaged over runs
double start
observation window [start, end]
state of run 0 at a sampling time