votca 2026-dev
Loading...
Searching...
No Matches
trapfinder.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 <complex>
24#include <numeric>
25#include <stdexcept>
26
27// Third party includes
28#include <Eigen/Eigenvalues>
29
30// Local VOTCA includes
31#include "votca/xtp/citations.h"
33
34namespace votca {
35namespace xtp {
36
37namespace {
40Eigen::VectorXd Normalized(const Eigen::VectorXcd& v) {
41 Index imax = 0;
42 v.cwiseAbs().maxCoeff(&imax);
43 Eigen::VectorXcd w = v / v(imax);
44 return w.real();
45}
46} // namespace
47
49 const Settings& settings) {
50 citations::Cite("Chen:2025b");
51 if (!(settings.threshold > 0.0 && settings.threshold < 1.0) ||
52 !(settings.gap >= 1.0) || settings.modes < 3 ||
53 !(settings.mass > 0.0 && settings.mass <= 1.0) || settings.max_size < 1) {
54 throw std::runtime_error(
55 "TrapFinder: needs 0 < threshold < 1, gap >= 1, modes >= 3, "
56 "0 < mass <= 1 and max_size >= 1");
57 }
58 // sites with hops
59 const Index n = graph.NumberOfSites();
60 std::vector<Index> local(n, -1);
61 std::vector<Index> sites;
62 for (Index i = 0; i < n; ++i) {
63 if (!graph.getSite(i).ignored && graph.OutEdges(i).size() > 0) {
64 local[i] = Index(sites.size());
65 sites.push_back(i);
66 }
67 }
68 const Index m = Index(sites.size());
69 if (m < 3) {
70 throw std::runtime_error("TrapFinder: needs at least three sites");
71 }
72 if (m > settings.max_sites) {
73 throw std::runtime_error(
74 "TrapFinder: " + std::to_string(m) +
75 " sites; the dense eigenvalue problem is limited to " +
76 std::to_string(settings.max_sites));
77 }
78 // the network must be strongly connected (one closed class): all sites
79 // are reached from the first one along hops and backwards
80 {
81 std::vector<std::vector<Index>> fwd(m), bwd(m);
82 for (Index a = 0; a < m; ++a) {
83 for (const ToFGraph::Edge& e : graph.OutEdges(sites[a])) {
84 Index b = local[e.to];
85 if (b >= 0 && e.rate > 0.0) {
86 fwd[a].push_back(b);
87 bwd[b].push_back(a);
88 }
89 }
90 }
91 for (const auto* g : {&fwd, &bwd}) {
92 std::vector<char> seen(m, 0);
93 std::vector<Index> stack{0};
94 seen[0] = 1;
95 Index count = 1;
96 while (!stack.empty()) {
97 Index a = stack.back();
98 stack.pop_back();
99 for (Index b : (*g)[a]) {
100 if (!seen[b]) {
101 seen[b] = 1;
102 ++count;
103 stack.push_back(b);
104 }
105 }
106 }
107 if (count != m) {
108 throw std::runtime_error(
109 "TrapFinder: the hopping network is not strongly connected (" +
110 std::to_string(m - count) +
111 " sites unreachable); traps are not defined");
112 }
113 }
114 }
115 // transposed generator D - W, scaled by the largest escape rate
116 double scale = 0.0;
117 for (Index i : sites) {
118 scale = std::max(scale, graph.EscapeRate(i));
119 }
120 if (!(scale > 0.0)) {
121 throw std::runtime_error("TrapFinder: no hops");
122 }
123 Eigen::MatrixXd At = Eigen::MatrixXd::Zero(m, m);
124 for (Index a = 0; a < m; ++a) {
125 for (const ToFGraph::Edge& e : graph.OutEdges(sites[a])) {
126 Index b = local[e.to];
127 if (b < 0 || e.rate <= 0.0) {
128 continue;
129 }
130 At(b, a) -= e.rate / scale;
131 At(a, a) += e.rate / scale;
132 }
133 }
134 Eigen::EigenSolver<Eigen::MatrixXd> es(At, true);
135 if (es.info() != Eigen::Success) {
136 throw std::runtime_error("TrapFinder: eigenvalue problem failed");
137 }
138 const Eigen::VectorXcd& lambda = es.eigenvalues();
139 std::vector<Index> order(m);
140 std::iota(order.begin(), order.end(), 0);
141 std::sort(order.begin(), order.end(), [&lambda](Index a, Index b) {
142 return lambda(a).real() < lambda(b).real();
143 });
144 Result res;
145 const Index modes = std::min(settings.modes, m);
146 for (Index k = 0; k < modes; ++k) {
147 res.eigenvalues.push_back(lambda(order[k]).real() * scale);
148 }
149 // stationary distribution: density mode of the zero eigenvalue
150 Eigen::VectorXd pi = Normalized(es.eigenvectors().col(order[0]));
151 pi = pi.cwiseMax(0.0);
152 pi /= pi.sum();
153 res.occupation.assign(n, 0.0);
154 for (Index a = 0; a < m; ++a) {
155 res.occupation[sites[a]] = pi(a);
156 }
157
158 for (Index i = 1; i + 1 < modes; ++i) {
159 double r = res.eigenvalues[i + 1] / res.eigenvalues[i];
160 if (r > res.gap) {
161 res.gap = r;
162 res.slow_modes = i;
163 }
164 }
165
166 // candidates: connected components of the localized parts of the slow
167 // density modes
168 // undirected adjacency of the hopping graph
169 std::vector<std::vector<Index>> adj(m);
170 for (Index a = 0; a < m; ++a) {
171 for (const ToFGraph::Edge& e : graph.OutEdges(sites[a])) {
172 Index b = local[e.to];
173 if (b >= 0 && e.rate > 0.0) {
174 adj[a].push_back(b);
175 adj[b].push_back(a);
176 }
177 }
178 }
179 struct Candidate {
180 std::vector<Index> sites; // local indices, sorted
181 Index mode;
182 };
183 std::vector<Candidate> candidates;
184 std::vector<char> in(m, 0);
185 std::vector<char> seen(m, 0);
186 for (Index k = 1; k <= res.slow_modes; ++k) {
187 Eigen::VectorXd v = Normalized(es.eigenvectors().col(order[k]));
188 // A density mode sums to zero: it moves weight between the trap and the
189 // rest. The trap is the side with fewer sites; when the trap holds most
190 // of the equilibrium weight, the other side has entries of the same
191 // size on the sites that hold the rest of the weight.
192 Index positive = 0;
193 Index negative = 0;
194 const double tiny = 1e-12 * v.cwiseAbs().maxCoeff();
195 for (Index a = 0; a < m; ++a) {
196 positive += v(a) > tiny;
197 negative += v(a) < -tiny;
198 }
199 const double sign = positive <= negative ? 1.0 : -1.0;
200 Eigen::VectorXd side = (sign * v).cwiseMax(0.0);
201 side /= side.maxCoeff();
202 const double side_mass = side.sum();
203 for (Index a = 0; a < m; ++a) {
204 in[a] = side(a) > settings.threshold;
205 seen[a] = 0;
206 }
207 for (Index a = 0; a < m; ++a) {
208 if (!in[a] || seen[a]) {
209 continue;
210 }
211 std::vector<Index> comp{a};
212 seen[a] = 1;
213 for (std::size_t q = 0; q < comp.size(); ++q) {
214 for (Index b : adj[comp[q]]) {
215 if (in[b] && !seen[b]) {
216 seen[b] = 1;
217 comp.push_back(b);
218 }
219 }
220 }
221 // a trap is localized: few sites carrying most of the side's weight
222 double mass = 0.0;
223 for (Index b : comp) {
224 mass += side(b);
225 }
226 if (Index(comp.size()) > settings.max_size ||
227 mass < settings.mass * side_mass) {
228 continue;
229 }
230 std::sort(comp.begin(), comp.end());
231 candidates.push_back({std::move(comp), k});
232 }
233 }
234 // drop duplicates and components contained in others
235 std::sort(
236 candidates.begin(), candidates.end(),
237 [](const Candidate& a, const Candidate& b) {
238 return a.sites.size() > b.sites.size() ||
239 (a.sites.size() == b.sites.size() &&
240 (a.sites < b.sites || (a.sites == b.sites && a.mode < b.mode)));
241 });
242 std::vector<Candidate> traps;
243 for (const Candidate& c : candidates) {
244 bool contained =
245 std::any_of(traps.begin(), traps.end(), [&c](const Candidate& t) {
246 return std::includes(t.sites.begin(), t.sites.end(), c.sites.begin(),
247 c.sites.end());
248 });
249 if (!contained) {
250 traps.push_back(c);
251 }
252 }
253 for (const Candidate& t : traps) {
254 Trap trap;
255 std::vector<char> member(m, 0);
256 for (Index a : t.sites) {
257 member[a] = 1;
258 }
259 double out = 0.0;
260 for (Index a : t.sites) {
261 trap.weight += pi(a);
262 for (const ToFGraph::Edge& e : graph.OutEdges(sites[a])) {
263 Index b = local[e.to];
264 if (b >= 0 && !member[b]) {
265 out += pi(a) * e.rate;
266 }
267 }
268 trap.sites.push_back(sites[a]);
269 }
270 trap.hop_rate = trap.weight > 0.0 ? out / trap.weight : 0.0;
271 // two-state picture of the mode: relaxation rate = release + capture,
272 // weight = capture / (release + capture)
273 trap.relaxation_rate = res.eigenvalues[t.mode];
274 trap.release_rate = trap.relaxation_rate * (1.0 - trap.weight);
275 trap.capture_rate = trap.relaxation_rate * trap.weight;
276 // a trap holds a carrier much longer than the network relaxes otherwise
277 if (trap.release_rate * settings.gap >
278 res.eigenvalues[res.slow_modes + 1]) {
279 continue;
280 }
281 std::sort(trap.sites.begin(), trap.sites.end(), [&graph](Index a, Index b) {
282 return graph.getSite(a).energy < graph.getSite(b).energy;
283 });
284 res.traps.push_back(std::move(trap));
285 }
286 std::sort(res.traps.begin(), res.traps.end(),
287 [](const Trap& a, const Trap& b) {
288 return a.release_rate < b.release_rate;
289 });
290 return res;
291}
292
293} // namespace xtp
294} // namespace votca
Hopping graph for time-of-flight (ToF) transport.
Definition tofgraph.h:77
const Site & getSite(Index i) const
Definition tofgraph.h:151
Index NumberOfSites() const
Definition tofgraph.h:149
EdgeRange OutEdges(Index i) const
Definition tofgraph.h:153
double EscapeRate(Index i) const
sum of the rates of all out-edges
Definition tofgraph.h:163
static Result Find(const ToFGraph &graph, const Settings &settings)
Definition trapfinder.cc:48
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
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
bool ignored
segment type in Options::ignore_types, no edges
Definition tofgraph.h:122
std::vector< double > occupation
Definition trapfinder.h:105
std::vector< Trap > traps
Definition trapfinder.h:104
std::vector< double > eigenvalues
the smallest eigenvalues of -Q (relaxation rates, 1/s)
Definition trapfinder.h:101
std::vector< Index > sites
Definition trapfinder.h:86