26#include <Eigen/IterativeLinearSolvers>
27#include <Eigen/SparseLU>
42 LifetimeObserver(
const ToFGraph& graph,
Index run,
bool keep,
43 Eigen::VectorXd& occupation, Eigen::VectorXd& decays,
44 std::vector<LifetimeSimulation::Record>& records,
46 std::vector<LifetimeSimulation::EnergySample>* energy)
50 occupation_(occupation),
53 energy_hops_(energy_hops),
56 void Inject(
Index carrier,
Index site,
double t)
override {
57 if (carrier >=
Index(site_.size())) {
58 Index n = carrier + 1;
60 entry_.resize(n, 0.0);
61 injected_.resize(n, 0.0);
63 displacement_.resize(n, Eigen::Vector3d::Zero());
65 site_[carrier] = site;
67 injected_[carrier] = t;
70 void Hop(
Index carrier,
Index from,
Index to,
const Eigen::Vector3d& dR,
72 occupation_[from] += t - entry_[carrier];
75 displacement_[carrier] += dR;
78 if (energy_ !=
nullptr && energy_hops_ > 0 &&
79 total_hops_ % energy_hops_ == 0) {
82 for (
Index s : site_) {
84 e += graph_.getSite(s).energy;
88 energy_->push_back({t, total_hops_, n > 0 ?
e / double(n) : 0.0});
92 void Decay(
Index carrier,
Index site,
double t)
override {
93 occupation_[site] += t - entry_[carrier];
95 double lifetime = t - injected_[carrier];
96 const Eigen::Vector3d& R = displacement_[carrier];
97 double r2 = R.squaredNorm();
99 sums_.lifetime += lifetime;
100 sums_.lifetime2 += lifetime * lifetime;
101 sums_.distance += std::sqrt(r2);
102 sums_.distance2 += r2;
103 sums_.distance4 += r2 * r2;
104 sums_.components += R.cwiseAbs2();
105 sums_.hops += double(hops_[carrier]);
107 LifetimeSimulation::Record r;
111 r.lifetime = lifetime;
112 r.hops = hops_[carrier];
115 records_.push_back(r);
120 void Finish(
double end) {
121 for (
Index c = 0; c <
Index(site_.size()); ++c) {
123 occupation_[site_[c]] += end - entry_[c];
128 const DecaySums& Sums()
const {
return sums_; }
131 const ToFGraph& graph_;
134 Eigen::VectorXd& occupation_;
135 Eigen::VectorXd& decays_;
136 std::vector<LifetimeSimulation::Record>& records_;
138 std::vector<LifetimeSimulation::EnergySample>* energy_;
139 std::vector<Index> site_;
140 std::vector<double> entry_;
141 std::vector<double> injected_;
142 std::vector<Index> hops_;
143 std::vector<Eigen::Vector3d> displacement_;
144 Index total_hops_ = 0;
151 std::vector<double> decay_rates,
162 std::vector<double> decay_rates,
168 throw std::runtime_error(
169 "LifetimeSimulation: the rates belong to a different graph");
171 Setup(strategy, rates);
187 throw std::runtime_error(
188 "LifetimeSimulation: need one decay rate per site");
190 for (
Index i = 0; i <
graph_.NumberOfSites(); ++i) {
192 if (!
graph_.getSite(i).ignored && !(k > 0.0 && std::isfinite(k))) {
193 throw std::runtime_error(
"LifetimeSimulation: the decay rate of site " +
195 " must be positive and finite");
199 throw std::runtime_error(
200 "LifetimeSimulation: need at least one run and one decay per run");
204 throw std::runtime_error(
205 "LifetimeSimulation: the number of carriers must be between 1 and "
206 "the number of injection sites (" +
207 std::to_string(
graph_.SourceSites().size()) +
")");
210 throw std::runtime_error(
"LifetimeSimulation: energy_hops < 0");
215 Index run,
Index decays, std::vector<EnergySample>* energy)
const {
219 data.
decays = Eigen::VectorXd::Zero(n);
227 std::vector<double> injection(
settings_.carriers, 0.0);
228 data.
status =
engine_->Run(injection, random, observer, limits);
230 data.
sums = observer.Sums();
243 const Index block = 64;
244 std::vector<RunData> data(block);
245 for (
Index start = 0; start < runs; start += block) {
246 Index count = std::min(block, runs - start);
247#pragma omp parallel for schedule(dynamic)
248 for (
Index k = 0; k < count; ++k) {
249 Index run = start + k;
254 for (
Index k = 0; k < count; ++k) {
272 total.
hops += s.hops;
278 throw std::runtime_error(
"LifetimeSimulation: no carrier decayed");
280 double N = double(total.
decays);
282 auto error = [N](
double sum,
double sum2) {
286 double mean = sum / N;
287 double var = std::max(sum2 / N - mean * mean, 0.0) * N / (N - 1.0);
288 return std::sqrt(var / N);
306 const ToFGraph& graph,
const std::vector<double>& decay_rates,
307 Index direct_limit,
double tolerance) {
309 if (
Index(decay_rates.size()) != n) {
310 throw std::runtime_error(
"LifetimeSimulation::Exact: one rate per site");
312 using SpMat = Eigen::SparseMatrix<double>;
313 std::vector<Eigen::Triplet<double>> triplets;
315 Eigen::MatrixXd b = Eigen::MatrixXd::Zero(n, 3);
316 for (
Index i = 0; i < n; ++i) {
317 double diag = decay_rates[i] + graph.
EscapeRate(i);
322 throw std::runtime_error(
"LifetimeSimulation::Exact: site " +
324 " neither decays nor hops");
327 triplets.emplace_back(i, i, diag);
329 triplets.emplace_back(i,
e.to, -
e.rate);
330 b.row(i) +=
e.rate *
e.dR.transpose();
334 A.setFromTriplets(triplets.begin(), triplets.end());
336 SpMat At = A.transpose();
341 auto solve = [&](
const SpMat& M,
const Eigen::MatrixXd& rhs) {
342 Eigen::MatrixXd x(rhs.rows(), rhs.cols());
343 if (n <= direct_limit) {
344 result.
solver =
"sparse LU";
345 Eigen::SparseLU<SpMat, Eigen::COLAMDOrdering<int>> lu;
347 if (lu.info() != Eigen::Success) {
348 throw std::runtime_error(
349 "LifetimeSimulation::Exact: LU factorization failed");
353 result.
solver =
"BiCGSTAB with ILUT";
354 Eigen::BiCGSTAB<SpMat, Eigen::IncompleteLUT<double>> bicg;
355 bicg.setTolerance(tolerance);
356 bicg.setMaxIterations(100000);
358 for (
Index c = 0; c < rhs.cols(); ++c) {
361 x.col(c) =
ScaledSolve(bicg, Eigen::VectorXd(rhs.col(c)), &solved);
362 if (solved && bicg.iterations() >= bicg.maxIterations()) {
367 for (
Index c = 0; c < rhs.cols(); ++c) {
368 double scale = (M * x.col(c)).cwiseAbs().maxCoeff() +
369 rhs.col(c).cwiseAbs().maxCoeff();
370 double err = (M * x.col(c) - rhs.col(c)).cwiseAbs().maxCoeff() /
371 std::max(scale, std::numeric_limits<double>::min());
378 Eigen::MatrixXd rhs1(n, 4);
379 rhs1.col(0).setOnes();
380 rhs1.rightCols(3) = b;
381 Eigen::MatrixXd x1 = solve(A, rhs1);
382 Eigen::VectorXd tau = x1.col(0);
383 Eigen::MatrixXd m = x1.rightCols(3);
385 Eigen::MatrixXd rhs2 = Eigen::MatrixXd::Zero(n, 3);
386 for (
Index i = 0; i < n; ++i) {
389 e.rate * (
e.dR.cwiseAbs2().transpose() +
390 2.0 *
e.dR.transpose().cwiseProduct(m.row(
e.to)));
393 Eigen::MatrixXd s2 = solve(A, rhs2);
395 const std::vector<Index>& sources = graph.
SourceSites();
396 Eigen::VectorXd p0 = Eigen::VectorXd::Zero(n);
397 for (
Index s : sources) {
398 p0[s] = 1.0 / double(sources.size());
400 Eigen::VectorXd g = solve(At, p0);
405 Eigen::VectorXd k = Eigen::Map<const Eigen::VectorXd>(decay_rates.data(), n);
Time-homogeneous KMC: waiting time Exp(escape rate), destination with probability w_e / escape rate....
std::unique_ptr< TimeDependentRates > own_rates_
std::unique_ptr< KMCEngine > engine_
std::unique_ptr< WaitingTimeStrategy > own_strategy_
static ExactResult Exact(const ToFGraph &graph, const std::vector< double > &decay_rates, Index direct_limit=20000, double tolerance=1e-12)
RunData SingleRun(Index run, Index decays, std::vector< EnergySample > *energy) const
std::vector< double > decay_rates_
void Setup(WaitingTimeStrategy &strategy, const TimeDependentRates &rates)
LifetimeSimulation(const ToFGraph &graph, std::vector< double > decay_rates, const Settings &settings)
the rates stored in the ToFGraph, constant in time
Hop rates of the edges of a ToFGraph as functions of time.
const ToFGraph & Graph() const
Hopping graph for time-of-flight (ToF) transport.
const Site & getSite(Index i) const
const std::vector< Index > & SourceSites() const
source sites from which a sink can be reached
Index NumberOfSites() const
Index NumberOfEdges() const
EdgeRange OutEdges(Index i) const
double EscapeRate(Index i) const
sum of the rates of all out-edges
How a carrier's next hop is drawn: the waiting time and the destination. The pluggable part of the KM...
virtual void Prepare(const TimeDependentRates &rates)=0
Charge transport classes.
Eigen::VectorXd ScaledSolve(const IterativeSolver &solver, const Eigen::VectorXd &b, bool *solved=nullptr)
Solves A x = b with an Eigen iterative solver for the right-hand side scaled to a largest entry of 1.
Provides a means for comparing floating point numbers.
const std::vector< double > * rates
bool replace
inject a new carrier at the time of every decay
double max_wall_time
wall-clock time of the run, s (checked every 4096 events)
Index max_decays
stop after this many decays
sums over the decays of one run
Eigen::Vector3d components
Eigen::VectorXd decay_probability
double squared_displacement
Eigen::Vector3d squared_components
Eigen::VectorXd occupation
expected time per site per carrier, normalized by the lifetime
Eigen::VectorXd occupation
occupation probability per site (sum: number of carriers)
std::vector< Record > records
double squared_displacement
Eigen::VectorXd decay_fraction
fraction of the decays per site
std::vector< EnergySample > energy
double mean_distance_error
Eigen::Vector3d squared_components
double squared_displacement_error
KMCEngine::RunStatus status
std::vector< Record > records
Eigen::VectorXd occupation
bool ignored
segment type in Options::ignore_types, no edges