votca 2026-dev
Loading...
Searching...
No Matches
kmcengine.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 <algorithm>
22#include <chrono>
23#include <cmath>
24#include <functional>
25#include <iterator>
26#include <queue>
27#include <stdexcept>
28#include <utility>
29
30// VOTCA includes
32
33// Local VOTCA includes
34#include "votca/xtp/kmcengine.h"
35
36namespace votca {
37namespace xtp {
38
39KMCRandom::KMCRandom(std::uint64_t seed, std::uint64_t stream) {
40 // std::seed_seq and mt19937_64 are fully specified by the standard
41 std::seed_seq seq{
42 std::uint32_t(seed & 0xffffffffu), std::uint32_t(seed >> 32),
43 std::uint32_t(stream & 0xffffffffu), std::uint32_t(stream >> 32)};
44 engine_.seed(seq);
45}
46
48 if (n <= 0) {
49 throw std::runtime_error("KMCRandom::Integer: n must be positive");
50 }
51 const std::uint64_t un = std::uint64_t(n);
52 const std::uint64_t limit = std::numeric_limits<std::uint64_t>::max() -
53 std::numeric_limits<std::uint64_t>::max() % un;
54 std::uint64_t x;
55 do {
56 x = engine_();
57 } while (x >= limit);
58 return Index(x % un);
59}
60
62 double u1 = Uniform();
63 double u2 = Uniform();
64 return std::sqrt(-2.0 * std::log(u1)) * std::cos(2.0 * tools::conv::Pi * u2);
65}
66
68 if (name == "delta") {
70 } else if (name == "gaussian") {
72 } else if (name == "rectangular") {
74 }
75 throw std::runtime_error("KMCEngine: unknown pulse shape '" + name +
76 "', use delta, gaussian or rectangular");
77}
78
79std::vector<double> KMCEngine::InjectionTimes(const std::vector<Pulse>& pulses,
80 Random& random) {
81 std::vector<double> times;
82 for (const Pulse& p : pulses) {
83 if (p.carriers < 0 || p.start < 0.0 || p.width < 0.0) {
84 throw std::runtime_error(
85 "KMCEngine: pulses need start >= 0, width >= 0 and carriers >= 0");
86 }
87 for (Index c = 0; c < p.carriers; ++c) {
88 double t = p.start;
89 if (p.shape == Pulse::Shape::Gaussian && p.width > 0.0) {
90 do {
91 t = p.start + p.width * random.Normal();
92 } while (t < 0.0);
93 } else if (p.shape == Pulse::Shape::Rectangular) {
94 t = p.start + p.width * random.Uniform();
95 }
96 times.push_back(t);
97 }
98 }
99 std::sort(times.begin(), times.end());
100 return times;
101}
102
104 if (decay.rates != nullptr) {
105 if (Index(decay.rates->size()) != graph_.NumberOfSites()) {
106 throw std::runtime_error(
107 "KMCEngine: need one decay rate per site of the graph");
108 }
109 if (!std::all_of(decay.rates->begin(), decay.rates->end(),
110 [](double k) { return k >= 0.0 && std::isfinite(k); })) {
111 throw std::runtime_error(
112 "KMCEngine: decay rates must be finite and not negative");
113 }
114 } else if (decay.replace) {
115 throw std::runtime_error("KMCEngine: replacement needs decay rates");
116 }
117 decay_ = decay;
118}
119
120KMCEngine::RunStatus KMCEngine::Run(const std::vector<double>& injection_times,
121 Random& random, Observer& observer,
122 const Limits& limits) const {
123 if (!std::is_sorted(injection_times.begin(), injection_times.end())) {
124 throw std::runtime_error("KMCEngine: injection times must be sorted");
125 }
126 const double inf = std::numeric_limits<double>::infinity();
127 const Index n = graph_.NumberOfSites();
128 const Index ncarriers = Index(injection_times.size());
129 const std::vector<Index>& sources = graph_.SourceSites();
130 std::vector<Index> occupant(n, -1);
131 // per carrier, indexed by carrier number (replacements add carriers)
132 std::vector<Index> site_of(ncarriers, -1);
133 std::vector<char> decays_next(ncarriers, 0);
134 const std::vector<double>* decay = decay_.rates;
135 // pending candidate event per carrier: (time, carrier), earliest first;
136 // ties broken by carrier index for reproducibility
137 using Pending = std::pair<double, Index>;
138 std::priority_queue<Pending, std::vector<Pending>, std::greater<Pending>>
139 pending;
140 RunStatus status;
141 Index next = 0; // next scheduled injection
142 Index next_carrier = 0;
143 Index alive = 0;
144 double t = 0.0;
145 Index events = 0;
146 const auto wall_start = std::chrono::steady_clock::now();
147 auto wall_exceeded = [&]() {
148 if ((events & 4095) != 0 || limits.max_wall_time == inf) {
149 return false;
150 }
151 std::chrono::duration<double> elapsed =
152 std::chrono::steady_clock::now() - wall_start;
153 return elapsed.count() > limits.max_wall_time;
154 };
155
156 auto schedule = [&](Index carrier) {
157 double tc = strategy_.NextTime(site_of[carrier], t, random);
158 // t + dt can round to t when dt < t * 1e-16 (rare short waiting times in
159 // long runs); an event at the same time is fine, an earlier one is not
160 if (!(tc >= t)) {
161 throw std::runtime_error(
162 "KMCEngine: the waiting-time strategy returned a time before the "
163 "current time");
164 }
165 decays_next[carrier] = 0;
166 if (decay != nullptr) {
167 // competing decay clock, memoryless: redrawn after every event
168 double k = (*decay)[site_of[carrier]];
169 if (k > 0.0) {
170 double td = t + random.Exponential(k);
171 if (td < tc) {
172 tc = td;
173 decays_next[carrier] = 1;
174 }
175 }
176 }
177 if (tc < inf) {
178 pending.emplace(tc, carrier);
179 }
180 };
181
182 auto inject = [&]() {
183 Index carrier = next_carrier++;
184 if (carrier >= Index(site_of.size())) {
185 site_of.resize(carrier + 1, -1);
186 decays_next.resize(carrier + 1, 0);
187 }
188 std::vector<Index> free;
189 free.reserve(sources.size());
190 std::copy_if(sources.begin(), sources.end(), std::back_inserter(free),
191 [&occupant](Index s) { return occupant[s] < 0; });
192 if (free.empty()) {
193 ++status.blocked_injections;
194 return;
195 }
196 Index s = free[random.Integer(Index(free.size()))];
197 occupant[s] = carrier;
198 site_of[carrier] = s;
199 ++alive;
200 ++status.injected;
201 observer.Inject(carrier, s, t);
202 schedule(carrier);
203 };
204
205 while (alive > 0 || next < ncarriers) {
206 double t_inject = (next < ncarriers) ? injection_times[next] : inf;
207 double t_event = pending.empty() ? inf : pending.top().first;
208 if (t_inject <= t_event) {
209 if (t_inject == inf || t_inject > limits.max_time) {
210 status.censored = true; // nobody can move any more, or time limit
211 status.reached_max_time = t_inject != inf;
212 break;
213 }
214 t = t_inject;
215 ++next;
216 inject();
217 continue;
218 }
219 if (t_event > limits.max_time || events >= limits.max_events) {
220 status.censored = true;
221 status.reached_max_time = t_event > limits.max_time;
222 break;
223 }
224 if (wall_exceeded()) {
225 status.censored = true;
226 status.reached_wall_time = true;
227 break;
228 }
229 Index carrier = pending.top().second;
230 pending.pop();
231 t = t_event;
232 ++events;
233 Index s = site_of[carrier];
234 if (decays_next[carrier]) {
235 occupant[s] = -1;
236 site_of[carrier] = -1;
237 --alive;
238 ++status.decayed;
239 observer.Decay(carrier, s, t);
240 if (status.decayed >= limits.max_decays) {
241 status.reached_max_decays = true;
242 break;
243 }
244 if (decay_.replace) {
245 inject();
246 }
247 continue;
248 }
249 Index e = strategy_.ChooseHop(s, t, random);
250 if (e < 0) {
251 ++status.null_events; // rejected by the strategy (thinning)
252 schedule(carrier);
253 continue;
254 }
255 if (e < graph_.EdgeOffset(s) || e >= graph_.EdgeOffset(s + 1)) {
256 throw std::runtime_error(
257 "KMCEngine: the waiting-time strategy chose an edge of another "
258 "site");
259 }
260 const ToFGraph::Edge& edge = graph_.getEdge(e);
261 bool sink = graph_.getSite(edge.to).role == ToFGraph::Role::Sink;
262 if (!sink && occupant[edge.to] >= 0) {
263 ++status.null_events; // blocked: time has advanced, nothing happens
264 schedule(carrier);
265 continue;
266 }
267 occupant[s] = -1;
268 ++status.hops;
269 observer.Hop(carrier, s, edge.to, edge.dR, t);
270 if (sink) {
271 site_of[carrier] = -1;
272 --alive;
273 ++status.absorbed;
274 observer.Absorb(carrier, edge.to, t);
275 } else {
276 occupant[edge.to] = carrier;
277 site_of[carrier] = edge.to;
278 schedule(carrier);
279 }
280 }
281 status.end_time = t;
282 status.events = events;
283 return status;
284}
285
286} // namespace xtp
287} // namespace votca
virtual void Absorb(Index, Index, double)
Definition kmcengine.h:92
virtual void Hop(Index, Index, Index, const Eigen::Vector3d &, double)
Definition kmcengine.h:90
virtual void Decay(Index, Index, double)
Definition kmcengine.h:93
virtual void Inject(Index, Index, double)
Definition kmcengine.h:89
const ToFGraph & graph_
Definition kmcengine.h:153
static std::vector< double > InjectionTimes(const std::vector< Pulse > &pulses, Random &random)
Definition kmcengine.cc:79
RunStatus Run(const std::vector< double > &injection_times, Random &random, Observer &observer, const Limits &limits) const
Definition kmcengine.cc:120
static Pulse::Shape ShapeFromString(const std::string &name)
Definition kmcengine.cc:67
DecaySettings decay_
Definition kmcengine.h:155
void setDecay(const DecaySettings &decay)
per-site decay; rates must be >= 0 and finite, one per site
Definition kmcengine.cc:103
const WaitingTimeStrategy & strategy_
Definition kmcengine.h:154
double Normal()
standard normal (Box-Muller)
Definition kmcengine.cc:61
double Uniform()
uniform in (0,1]
Definition kmcrandom.h:46
KMCRandom(std::uint64_t seed, std::uint64_t stream)
Definition kmcengine.cc:39
std::mt19937_64 engine_
Definition kmcrandom.h:57
double Exponential(double rate)
Exp(rate), i.e. -ln(u)/rate.
Definition kmcrandom.h:50
Index Integer(Index n)
uniform integer in [0, n)
Definition kmcengine.cc:47
const double Pi
Definition constants.h:36
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
const std::vector< double > * rates
Definition kmcengine.h:99
bool replace
inject a new carrier at the time of every decay
Definition kmcengine.h:101
Index max_events
candidate events (hops and null events)
Definition kmcengine.h:108
double max_wall_time
wall-clock time of the run, s (checked every 4096 events)
Definition kmcengine.h:110
Index max_decays
stop after this many decays
Definition kmcengine.h:112
double max_time
simulated time, s
Definition kmcengine.h:106
Index blocked_injections
injections dropped because all source sites were occupied
Definition kmcengine.h:125
Index events
candidate events processed
Definition kmcengine.h:135