votca 2026-dev
Loading...
Searching...
No Matches
graphrandomwalk.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 <limits>
25#include <numeric>
26#include <stdexcept>
27
28// Third party includes
29#include <Eigen/IterativeLinearSolvers>
30#include <Eigen/SparseCholesky>
31#include <Eigen/SparseLU>
32
33// Local VOTCA includes
34#include "votca/xtp/citations.h"
37
38namespace votca {
39namespace xtp {
40
41namespace {
42using SpMat = Eigen::SparseMatrix<double>;
43using SpMatR = Eigen::SparseMatrix<double, Eigen::RowMajor>;
44using SolveFn = std::function<Eigen::VectorXd(const Eigen::VectorXd&)>;
45using Solver = GraphRandomWalk::Solver;
46using Settings = GraphRandomWalk::Settings;
47
48// conjugate gradients with the Jacobi preconditioner; the hitting times are
49// dominated by the slow modes (traps), so the residual has to be driven far
50// down (default 1e-14) before they are resolved
51Eigen::VectorXd PCG(const SpMatR& A, const Eigen::VectorXd& b,
52 const Settings& settings, Index& its, bool& converged) {
53 Eigen::VectorXd dinv = A.diagonal().cwiseInverse();
54 Eigen::VectorXd x = Eigen::VectorXd::Zero(b.size());
55 Eigen::VectorXd r = b;
56 double bnorm = b.norm();
57 its = 0;
58 converged = true;
59 if (bnorm == 0.0) {
60 return x;
61 }
62 Eigen::VectorXd z = dinv.cwiseProduct(r);
63 Eigen::VectorXd d = z;
64 Eigen::VectorXd Ad(b.size());
65 double rz = r.dot(z);
66 const double threshold2 =
67 settings.tolerance * settings.tolerance * bnorm * bnorm;
68 // stop at a rounding floor just above the tolerance instead of running
69 // into the iteration limit; the refinement decides. Only close to the
70 // tolerance: further up, plateaus of the residual are normal while the
71 // slow trap modes are resolved.
72 const double floor2 = 1e6 * threshold2; // 1000 x tolerance
73 double best2 = r.squaredNorm();
74 Index last_improvement = 0;
75 while (r.squaredNorm() > threshold2) {
76 if (its >= settings.max_iterations) {
77 converged = false;
78 break;
79 }
80 if (best2 < floor2 && its - last_improvement > 1000) {
81 break;
82 }
83 Ad.noalias() = A * d;
84 double alpha = rz / d.dot(Ad);
85 x += alpha * d;
86 r -= alpha * Ad;
87 z = dinv.cwiseProduct(r);
88 double rz_new = r.dot(z);
89 d = z + (rz_new / rz) * d;
90 rz = rz_new;
91 ++its;
92 double r2 = r.squaredNorm();
93 if (r2 < 0.81 * best2) { // residual down by 10 %
94 best2 = r2;
95 last_improvement = its;
96 }
97 }
98 return x;
99}
100
106class GeneratorSolver {
107 public:
108 GeneratorSolver(const SpMatR& Q, const Eigen::VectorXd& h, Solver solver,
109 const Settings& settings)
110 : Q_(Q), settings_(settings) {
111 Qabs_ = Q_.cwiseAbs();
112 Qt_ = Q_.transpose();
113 Qtabs_ = Qt_.cwiseAbs();
114 const Index m = Q_.rows();
115
116 bool symmetric = (solver == Solver::Cholesky || solver == Solver::CG ||
117 (solver == Solver::Auto && h.size() == m));
118 if ((solver == Solver::Cholesky || solver == Solver::CG) && h.size() != m) {
119 throw std::runtime_error(
120 "GraphRandomWalk: the cholesky and cg solvers need rates with "
121 "detailed balance, use lu or bicgstab");
122 }
123 if (symmetric && h.cwiseAbs().maxCoeff() > 600.0) {
124 if (solver != Solver::Auto) {
125 throw std::runtime_error(
126 "GraphRandomWalk: the potential range is too large for the "
127 "symmetrized solvers, use lu or bicgstab");
128 }
129 symmetric = false;
130 }
131 if (solver == Solver::Auto) {
132 bool direct = m <= settings_.direct_limit;
133 if (symmetric) {
134 solver = direct ? Solver::Cholesky : Solver::CG;
135 } else {
136 solver = direct ? Solver::LU : Solver::BiCGSTAB;
137 }
138 }
139 iterative_ = (solver == Solver::CG || solver == Solver::BiCGSTAB);
140
141 if (symmetric) {
142 p_ = h.array().exp();
143 pinv_ = p_.cwiseInverse();
144 // S = P Q P^-1 is symmetric; Q = P^-1 S P, Q^T = P S P^-1
145 SpMatR S = p_.asDiagonal() * Q_ * pinv_.asDiagonal();
146 SpMatR St = S.transpose();
147 S = 0.5 * (S + St);
148 if (solver == Solver::Cholesky) {
149 name = "cholesky (LDLT of the symmetrized generator)";
150 ldlt_.compute(SpMat(S));
151 if (ldlt_.info() != Eigen::Success) {
152 throw std::runtime_error(
153 "GraphRandomWalk: Cholesky factorization failed");
154 }
155 solveS_ = [this](const Eigen::VectorXd& b) {
156 return Eigen::VectorXd(ldlt_.solve(b));
157 };
158 } else {
159 name = "cg (Jacobi preconditioned, symmetrized generator)";
160 S_ = std::move(S);
161 solveS_ = [this](const Eigen::VectorXd& b) {
162 Index its = 0;
163 bool ok = true;
164 Eigen::VectorXd y = PCG(S_, b, settings_, its, ok);
165 iterations += its;
166 converged = converged && ok;
167 return y;
168 };
169 }
170 solve_ = [this](const Eigen::VectorXd& b) {
171 Eigen::VectorXd y = solveS_((p_.array() * b.array()).matrix());
172 return Eigen::VectorXd(pinv_.array() * y.array());
173 };
174 solve_adjoint_ = [this](const Eigen::VectorXd& b) {
175 Eigen::VectorXd y = solveS_((pinv_.array() * b.array()).matrix());
176 return Eigen::VectorXd(p_.array() * y.array());
177 };
178 } else if (solver == Solver::LU) {
179 name = "lu";
180 Qc_ = Q_;
181 lu_.analyzePattern(Qc_);
182 lu_.factorize(Qc_);
183 if (lu_.info() != Eigen::Success) {
184 throw std::runtime_error("GraphRandomWalk: LU factorization failed: " +
185 lu_.lastErrorMessage());
186 }
187 solve_ = [this](const Eigen::VectorXd& b) {
188 return Eigen::VectorXd(lu_.solve(b));
189 };
190 solve_adjoint_ = [this](const Eigen::VectorXd& b) {
191 return Eigen::VectorXd(lu_.transpose().solve(b));
192 };
193 } else {
194 name = "bicgstab (incomplete LU preconditioned)";
195 for (auto* b : {&bicg_, &bicg_adjoint_}) {
196 b->setTolerance(settings_.tolerance);
197 b->setMaxIterations(settings_.max_iterations);
198 // a cheap incomplete factorization; accuracy comes from the
199 // iterations and the refinement
200 b->preconditioner().setDroptol(1e-4);
201 b->preconditioner().setFillfactor(5);
202 }
203 // the iterative solvers keep a reference to the matrix
204 Qc_ = Q_;
205 Qtc_ = Qt_;
206 bicg_.compute(Qc_);
207 bicg_adjoint_.compute(Qtc_);
208 auto make = [this](BiCG& b) {
209 return [this, &b](const Eigen::VectorXd& rhs) {
210 bool solved = false;
211 Eigen::VectorXd x = ScaledSolve(b, rhs, &solved);
212 if (!solved) {
213 return x; // zero right-hand side
214 }
215 iterations += b.iterations();
216 // only the iteration limit counts here: Eigen versions differ in
217 // reporting a residual floor just above the tolerance or a restart
218 // as NoConvergence/NumericalIssue; the refinement against Q and the
219 // backward error decide whether the result is usable
220 converged = converged && b.iterations() < b.maxIterations();
221 return x;
222 };
223 };
224 solve_ = make(bicg_);
225 solve_adjoint_ = make(bicg_adjoint_);
226 }
227 }
228
229 Eigen::VectorXd Solve(const Eigen::VectorXd& b) {
230 return Refine(Q_, Qabs_, solve_, b);
231 }
232 Eigen::VectorXd SolveAdjoint(const Eigen::VectorXd& b) {
233 return Refine(Qt_, Qtabs_, solve_adjoint_, b);
234 }
235
236 std::string name;
237 Index iterations = 0;
238 bool converged = true;
239 double backward_error = 0.0;
240 Index refinement_steps = 0;
241
242 private:
243 using BiCG = Eigen::BiCGSTAB<SpMat, Eigen::IncompleteLUT<double>>;
244
245 // Solve A x = b with an approximate solver and iterative refinement
246 // against A; tracks the largest componentwise backward error
247 // |b - A x|_i / (|A| |x| + |b|)_i.
248 Eigen::VectorXd Refine(const SpMatR& A, const SpMatR& Aabs,
249 const SolveFn& solve, const Eigen::VectorXd& b) {
250 // direct solvers: refine to rounding level; iterative solvers: 1e-12
251 // suffices, each step is a full solve
252 double target =
253 iterative_ ? std::max(1e-12, 100.0 * settings_.tolerance) : 1e-14;
254 auto error = [&A, &Aabs, &b](const Eigen::VectorXd& y) {
255 Eigen::VectorXd r = b - A * y;
256 Eigen::VectorXd scale = Aabs * y.cwiseAbs() + b.cwiseAbs();
257 double e = 0.0;
258 for (Index i = 0; i < r.size(); ++i) {
259 if (scale[i] > 0.0) {
260 e = std::max(e, std::abs(r[i]) / scale[i]);
261 }
262 }
263 return std::make_pair(e, Eigen::VectorXd(r));
264 };
265 Eigen::VectorXd x = solve(b);
266 Index steps = 0;
267 auto [err, r] = error(x);
268 // refine until the backward error is at the target or stops improving
269 while (err > target && steps < 5) {
270 Eigen::VectorXd x_new = x + solve(r);
271 auto [err_new, r_new] = error(x_new);
272 ++steps;
273 if (err_new > 0.5 * err) {
274 if (err_new < err) {
275 x = x_new;
276 err = err_new;
277 }
278 break;
279 }
280 x = x_new;
281 err = err_new;
282 r = r_new;
283 }
284 if (!x.allFinite()) {
285 throw std::runtime_error("GraphRandomWalk: solution is not finite");
286 }
287 backward_error = std::max(backward_error, err);
288 refinement_steps = std::max(refinement_steps, steps);
289 return x;
290 }
291
292 const SpMatR& Q_;
293 Settings settings_;
294 SpMatR Qabs_;
295 SpMatR Qt_;
296 SpMatR Qtabs_;
297 bool iterative_ = false;
298 Eigen::VectorXd p_;
299 Eigen::VectorXd pinv_;
300 SpMatR S_;
301 SpMat Qc_; // column-major copies for LU and BiCGSTAB
302 SpMat Qtc_;
303 Eigen::SimplicialLDLT<SpMat, Eigen::Lower, Eigen::AMDOrdering<int>> ldlt_;
304 Eigen::SparseLU<SpMat, Eigen::COLAMDOrdering<int>> lu_;
305 BiCG bicg_;
306 BiCG bicg_adjoint_;
307 SolveFn solveS_;
308 SolveFn solve_;
309 SolveFn solve_adjoint_;
310};
311
312// row-major sparse matrix from rows of (column, value), duplicates summed
313class RowBuilder {
314 public:
315 explicit RowBuilder(Index rows) : rows_(rows) {
316 outer_.reserve(rows + 1);
317 outer_.push_back(0);
318 }
319 void Add(Index col, double value) { row_.emplace_back(col, value); }
320 void FinishRow() {
321 std::sort(row_.begin(), row_.end());
322 for (std::size_t k = 0; k < row_.size(); ++k) {
323 if (k > 0 && row_[k].first == row_[k - 1].first) {
324 values_.back() += row_[k].second;
325 } else {
326 inner_.push_back(row_[k].first);
327 values_.push_back(row_[k].second);
328 }
329 }
330 row_.clear();
331 outer_.push_back(Index(inner_.size()));
332 }
333 SpMatR Build(Index cols) const {
334 if (Index(outer_.size()) != rows_ + 1) {
335 throw std::runtime_error("RowBuilder: incomplete matrix");
336 }
337 SpMatR A(rows_, cols);
338 A.reserve(Index(inner_.size()));
339 for (Index i = 0; i < rows_; ++i) {
340 A.startVec(i);
341 for (Index k = outer_[i]; k < outer_[i + 1]; ++k) {
342 A.insertBack(i, inner_[k]) = values_[k];
343 }
344 }
345 A.finalize();
346 return A;
347 }
348
349 private:
350 Index rows_;
351 std::vector<std::pair<Index, double>> row_;
352 std::vector<Index> outer_;
353 std::vector<Index> inner_;
354 std::vector<double> values_;
355};
356
357inline Index PairRank(Index i, Index j) {
358 if (i > j) {
359 std::swap(i, j);
360 }
361 return j * (j - 1) / 2 + i;
362}
363} // namespace
364
366 const std::string& name) {
367 if (name == "auto") {
368 return Solver::Auto;
369 } else if (name == "cholesky") {
370 return Solver::Cholesky;
371 } else if (name == "lu") {
372 return Solver::LU;
373 } else if (name == "cg") {
374 return Solver::CG;
375 } else if (name == "bicgstab") {
376 return Solver::BiCGSTAB;
377 }
378 throw std::runtime_error("GraphRandomWalk: unknown solver '" + name +
379 "', use auto, cholesky, lu, cg or bicgstab");
380}
381
383 const std::string& name) {
384 if (name == "first") {
385 return Observable::First;
386 } else if (name == "transit") {
387 return Observable::Transit;
388 }
389 throw std::runtime_error("GraphRandomWalk: unknown observable '" + name +
390 "', use first or transit");
391}
392
394 const Index n = graph_.NumberOfSites();
395 const std::vector<ToFGraph::Site>& sites = graph_.Sites();
396 Transient t;
397 t.index_of.assign(n, -1);
398 // transient sites: not a sink, sink reachable
399 for (Index i = 0; i < n; ++i) {
400 if (sites[i].role != ToFGraph::Role::Sink && sites[i].reaches_sink) {
401 t.index_of[i] = Index(t.site_of.size());
402 t.site_of.push_back(i);
403 }
404 }
405 const Index m = Index(t.site_of.size());
406 if (m == 0) {
407 throw std::runtime_error("GraphRandomWalk: no transient sites");
408 }
409 for (Index i : t.site_of) {
410 if (!(graph_.EscapeRate(i) > 0.0)) {
411 throw std::runtime_error("GraphRandomWalk: site " + std::to_string(i) +
412 " has no hops");
413 }
414 // a carrier can enter a site from which no sink is reachable
415 ToFGraph::EdgeRange out = graph_.OutEdges(i);
416 const ToFGraph::Edge* trap =
417 std::find_if(out.begin(), out.end(), [&](const ToFGraph::Edge& e) {
418 return e.rate > 0.0 && sites[e.to].role != ToFGraph::Role::Sink &&
419 t.index_of[e.to] < 0;
420 });
421 if (trap != out.end()) {
422 throw std::runtime_error(
423 "GraphRandomWalk: site " + std::to_string(trap->to) +
424 " traps carriers (no path to a sink), the time of flight is "
425 "infinite");
426 }
427 }
428 // symmetrization weights, only with detailed balance
429 if (graph_.isReversible()) {
430 Eigen::VectorXd U(m);
431 for (Index k = 0; k < m; ++k) {
432 U[k] = sites[t.site_of[k]].potential;
433 }
434 std::vector<double> sorted(U.data(), U.data() + m);
435 std::nth_element(sorted.begin(), sorted.begin() + m / 2, sorted.end());
436 double Uref = sorted[m / 2];
437 t.half_log_weight = -(U.array() - Uref) / (2.0 * graph_.Temperature());
438 }
439 return t;
440}
441
443 citations::Cite("Chen:2025a");
444 const Index n = graph_.NumberOfSites();
445 const std::vector<ToFGraph::Site>& sites = graph_.Sites();
447 const Index m = Index(tr.site_of.size());
448
449 // Q = diag(D) - W on the transient sites
450 RowBuilder rows(m);
451 for (Index t = 0; t < m; ++t) {
452 Index i = tr.site_of[t];
453 rows.Add(t, graph_.EscapeRate(i));
454 for (const ToFGraph::Edge& e : graph_.OutEdges(i)) {
455 if (e.rate > 0.0 && sites[e.to].role != ToFGraph::Role::Sink) {
456 rows.Add(tr.index_of[e.to], -e.rate);
457 }
458 }
459 rows.FinishRow();
460 }
461 SpMatR Q = rows.Build(m);
462 GeneratorSolver gs(Q, tr.half_log_weight, solver, settings_);
463
464 Eigen::VectorXd tau = gs.Solve(Eigen::VectorXd::Ones(m));
465 Eigen::VectorXd m2 = gs.Solve(2.0 * tau);
466 const std::vector<Index>& sources = graph_.SourceSites();
467 Eigen::VectorXd w = Eigen::VectorXd::Zero(m);
468 for (Index s : sources) {
469 w[tr.index_of[s]] = 1.0 / double(sources.size());
470 }
471 Eigen::VectorXd g = gs.SolveAdjoint(w);
472
473 Result result;
474 result.solver = gs.name;
475 result.carriers = 1;
476 result.unknowns = m;
477 result.backward_error = gs.backward_error;
478 result.refinement_steps = gs.refinement_steps;
479 result.iterations = gs.iterations;
480 result.converged = gs.converged && gs.backward_error < 1e-10;
481
482 Index ns = Index(sources.size());
483 result.tau.resize(ns);
484 result.tau_std.resize(ns);
485 double mean2 = 0.0;
486 for (Index k = 0; k < ns; ++k) {
487 Index t = tr.index_of[sources[k]];
488 result.sources.push_back({sources[k]});
489 result.tau[k] = tau[t];
490 result.tau_std[k] = std::sqrt(std::max(m2[t] - tau[t] * tau[t], 0.0));
491 mean2 += m2[t];
492 }
493 result.tau_mean = result.tau.mean();
494 mean2 /= double(ns);
495 result.tau_std_injection =
496 std::sqrt(std::max(mean2 - result.tau_mean * result.tau_mean, 0.0));
497 result.tau_harmonic = double(ns) / result.tau.cwiseInverse().sum();
498
499 result.occupation_time = Eigen::VectorXd::Zero(n);
500 for (Index t = 0; t < m; ++t) {
501 result.occupation_time[tr.site_of[t]] = g[t];
502 }
503 return result;
504}
505
507 Solver solver) const {
508 citations::Cite("Chen:2025a");
509 const Index n = graph_.NumberOfSites();
510 const std::vector<ToFGraph::Site>& sites = graph_.Sites();
512 const Index m = Index(tr.site_of.size());
513 if (m < 2) {
514 throw std::runtime_error(
515 "GraphRandomWalk: two carriers need at least two transient sites");
516 }
517 const Index M = m * (m - 1) / 2;
518 if (M > settings_.max_states || M > Index(std::numeric_limits<int>::max())) {
519 throw std::runtime_error(
520 "GraphRandomWalk: " + std::to_string(M) +
521 " pair states exceed the limit of " +
522 std::to_string(settings_.max_states) +
523 " (maxstates); use kinetic Monte Carlo for this system");
524 }
525 const std::vector<Index>& sources = graph_.SourceSites();
526 if (sources.size() < 2) {
527 throw std::runtime_error(
528 "GraphRandomWalk: two carriers need at least two source sites");
529 }
530 const bool transit = (observable == Observable::Transit);
531
532 // pair block Q_PP; for Transit also W_PS (pair -> remaining single carrier)
533 RowBuilder pp(M);
534 RowBuilder ps(M);
535 for (Index j = 1; j < m; ++j) {
536 for (Index i = 0; i < j; ++i) {
537 double D = 0.0;
538 const std::array<std::array<Index, 2>, 2> movers = {
539 {{i, j}, {j, i}}}; // {moving carrier, other carrier}
540 for (const auto& mv : movers) {
541 Index a = mv[0];
542 Index b = mv[1];
543 for (const ToFGraph::Edge& e : graph_.OutEdges(tr.site_of[a])) {
544 if (e.rate <= 0.0 || e.to == tr.site_of[b]) {
545 continue; // blocked: site occupied by the other carrier
546 }
547 D += e.rate;
548 if (sites[e.to].role == ToFGraph::Role::Sink) {
549 if (transit) {
550 ps.Add(b, e.rate);
551 }
552 } else {
553 pp.Add(PairRank(tr.index_of[e.to], b), -e.rate);
554 }
555 }
556 }
557 pp.Add(PairRank(i, j), D);
558 pp.FinishRow();
559 ps.FinishRow();
560 }
561 }
562 SpMatR Q = pp.Build(M);
563
564 // symmetrization weights of a pair: product of the site weights
565 Eigen::VectorXd h;
566 if (tr.half_log_weight.size() == m) {
567 h.resize(M);
568 for (Index j = 1; j < m; ++j) {
569 for (Index i = 0; i < j; ++i) {
570 h[PairRank(i, j)] = tr.half_log_weight[i] + tr.half_log_weight[j];
571 }
572 }
573 }
574 GeneratorSolver gs(Q, h, solver, settings_);
575
576 // injection: uniform over pairs of distinct source sites
577 std::vector<Index> src(sources.size());
578 std::transform(sources.begin(), sources.end(), src.begin(),
579 [&tr](Index s) { return tr.index_of[s]; });
580 std::vector<std::array<Index, 2>> pairs;
581 for (std::size_t a = 0; a < src.size(); ++a) {
582 for (std::size_t b = a + 1; b < src.size(); ++b) {
583 pairs.push_back({src[a], src[b]});
584 }
585 }
586 Eigen::VectorXd w = Eigen::VectorXd::Zero(M);
587 for (const auto& pr : pairs) {
588 w[PairRank(pr[0], pr[1])] = 1.0 / double(pairs.size());
589 }
590
591 Result result;
592 result.carriers = 2;
593 result.observable = observable;
594 result.unknowns = M;
595 Eigen::VectorXd tau;
596 Eigen::VectorXd m2;
597 Eigen::VectorXd g;
598 Eigen::VectorXd g_single;
599 std::string single_solver;
600 double single_error = 0.0;
601 if (!transit) {
602 tau = gs.Solve(Eigen::VectorXd::Ones(M));
603 m2 = gs.Solve(2.0 * tau);
604 g = gs.SolveAdjoint(w);
605 } else {
606 // remaining single carrier: Q1 tau_S = 1/2
607 RowBuilder rows(m);
608 for (Index t = 0; t < m; ++t) {
609 Index i = tr.site_of[t];
610 rows.Add(t, graph_.EscapeRate(i));
611 for (const ToFGraph::Edge& e : graph_.OutEdges(i)) {
612 if (e.rate > 0.0 && sites[e.to].role != ToFGraph::Role::Sink) {
613 rows.Add(tr.index_of[e.to], -e.rate);
614 }
615 }
616 rows.FinishRow();
617 }
618 SpMatR Q1 = rows.Build(m);
619 SpMatR W = ps.Build(m);
620 GeneratorSolver gs1(Q1, tr.half_log_weight, solver, settings_);
621 Eigen::VectorXd tau_s = gs1.Solve(Eigen::VectorXd::Constant(m, 0.5));
622 tau = gs.Solve(Eigen::VectorXd::Ones(M) + W * tau_s);
623 g = gs.SolveAdjoint(w);
624 // expected number of transitions into each single-carrier state
625 Eigen::VectorXd inflow = W.transpose() * g;
626 g_single = gs1.SolveAdjoint(inflow);
627 single_solver = gs1.name;
628 single_error = gs1.backward_error;
629 result.iterations += gs1.iterations;
630 result.converged = gs1.converged;
631 }
632 result.solver = gs.name;
633 if (transit && single_solver != gs.name) {
634 result.solver += " (remaining carrier: " + single_solver + ")";
635 }
636 result.backward_error = std::max(gs.backward_error, single_error);
637 result.refinement_steps = gs.refinement_steps;
638 result.iterations += gs.iterations;
639 result.converged =
640 result.converged && gs.converged && result.backward_error < 1e-10;
641
642 Index np = Index(pairs.size());
643 result.tau.resize(np);
644 if (!transit) {
645 result.tau_std.resize(np);
646 }
647 double mean2 = 0.0;
648 for (Index k = 0; k < np; ++k) {
649 Index r = PairRank(pairs[k][0], pairs[k][1]);
650 result.sources.push_back(
651 {tr.site_of[pairs[k][0]], tr.site_of[pairs[k][1]]});
652 result.tau[k] = tau[r];
653 if (!transit) {
654 result.tau_std[k] = std::sqrt(std::max(m2[r] - tau[r] * tau[r], 0.0));
655 mean2 += m2[r];
656 }
657 }
658 result.tau_mean = result.tau.mean();
659 if (!transit) {
660 mean2 /= double(np);
661 result.tau_std_injection =
662 std::sqrt(std::max(mean2 - result.tau_mean * result.tau_mean, 0.0));
663 }
664 result.tau_harmonic = double(np) / result.tau.cwiseInverse().sum();
665
666 // time each site is occupied: sum over the pair states containing it,
667 // plus the single-carrier states for Transit
668 result.occupation_time = Eigen::VectorXd::Zero(n);
669 for (Index j = 1; j < m; ++j) {
670 for (Index i = 0; i < j; ++i) {
671 double t = g[PairRank(i, j)];
672 result.occupation_time[tr.site_of[i]] += t;
673 result.occupation_time[tr.site_of[j]] += t;
674 }
675 }
676 if (transit) {
677 for (Index t = 0; t < m; ++t) {
678 result.occupation_time[tr.site_of[t]] += g_single[t];
679 }
680 }
681 return result;
682}
683
684} // namespace xtp
685} // namespace votca
Result TwoCarriers(Observable observable, Solver solver=Solver::Auto) const
static Solver SolverFromString(const std::string &name)
Result SingleCarrier(Solver solver=Solver::Auto) const
static Observable ObservableFromString(const std::string &name)
[begin, end) of the out-edges of one site
Definition tofgraph.h:133
const Edge * begin() const
Definition tofgraph.h:136
const Edge * end() const
Definition tofgraph.h:137
void Cite(const std::string &key)
marks a reference as used (thread safe; unknown keys are printed as such)
Definition citations.cc:289
Charge transport classes.
Definition ERIs.h:28
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.
Definition scaledsolve.h:44
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
std::vector< std::vector< Index > > sources
Index iterations
iterative solvers: total iterations
double tau_harmonic
harmonic mean over the injection states (eq. 3.4 of the paper)