votca 2026-dev
Loading...
Searching...
No Matches
tofsimulation.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 <cmath>
23#include <functional>
24#include <numeric>
25#include <stdexcept>
26
27// Local VOTCA includes
28#include "votca/xtp/eigen.h"
30
31namespace votca {
32namespace xtp {
33
34namespace {
35class RunObserver : public KMCEngine::Observer {
36 public:
37 RunObserver(const Eigen::Vector3d& normal, Index carriers,
38 std::function<void(double, double)> displacement,
39 std::function<void(double)> arrival)
40 : normal_(normal),
41 inject_time_(carriers, 0.0),
42 displacement_(std::move(displacement)),
43 arrival_(std::move(arrival)) {}
44 void Inject(Index carrier, Index, double t) override {
45 inject_time_[carrier] = t;
46 }
47 void Hop(Index, Index, Index, const Eigen::Vector3d& dR, double t) override {
48 displacement_(t, dR.dot(normal_));
49 }
50 void Absorb(Index carrier, Index, double t) override {
51 if (absorbed_ == 0) {
52 first_ = t;
53 }
54 ++absorbed_;
55 transit_sum_ += t - inject_time_[carrier];
56 arrival_(t);
57 }
58 double First() const { return first_; }
59 double MeanTransit() const {
60 return absorbed_ > 0 ? transit_sum_ / double(absorbed_) : 0.0;
61 }
62
63 private:
64 Eigen::Vector3d normal_;
65 std::vector<double> inject_time_;
66 std::function<void(double, double)> displacement_;
67 std::function<void(double)> arrival_;
68 Index absorbed_ = 0;
69 double first_ = 0.0;
70 double transit_sum_ = 0.0;
71};
72} // namespace
73
74ToFSimulation::ToFSimulation(const ToFGraph& graph, const Settings& settings)
75 : graph_(graph),
76 settings_(settings),
77 own_rates_(std::make_unique<StaticRates>(graph)),
79 own_strategy_->Prepare(*own_rates_);
80 engine_ = std::make_unique<KMCEngine>(graph_, *own_strategy_);
82 Setup();
83}
84
85ToFSimulation::ToFSimulation(const ToFGraph& graph, const Settings& settings,
86 const TimeDependentRates& rates,
87 WaitingTimeStrategy& strategy)
88 : graph_(graph), settings_(settings) {
89 if (&rates.Graph() != &graph_) {
90 throw std::runtime_error(
91 "ToFSimulation: the rates belong to a different graph");
92 }
93 strategy.Prepare(rates);
94 engine_ = std::make_unique<KMCEngine>(graph_, strategy);
95 strategy_name_ = strategy.Name();
96 Setup();
97}
98
100 if (graph_.isBulk()) {
101 throw std::runtime_error(
102 "ToFSimulation: needs a ToF graph with sinks, not a bulk graph without "
103 "sinks");
104 }
105
106 if (settings_.runs < 1) {
107 throw std::runtime_error("ToFSimulation: runs must be positive");
108 }
109 if (!(settings_.tmin > 0.0) || !(settings_.tmax > settings_.tmin) ||
110 settings_.bins_per_decade < 1) {
111 throw std::runtime_error(
112 "ToFSimulation: need 0 < tmin < tmax and bins_per_decade >= 1");
113 }
114 Index bins = Index(std::ceil(double(settings_.bins_per_decade) *
115 std::log10(settings_.tmax / settings_.tmin)));
116 bins = std::max<Index>(bins, 1);
117 edges_.resize(bins + 1);
118 for (Index b = 0; b <= bins; ++b) {
119 edges_[b] = settings_.tmin *
120 std::pow(10.0, double(b) / double(settings_.bins_per_decade));
121 }
122}
123
124Index ToFSimulation::Bin(double t) const {
125 if (t < edges_.front()) {
126 return -1;
127 }
128 if (t >= edges_.back()) {
129 return Index(edges_.size()) - 1; // overflow
130 }
131 return Index(std::upper_bound(edges_.begin(), edges_.end(), t) -
132 edges_.begin()) -
133 1;
134}
135
137 std::vector<double>& current,
138 std::vector<double>& arrivals,
139 double& before,
140 double& after) const {
141 // two streams per run: the injection times and the dynamics
142 KMCEngine::Random pulse_random(settings_.seed, 2 * std::uint64_t(run));
143 KMCEngine::Random random(settings_.seed, 2 * std::uint64_t(run) + 1);
144 std::vector<double> times =
145 KMCEngine::InjectionTimes(settings_.pulses, pulse_random);
146 const Index nbins = Index(edges_.size()) - 1;
147 RunObserver observer(
148 graph_.Normal(), Index(times.size()),
149 [&](double t, double dx) {
150 Index b = Bin(t);
151 if (b < 0) {
152 before += dx;
153 } else if (b >= nbins) {
154 after += dx;
155 } else {
156 current[b] += dx;
157 }
158 },
159 [&](double t) {
160 Index b = Bin(t);
161 if (b < nbins) {
162 arrivals[std::max<Index>(b, 0)] += 1.0;
163 }
164 });
165 RunData data;
166 data.status = engine_->Run(times, random, observer, settings_.limits);
167 data.first_arrival = observer.First();
168 data.mean_transit = observer.MeanTransit();
169 return data;
170}
171
173 Statistic s;
174 s.samples = Index(samples.size());
175 if (samples.empty()) {
176 return s;
177 }
178 double n = double(samples.size());
179 s.mean = std::accumulate(samples.begin(), samples.end(), 0.0) / n;
180 double var = std::accumulate(
181 samples.begin(), samples.end(), 0.0,
182 [&s](double acc, double x) { return acc + (x - s.mean) * (x - s.mean); });
183 var = (samples.size() > 1) ? var / (n - 1.0) : 0.0;
184 s.standard_deviation = std::sqrt(var);
185 s.standard_error = s.standard_deviation / std::sqrt(n);
186 std::sort(samples.begin(), samples.end(), std::greater<double>());
187 Index top = std::max<Index>(1, Index(std::ceil(0.01 * n)));
188 double top_sum = std::accumulate(samples.begin(), samples.begin() + top, 0.0);
189 s.top_percent_share = (s.mean > 0.0) ? top_sum / (s.mean * n) : 0.0;
190 return s;
191}
192
194 const Index nbins = Index(edges_.size()) - 1;
195 const Index runs = settings_.runs;
196 Result result;
197 result.runs.resize(runs);
198 result.bin_edges = edges_;
199 std::vector<double> current(nbins, 0.0);
200 std::vector<double> arrivals(nbins, 0.0);
201 double before = 0.0;
202 double after = 0.0;
203
204 // blocks of runs in parallel; reduction in run order (thread independent)
205 const Index block = 256;
206 std::vector<std::vector<double>> run_current(block);
207 std::vector<std::vector<double>> run_arrivals(block);
208 std::vector<double> run_before(block);
209 std::vector<double> run_after(block);
210 for (Index start = 0; start < runs; start += block) {
211 Index count = std::min(block, runs - start);
212#pragma omp parallel for schedule(dynamic)
213 for (Index k = 0; k < count; ++k) {
214 run_current[k].assign(nbins, 0.0);
215 run_arrivals[k].assign(nbins, 0.0);
216 run_before[k] = 0.0;
217 run_after[k] = 0.0;
218 result.runs[start + k] =
219 SingleRun(start + k, run_current[k], run_arrivals[k], run_before[k],
220 run_after[k]);
221 }
222 for (Index k = 0; k < count; ++k) {
223 for (Index b = 0; b < nbins; ++b) {
224 current[b] += run_current[k][b];
225 arrivals[b] += run_arrivals[k][b];
226 }
227 before += run_before[k];
228 after += run_after[k];
229 }
230 }
231
232 std::vector<double> first;
233 std::vector<double> transit;
234 for (const RunData& r : result.runs) {
235 result.injected += r.status.injected;
237 result.hops += r.status.hops;
238 result.null_events += r.status.null_events;
239 if (r.status.censored) {
240 ++result.censored;
241 continue;
242 }
243 if (r.status.absorbed > 0) {
244 first.push_back(r.first_arrival);
245 transit.push_back(r.mean_transit);
246 }
247 }
248 result.first = Analyze(first);
249 result.transit = Analyze(transit);
250
251 double norm = graph_.Length() * double(std::max<Index>(result.injected, 1));
252 result.current.resize(nbins);
253 result.collected.resize(nbins);
254 double cumulative = 0.0;
255 for (Index b = 0; b < nbins; ++b) {
256 result.current[b] = current[b] / (norm * (edges_[b + 1] - edges_[b]));
257 cumulative += arrivals[b];
258 result.collected[b] =
259 cumulative / double(std::max<Index>(result.injected, 1));
260 }
261 result.displacement_before = before / norm;
262 result.displacement_after = after / norm;
263 return result;
264}
265
266} // namespace xtp
267} // namespace votca
Time-homogeneous KMC: waiting time Exp(escape rate), destination with probability w_e / escape rate....
static std::vector< double > InjectionTimes(const std::vector< Pulse > &pulses, Random &random)
Definition kmcengine.cc:79
the rates stored in the ToFGraph, constant in time
Definition kmcrates.h:80
Hop rates of the edges of a ToFGraph as functions of time.
Definition kmcrates.h:52
const ToFGraph & Graph() const
Definition kmcrates.h:72
Hopping graph for time-of-flight (ToF) transport.
Definition tofgraph.h:77
Index Bin(double t) const
std::unique_ptr< WaitingTimeStrategy > own_strategy_
std::vector< double > edges_
std::unique_ptr< StaticRates > own_rates_
std::unique_ptr< KMCEngine > engine_
ToFSimulation(const ToFGraph &graph, const Settings &settings)
static rates of the graph, exponential waiting times
RunData SingleRun(Index run, std::vector< double > &current, std::vector< double > &arrivals, double &before, double &after) const
static Statistic Analyze(std::vector< double > samples)
How a carrier's next hop is drawn: the waiting time and the destination. The pluggable part of the KM...
virtual std::string Name() const =0
virtual void Prepare(const TimeDependentRates &rates)=0
STL namespace.
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
Index blocked_injections
injections dropped because all source sites were occupied
Definition kmcengine.h:125
std::vector< double > collected
std::vector< RunData > runs
std::vector< double > bin_edges
std::vector< double > current