28#include <Eigen/IterativeLinearSolvers>
29#include <Eigen/SparseLU>
45 BulkObserver(
const ToFGraph& graph,
Index carriers,
double start,
46 Index blocks,
double tau0, Eigen::VectorXd& occupation,
47 double sample_interval,
Index sample_hops,
48 std::vector<BulkSimulation::Sample>* samples)
50 position_(carriers, Eigen::Vector3d::Zero()),
52 entry_(carriers, 0.0),
56 occupation_(occupation),
57 sample_interval_(sample_interval),
58 sample_hops_(sample_hops),
61 void Inject(
Index carrier,
Index site,
double t)
override {
63 position_[carrier] = graph_.getSite(site).pos;
64 site_[carrier] = site;
68 void Hop(
Index carrier,
Index from,
Index to,
const Eigen::Vector3d& dR,
71 Occupy(from, entry_[carrier], t);
72 position_[carrier] += dR;
76 if (samples_ !=
nullptr && sample_hops_ > 0 && hops_ % sample_hops_ == 0) {
82 void Finish(
double end) {
84 for (
Index c = 0; c <
Index(site_.size()); ++c) {
86 Occupy(site_[c], entry_[c], end);
91 const std::vector<std::vector<Eigen::Vector3d>>& Boundaries()
const {
94 double Tau()
const {
return tau_; }
95 const std::vector<Eigen::Vector3d>& Positions()
const {
return position_; }
98 const ToFGraph& graph_;
99 std::vector<Eigen::Vector3d> position_;
100 std::vector<Index> site_;
101 std::vector<double> entry_;
105 Eigen::VectorXd& occupation_;
106 double sample_interval_;
108 std::vector<BulkSimulation::Sample>* samples_;
109 std::vector<std::vector<Eigen::Vector3d>> boundaries_;
110 Index next_sample_ = 0;
113 void Occupy(
Index site,
double from,
double to) {
114 from = std::max(from, start_);
116 occupation_[site] += to - from;
122 void Advance(
double t) {
123 while (start_ +
double(boundaries_.size()) * tau_ < t) {
124 if (
Index(boundaries_.size()) == 2 * blocks_ + 1) {
126 std::vector<std::vector<Eigen::Vector3d>> kept;
127 kept.reserve(blocks_ + 1);
128 for (
Index j = 0; j <
Index(boundaries_.size()); j += 2) {
129 kept.push_back(std::move(boundaries_[j]));
131 boundaries_ = std::move(kept);
135 boundaries_.push_back(position_);
137 if (samples_ ==
nullptr || !(sample_interval_ > 0.0)) {
140 while (
double(next_sample_) * sample_interval_ < t) {
141 Record(
double(next_sample_) * sample_interval_);
146 void Record(
double t) {
147 BulkSimulation::Sample
s;
150 s.positions = position_;
153 for (
Index site : site_) {
155 energy += graph_.getSite(site).energy;
159 s.energy = (n > 0) ? energy /
double(n) : 0.0;
160 samples_->push_back(std::move(s));
182 throw std::runtime_error(
183 "BulkSimulation: the rates belong to a different graph");
191 throw std::runtime_error(
192 "BulkSimulation: needs a bulk graph (ToFGraph::BulkOptions), not a ToF "
196 throw std::runtime_error(
197 "BulkSimulation: need at least one run and two blocks");
201 throw std::runtime_error(
202 "BulkSimulation: the number of carriers must be between 1 and the "
203 "number of injection sites (" +
204 std::to_string(
graph_.SourceSites().size()) +
")");
208 throw std::runtime_error(
209 "BulkSimulation: equilibration and sampling interval must not be "
213 if (l.
max_time == std::numeric_limits<double>::infinity() &&
214 l.
max_events == std::numeric_limits<Index>::max()) {
215 throw std::runtime_error(
216 "BulkSimulation: a limit on the simulated time or on the number of "
217 "events is required");
220 throw std::runtime_error(
221 "BulkSimulation: the equilibration time is not shorter than the run");
226 Index run, Eigen::VectorXd& occupation,
227 std::vector<Sample>* trajectory)
const {
230 double max_escape = 0.0;
231 for (
Index i = 0; i <
graph_.NumberOfSites(); ++i) {
232 max_escape = std::max(max_escape,
graph_.EscapeRate(i));
235 double tau0 = (max_escape > 0.0) ? 1
e-3 / max_escape : 1
e-18;
237 tau0, occupation,
settings_.sample_interval,
240 std::vector<double> injection(n, 0.0);
243 data.
status =
engine_->Run(injection, random, observer, limits);
248 double end = (!stopped_early && std::isfinite(limits.
max_time))
251 observer.Finish(end);
254 double window = end - data.
start;
255 if (!(window > 0.0)) {
256 occupation.setZero();
259 occupation /= window;
261 const auto& b = observer.Boundaries();
263 for (
Index c = 0; c < n; ++c) {
264 data.
displacement[c] = observer.Positions()[c] - b.front()[c];
267 data.
velocity /= double(n) * window;
275 const double tau = observer.Tau();
276 Eigen::Vector3d mean = Eigen::Vector3d::Zero();
277 for (
Index k = 0; k < blocks; ++k) {
278 for (
Index c = 0; c < n; ++c) {
279 mean += b[k + 1][c] - b[k][c];
282 double samples = double(blocks * n);
284 Eigen::Matrix3d cov = Eigen::Matrix3d::Zero();
285 Eigen::Matrix3d block_cov = Eigen::Matrix3d::Zero();
286 for (
Index k = 0; k < blocks; ++k) {
287 Eigen::Vector3d per_block = Eigen::Vector3d::Zero();
288 for (
Index c = 0; c < n; ++c) {
289 Eigen::Vector3d d = b[k + 1][c] - b[k][c] - mean;
290 cov += d * d.transpose();
293 per_block /= double(n) * tau;
294 block_cov += per_block * per_block.transpose();
297 data.
diffusion = cov / ((samples - 1.0) * 2.0 * tau);
307 result.
runs.resize(runs);
308 result.
occupation = Eigen::VectorXd::Zero(sites);
311 const Index block = 64;
312 std::vector<Eigen::VectorXd> run_occupation(block);
313 for (
Index start = 0; start < runs; start += block) {
314 Index count = std::min(block, runs - start);
315#pragma omp parallel for schedule(dynamic)
316 for (
Index k = 0; k < count; ++k) {
317 run_occupation[k] = Eigen::VectorXd::Zero(sites);
318 Index run = start + k;
322 for (
Index k = 0; k < count; ++k) {
342 throw std::runtime_error(
343 "BulkSimulation: no run lasted longer than the equilibration time");
356 Eigen::Vector3d var_v = Eigen::Vector3d::Zero();
357 Eigen::Matrix3d var_d = Eigen::Matrix3d::Zero();
364 double norm = double(valid) * double(valid - 1);
373std::vector<Index> Components(
const ToFGraph& g,
Index& count) {
375 std::vector<Index> index(n, -1), low(n, 0), comp(n, -1);
376 std::vector<char> on_stack(n, 0);
377 std::vector<Index> stack;
378 std::vector<std::pair<Index, Index>> call;
381 for (
Index root = 0; root < n; ++root) {
382 if (index[root] >= 0) {
386 index[root] = low[root] = counter++;
387 stack.push_back(root);
389 while (!call.empty()) {
390 Index v = call.back().first;
391 Index&
e = call.back().second;
393 const ToFGraph::Edge& edge = g.
getEdge(
e++);
394 if (!(edge.rate > 0.0)) {
399 index[w] = low[w] = counter++;
403 }
else if (on_stack[w]) {
404 low[v] = std::min(low[v], index[w]);
408 if (low[v] == index[v]) {
420 Index u = call.back().first;
421 low[u] = std::min(low[u], low[v]);
436 std::vector<Index> comp = Components(graph, ncomp);
439 std::vector<char> closed(ncomp, 1);
440 std::vector<char> relevant(ncomp, 0);
441 for (
Index i = 0; i < n; ++i) {
443 relevant[comp[i]] = 1;
446 if (
e.rate > 0.0 && comp[
e.to] != comp[i]) {
451 std::vector<Index> classes;
452 for (
Index c = 0; c < ncomp; ++c) {
453 if (closed[c] && relevant[c]) {
454 classes.push_back(c);
457 if (classes.size() != 1) {
458 result.
message = std::to_string(classes.size()) +
459 " closed classes of sites (e.g. isolated sites or "
460 "clusters without connection): the stationary "
461 "distribution depends on the start";
465 std::vector<Index> local(n, -1);
466 std::vector<Index> sites;
467 for (
Index i = 0; i < n; ++i) {
468 if (comp[i] == classes[0]) {
469 local[i] =
Index(sites.size());
478 result.
solver =
"trivial";
483 using SpMat = Eigen::SparseMatrix<double>;
485 std::vector<Eigen::Triplet<double>> tl;
486 Eigen::MatrixXd b = Eigen::MatrixXd::Zero(m, 3);
487 for (
Index a = 0; a < m; ++a) {
492 tl.emplace_back(a, local[
e.to],
e.rate);
494 b.row(a) +=
e.rate *
e.dR.transpose();
497 tl.emplace_back(a, a, -out);
500 L.setFromTriplets(tl.begin(), tl.end());
502 for (
Index a = 0; a < m; ++a) {
503 scale = std::max(scale, -
L.coeff(a, a));
507 auto solve = [&](SpMat M,
const Eigen::MatrixXd& rhs) {
509 Eigen::MatrixXd x(rhs.rows(), rhs.cols());
510 if (m <= direct_limit) {
511 result.
solver =
"sparse LU";
512 Eigen::SparseLU<SpMat, Eigen::COLAMDOrdering<int>> lu;
514 if (lu.info() != Eigen::Success) {
515 throw std::runtime_error(
"BulkSimulation::Exact: LU failed");
519 result.
solver =
"BiCGSTAB with ILUT";
520 Eigen::BiCGSTAB<SpMat, Eigen::IncompleteLUT<double>> bicg;
521 bicg.setTolerance(tolerance);
522 bicg.setMaxIterations(100000);
524 for (
Index c = 0; c < rhs.cols(); ++c) {
527 x.col(c) =
ScaledSolve(bicg, Eigen::VectorXd(rhs.col(c)), &solved);
528 if (solved && bicg.iterations() >= bicg.maxIterations()) {
533 for (
Index c = 0; c < rhs.cols(); ++c) {
534 Eigen::VectorXd r = M * x.col(c) - rhs.col(c);
535 double denom = (M * x.col(c)).cwiseAbs().maxCoeff() +
536 rhs.col(c).cwiseAbs().maxCoeff();
552 for (
Index a = 1; a < m; ++a) {
557 auto pinned = [pin](SpMat M) {
558 M.prune([pin](
Index row,
Index,
double) {
return row != pin; });
559 M.coeffRef(pin, pin) = 1.0;
562 SpMat A = pinned(SpMat(
L.transpose()) / scale);
563 Eigen::MatrixXd e0 = Eigen::MatrixXd::Zero(m, 1);
565 Eigen::VectorXd pi = solve(A, e0).col(0);
567 Eigen::Vector3d v = (pi.transpose() * b).transpose();
568 SpMat B = pinned(
L / scale);
569 Eigen::MatrixXd rhs = (Eigen::VectorXd::Ones(m) * v.transpose() - b) / scale;
570 rhs.row(pin).setZero();
571 Eigen::MatrixXd chi = solve(B, rhs);
572 for (
Index c = 0; c < 3; ++c) {
573 chi.col(c).array() -= pi.dot(chi.col(c));
576 Eigen::Matrix3d
D = Eigen::Matrix3d::Zero();
577 for (
Index a = 0; a < m; ++a) {
581 e.dR + (chi.row(local[
e.to]) - chi.row(a)).transpose();
582 D += 0.5 * pi(a) *
e.rate * g * g.transpose();
586 for (
Index a = 0; a < m; ++a) {
std::unique_ptr< WaitingTimeStrategy > own_strategy_
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
std::unique_ptr< KMCEngine > engine_
RunData SingleRun(Index run, Eigen::VectorXd &occupation, std::vector< Sample > *trajectory) const
BulkSimulation(const ToFGraph &graph, const Settings &settings)
std::unique_ptr< TimeDependentRates > own_rates_
Time-homogeneous KMC: waiting time Exp(escape rate), destination with probability w_e / escape rate....
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
Index NumberOfSites() const
const Edge & getEdge(Index e) const
EdgeRange OutEdges(Index i) const
Index EdgeOffset(Index i) const
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.
Eigen::VectorXd occupation
Eigen::Matrix3d diffusion
std::vector< Sample > trajectory
Eigen::Matrix3d diffusion_error
standard error over runs, zero for one run
Eigen::VectorXd occupation
occupation probability per site, averaged over runs
Eigen::Matrix3d diffusion
std::vector< RunData > runs
Eigen::Vector3d velocity_error
Eigen::Matrix3d velocity_covariance
covariance of the block increments of the velocity estimate
Eigen::Matrix3d diffusion
KMCEngine::RunStatus status
double start
observation window [start, end]
std::vector< Eigen::Vector3d > displacement
displacement of each carrier over the window
Index max_events
candidate events (hops and null events)
double max_time
simulated time, s
Index events
candidate events processed
double energy
site energy of the carrier
bool ignored
segment type in Options::ignore_types, no edges