29#include <Eigen/IterativeLinearSolvers>
30#include <Eigen/SparseCholesky>
31#include <Eigen/SparseLU>
42using SpMat = Eigen::SparseMatrix<double>;
43using SpMatR = Eigen::SparseMatrix<double, Eigen::RowMajor>;
44using SolveFn = std::function<Eigen::VectorXd(
const Eigen::VectorXd&)>;
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();
62 Eigen::VectorXd
z = dinv.cwiseProduct(r);
63 Eigen::VectorXd d =
z;
64 Eigen::VectorXd Ad(b.size());
66 const double threshold2 =
67 settings.tolerance * settings.tolerance * bnorm * bnorm;
72 const double floor2 = 1e6 * threshold2;
73 double best2 = r.squaredNorm();
74 Index last_improvement = 0;
75 while (r.squaredNorm() > threshold2) {
76 if (its >= settings.max_iterations) {
80 if (best2 < floor2 && its - last_improvement > 1000) {
84 double alpha = rz / d.dot(Ad);
87 z = dinv.cwiseProduct(r);
88 double rz_new = r.dot(z);
89 d =
z + (rz_new / rz) * d;
92 double r2 = r.squaredNorm();
93 if (r2 < 0.81 * best2) {
95 last_improvement = its;
106class GeneratorSolver {
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();
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");
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");
131 if (solver == Solver::Auto) {
132 bool direct = m <= settings_.direct_limit;
134 solver = direct ? Solver::Cholesky : Solver::CG;
136 solver = direct ? Solver::LU : Solver::BiCGSTAB;
139 iterative_ = (solver == Solver::CG || solver == Solver::BiCGSTAB);
142 p_ = h.array().exp();
143 pinv_ = p_.cwiseInverse();
145 SpMatR
S = p_.asDiagonal() * Q_ * pinv_.asDiagonal();
146 SpMatR St =
S.transpose();
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");
155 solveS_ = [
this](
const Eigen::VectorXd& b) {
156 return Eigen::VectorXd(ldlt_.solve(b));
159 name =
"cg (Jacobi preconditioned, symmetrized generator)";
161 solveS_ = [
this](
const Eigen::VectorXd& b) {
164 Eigen::VectorXd
y = PCG(S_, b, settings_, its, ok);
166 converged = converged && ok;
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());
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());
178 }
else if (solver == Solver::LU) {
181 lu_.analyzePattern(Qc_);
183 if (lu_.info() != Eigen::Success) {
184 throw std::runtime_error(
"GraphRandomWalk: LU factorization failed: " +
185 lu_.lastErrorMessage());
187 solve_ = [
this](
const Eigen::VectorXd& b) {
188 return Eigen::VectorXd(lu_.solve(b));
190 solve_adjoint_ = [
this](
const Eigen::VectorXd& b) {
191 return Eigen::VectorXd(lu_.transpose().solve(b));
194 name =
"bicgstab (incomplete LU preconditioned)";
195 for (
auto* b : {&bicg_, &bicg_adjoint_}) {
196 b->setTolerance(settings_.tolerance);
197 b->setMaxIterations(settings_.max_iterations);
200 b->preconditioner().setDroptol(1
e-4);
201 b->preconditioner().setFillfactor(5);
207 bicg_adjoint_.compute(Qtc_);
208 auto make = [
this](BiCG& b) {
209 return [
this, &b](
const Eigen::VectorXd& rhs) {
215 iterations += b.iterations();
220 converged = converged && b.iterations() < b.maxIterations();
224 solve_ = make(bicg_);
225 solve_adjoint_ = make(bicg_adjoint_);
229 Eigen::VectorXd Solve(
const Eigen::VectorXd& b) {
230 return Refine(Q_, Qabs_, solve_, b);
232 Eigen::VectorXd SolveAdjoint(
const Eigen::VectorXd& b) {
233 return Refine(Qt_, Qtabs_, solve_adjoint_, b);
237 Index iterations = 0;
238 bool converged =
true;
239 double backward_error = 0.0;
240 Index refinement_steps = 0;
243 using BiCG = Eigen::BiCGSTAB<SpMat, Eigen::IncompleteLUT<double>>;
248 Eigen::VectorXd
Refine(
const SpMatR& A,
const SpMatR& Aabs,
249 const SolveFn& solve,
const Eigen::VectorXd& b) {
253 iterative_ ? std::max(1
e-12, 100.0 * settings_.tolerance) : 1
e-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();
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]);
263 return std::make_pair(
e, Eigen::VectorXd(r));
265 Eigen::VectorXd
x = solve(b);
267 auto [err, r] = error(x);
269 while (err > target && steps < 5) {
270 Eigen::VectorXd x_new =
x + solve(r);
271 auto [err_new, r_new] = error(x_new);
273 if (err_new > 0.5 * err) {
284 if (!
x.allFinite()) {
285 throw std::runtime_error(
"GraphRandomWalk: solution is not finite");
287 backward_error = std::max(backward_error, err);
288 refinement_steps = std::max(refinement_steps, steps);
297 bool iterative_ =
false;
299 Eigen::VectorXd pinv_;
303 Eigen::SimplicialLDLT<SpMat, Eigen::Lower, Eigen::AMDOrdering<int>> ldlt_;
304 Eigen::SparseLU<SpMat, Eigen::COLAMDOrdering<int>> lu_;
309 SolveFn solve_adjoint_;
315 explicit RowBuilder(
Index rows) : rows_(rows) {
316 outer_.reserve(rows + 1);
319 void Add(
Index col,
double value) { row_.emplace_back(col, value); }
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;
326 inner_.push_back(row_[k].first);
327 values_.push_back(row_[k].second);
331 outer_.push_back(
Index(inner_.size()));
333 SpMatR Build(
Index cols)
const {
334 if (
Index(outer_.size()) != rows_ + 1) {
335 throw std::runtime_error(
"RowBuilder: incomplete matrix");
337 SpMatR A(rows_, cols);
338 A.reserve(
Index(inner_.size()));
339 for (
Index i = 0; i < rows_; ++i) {
341 for (
Index k = outer_[i]; k < outer_[i + 1]; ++k) {
342 A.insertBack(i, inner_[k]) = values_[k];
351 std::vector<std::pair<Index, double>> row_;
352 std::vector<Index> outer_;
353 std::vector<Index> inner_;
354 std::vector<double> values_;
361 return j * (j - 1) / 2 + i;
366 const std::string& name) {
367 if (name ==
"auto") {
369 }
else if (name ==
"cholesky") {
371 }
else if (name ==
"lu") {
373 }
else if (name ==
"cg") {
375 }
else if (name ==
"bicgstab") {
378 throw std::runtime_error(
"GraphRandomWalk: unknown solver '" + name +
379 "', use auto, cholesky, lu, cg or bicgstab");
383 const std::string& name) {
384 if (name ==
"first") {
386 }
else if (name ==
"transit") {
389 throw std::runtime_error(
"GraphRandomWalk: unknown observable '" + name +
390 "', use first or transit");
395 const std::vector<ToFGraph::Site>& sites =
graph_.Sites();
399 for (
Index i = 0; i < n; ++i) {
407 throw std::runtime_error(
"GraphRandomWalk: no transient sites");
410 if (!(
graph_.EscapeRate(i) > 0.0)) {
411 throw std::runtime_error(
"GraphRandomWalk: site " + std::to_string(i) +
418 return e.rate > 0.0 && sites[e.to].role != ToFGraph::Role::Sink &&
419 t.index_of[e.to] < 0;
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 "
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;
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];
445 const std::vector<ToFGraph::Site>& sites =
graph_.Sites();
451 for (
Index t = 0; t < m; ++t) {
453 rows.Add(t,
graph_.EscapeRate(i));
461 SpMatR Q = rows.Build(m);
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());
471 Eigen::VectorXd g = gs.SolveAdjoint(w);
480 result.
converged = gs.converged && gs.backward_error < 1
e-10;
483 result.
tau.resize(ns);
486 for (
Index k = 0; k < ns; ++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));
500 for (
Index t = 0; t < m; ++t) {
510 const std::vector<ToFGraph::Site>& sites =
graph_.Sites();
514 throw std::runtime_error(
515 "GraphRandomWalk: two carriers need at least two transient sites");
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 " +
523 " (maxstates); use kinetic Monte Carlo for this system");
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");
535 for (
Index j = 1; j < m; ++j) {
536 for (
Index i = 0; i < j; ++i) {
538 const std::array<std::array<Index, 2>, 2> movers = {
540 for (
const auto& mv : movers) {
544 if (
e.rate <= 0.0 ||
e.to == tr.
site_of[b]) {
553 pp.Add(PairRank(tr.
index_of[
e.to], b), -
e.rate);
557 pp.Add(PairRank(i, j),
D);
562 SpMatR Q = pp.Build(M);
568 for (
Index j = 1; j < m; ++j) {
569 for (
Index i = 0; i < j; ++i) {
574 GeneratorSolver gs(Q, h, solver,
settings_);
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]});
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());
598 Eigen::VectorXd g_single;
599 std::string single_solver;
600 double single_error = 0.0;
602 tau = gs.Solve(Eigen::VectorXd::Ones(M));
603 m2 = gs.Solve(2.0 * tau);
604 g = gs.SolveAdjoint(w);
608 for (
Index t = 0; t < m; ++t) {
610 rows.Add(t,
graph_.EscapeRate(i));
618 SpMatR Q1 = rows.Build(m);
619 SpMatR W = ps.Build(m);
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);
625 Eigen::VectorXd inflow = W.transpose() * g;
626 g_single = gs1.SolveAdjoint(inflow);
627 single_solver = gs1.name;
628 single_error = gs1.backward_error;
633 if (transit && single_solver != gs.name) {
634 result.
solver +=
" (remaining carrier: " + single_solver +
")";
636 result.
backward_error = std::max(gs.backward_error, single_error);
643 result.
tau.resize(np);
648 for (
Index k = 0; k < np; ++k) {
649 Index r = PairRank(pairs[k][0], pairs[k][1]);
652 result.
tau[k] = tau[r];
654 result.
tau_std[k] = std::sqrt(std::max(m2[r] - tau[r] * tau[r], 0.0));
669 for (
Index j = 1; j < m; ++j) {
670 for (
Index i = 0; i < j; ++i) {
671 double t = g[PairRank(i, j)];
677 for (
Index t = 0; t < m; ++t) {
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)
Transient FindTransient() const
[begin, end) of the out-edges of one site
const Edge * begin() const
void Cite(const std::string &key)
marks a reference as used (thread safe; unknown keys are printed as such)
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.
std::vector< std::vector< Index > > sources
Index iterations
iterative solvers: total iterations
Eigen::VectorXd occupation_time
double tau_harmonic
harmonic mean over the injection states (eq. 3.4 of the paper)
std::vector< Index > index_of
std::vector< Index > site_of
Eigen::VectorXd half_log_weight