votca 2026-dev
Loading...
Searching...
No Matches
tofgraph.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 <limits>
24#include <numeric>
25#include <queue>
26#include <sstream>
27#include <stdexcept>
28
29// VOTCA includes
33
34// Local VOTCA includes
36#include "votca/xtp/segment.h"
37#include "votca/xtp/tofgraph.h"
38
39namespace votca {
40namespace xtp {
41
42namespace {
43double CarrierCharge(QMStateType type) {
44 if (type == QMStateType::Electron) {
45 return -1.0;
46 } else if (type == QMStateType::Hole) {
47 return 1.0;
48 }
49 return 0.0;
50}
51
52bool MatchesPattern(const std::string& pattern, const std::string& name) {
53 return tools::wildcmp(pattern, name) != 0;
54}
55
56std::vector<std::string> SplitTypes(const std::string& list) {
57 return tools::Tokenizer(list, ", \t\n").ToVector();
58}
59} // namespace
60
61ToFGraph::Options ToFGraph::BulkOptions(const std::string& injection_pattern,
62 const std::string& ignore_types) {
63 Options opt;
64 opt.bulk = true;
65 opt.source_thickness = 0.0;
66 opt.sink_thickness = 0.0;
67 opt.source_pattern = (injection_pattern == "*") ? "" : injection_pattern;
68 opt.sink_pattern = "";
69 opt.ignore_types = SplitTypes(ignore_types);
70 return opt;
71}
72
74 Options opt;
75 std::string axis = tof.get("axis").as<std::string>();
76 if (axis == "a") {
77 opt.axis = 0;
78 } else if (axis == "b") {
79 opt.axis = 1;
80 } else if (axis == "c") {
81 opt.axis = 2;
82 } else {
83 throw std::runtime_error("ToFGraph: axis must be a, b or c, not " + axis);
84 }
86 tof.get("source.thickness").as<double>() * tools::conv::nm2bohr;
87 opt.sink_thickness =
88 tof.get("sink.thickness").as<double>() * tools::conv::nm2bohr;
89 opt.source_pattern = tof.get("source.pattern").as<std::string>();
90 opt.sink_pattern = tof.get("sink.pattern").as<std::string>();
91 opt.ignore_types = SplitTypes(
92 tof.ifExistsReturnElseReturnDefault<std::string>("ignoretypes", ""));
93 return opt;
94}
95
96ToFGraph::ToFGraph(const Topology& top, const Options& opt, QMStateType carrier,
97 double temperature, const Eigen::Vector3d& field)
98 : opt_(opt), carrier_(carrier), temperature_(temperature), field_(field) {
99 if (!carrier_.isKMCState()) {
100 throw std::runtime_error("ToFGraph: " + carrier_.ToLongString() +
101 " is not a hopping state");
102 }
103 if (temperature_ <= 0.0) {
104 throw std::runtime_error("ToFGraph: the temperature must be positive");
105 }
106 if (opt_.axis < 0 || opt_.axis > 2) {
107 throw std::runtime_error("ToFGraph: axis must be 0, 1 or 2");
108 }
109 bool source_criterion =
110 opt_.source_thickness > 0.0 || !opt_.source_pattern.empty();
111 bool sink_criterion = opt_.sink_thickness > 0.0 || !opt_.sink_pattern.empty();
112 if (opt_.bulk) {
113 if (opt_.source_thickness > 0.0 || sink_criterion) {
114 throw std::runtime_error(
115 "ToFGraph: a bulk graph has no slabs and no sinks");
116 }
117 } else if (!source_criterion || !sink_criterion) {
118 throw std::runtime_error(
119 "ToFGraph: source and sink each need a slab thickness and/or a "
120 "segment pattern");
121 }
122 if (top.Segments().empty()) {
123 throw std::runtime_error("ToFGraph: the topology contains no segments");
124 }
125 SetupSites(top);
126 SetupEdges(top);
127 if (opt_.bulk) {
128 SetupBulkSources();
129 } else {
130 FindReachability();
131 }
132}
133
135 const Eigen::Matrix3d& box = top.getBox(); // columns: box vectors a, b, c
136 double volume = std::abs(box.determinant());
137 if (!(volume > 0.0)) {
138 throw std::runtime_error(
139 "ToFGraph: the box is degenerate, a periodic box is required");
140 }
141 if (!opt_.bulk) {
142 const Eigen::Vector3d u = box.col((opt_.axis + 1) % 3);
143 const Eigen::Vector3d v = box.col((opt_.axis + 2) % 3);
144 normal_ = u.cross(v).normalized();
145 if (normal_.dot(box.col(opt_.axis)) < 0.0) {
146 normal_ = -normal_;
147 }
148 thickness_ = box.col(opt_.axis).dot(normal_);
149 }
150 if (!opt_.bulk && opt_.source_thickness + opt_.sink_thickness >= thickness_) {
151 throw std::runtime_error(
152 "ToFGraph: source and sink slabs together are thicker than the box");
153 }
154
155 const Eigen::Matrix3d inv = box.inverse();
156 const double charge = CarrierCharge(carrier_);
157 const std::vector<Segment>& segs = top.Segments();
158 sites_.resize(segs.size());
159 for (Index i = 0; i < Index(segs.size()); ++i) {
160 const Segment& seg = segs[i];
161 if (seg.getId() != i) {
162 throw std::runtime_error(
163 "ToFGraph: segment ids must be consecutive and start at 0");
164 }
165 Site& site = sites_[i];
166 site.segment = i;
167 Eigen::Vector3d frac = inv * seg.getPos();
168 frac = frac.array() - frac.array().floor();
169 // floor can return 1 for -1e-17
170 for (Index d = 0; d < 3; ++d) {
171 if (frac[d] >= 1.0) {
172 frac[d] = 0.0;
173 }
174 }
175 site.pos = box * frac;
176 site.x = frac[opt_.axis] * thickness_; // 0 for a bulk graph
177 site.energy = seg.getSiteEnergy(carrier_);
178 site.potential = site.energy - charge * field_.dot(site.pos);
179 site.ignored =
180 std::any_of(opt_.ignore_types.begin(), opt_.ignore_types.end(),
181 [&seg](const std::string& p) {
182 return MatchesPattern(p, seg.getType());
183 });
184 if (site.ignored) {
186 continue; // role Bulk, no edges
187 }
188
189 // a criterion that is not given does not restrict
190 const bool source_slab =
191 !(opt_.source_thickness > 0.0) || site.x < opt_.source_thickness;
192 const bool source_type = opt_.source_pattern.empty() ||
193 MatchesPattern(opt_.source_pattern, seg.getType());
194 const bool sink_slab = !(opt_.sink_thickness > 0.0) ||
195 site.x >= thickness_ - opt_.sink_thickness;
196 const bool sink_type = opt_.sink_pattern.empty() ||
197 MatchesPattern(opt_.sink_pattern, seg.getType());
198 const bool source = source_slab && source_type;
199 const bool sink = sink_slab && sink_type && !opt_.bulk;
200 if (source && sink) {
201 throw std::runtime_error("ToFGraph: segment " + std::to_string(i) +
202 " is selected as source and as sink");
203 }
204 if (source) {
205 site.role = Role::Source;
206 } else if (sink) {
207 site.role = Role::Sink;
208 sinks_.push_back(i);
209 }
210 }
211 if (sinks_.empty() && !opt_.bulk) {
212 throw std::runtime_error("ToFGraph: no sink sites selected");
213 }
214}
215
217 const Eigen::Matrix3d& box = top.getBox();
218 const Eigen::Matrix3d inv = box.inverse();
220 const QMNBList& nblist = top.NBList();
221
222 struct RawEdge {
223 Index from;
224 Edge edge;
225 };
226 std::vector<RawEdge> raw;
227 raw.reserve(2 * nblist.size());
228 db_deviation_ = 0.0;
229 for (const QMPair* pair : nblist) {
230 Index i = pair->Seg1()->getId();
231 Index j = pair->Seg2()->getId();
232 const Eigen::Vector3d& R = pair->R();
233 // image shift between the pair vector and the wrapped positions
234 Eigen::Vector3d shift = inv * (R - (sites_[j].pos - sites_[i].pos));
235 Eigen::Vector3d rounded = shift.array().round();
236 if ((shift - rounded).cwiseAbs().maxCoeff() > 1e-6) {
237 throw std::runtime_error(
238 "ToFGraph: the vector of pair " + std::to_string(pair->getId()) +
239 " does not connect its segments or a periodic image of them");
240 }
241 if (sites_[i].ignored || sites_[j].ignored) {
242 continue;
243 }
244 if (!opt_.bulk && rounded[opt_.axis] != 0.0) {
245 ++cut_pairs_;
246 continue;
247 }
248 Rate_Engine::PairRates rates = engine.Rate(*pair, carrier_);
249 if (!std::isfinite(rates.rate12) || !std::isfinite(rates.rate21) ||
250 rates.rate12 < 0.0 || rates.rate21 < 0.0) {
251 throw std::runtime_error("ToFGraph: invalid rate for pair " +
252 std::to_string(pair->getId()));
253 }
254 if (rates.rate12 > 0.0 && rates.rate21 > 0.0) {
255 double lhs = std::log(rates.rate12 / rates.rate21);
256 double rhs = (sites_[i].potential - sites_[j].potential) / temperature_;
257 db_deviation_ = std::max(db_deviation_, std::abs(lhs - rhs));
258 } else if (rates.rate12 > 0.0 || rates.rate21 > 0.0) {
259 db_deviation_ = std::numeric_limits<double>::infinity();
260 }
261 raw.push_back({i, Edge{j, rates.rate12, R, pair->getId()}});
262 raw.push_back({j, Edge{i, rates.rate21, -R, pair->getId()}});
263 }
265
266 Index n = NumberOfSites();
267 offsets_.assign(n + 1, 0);
268 for (const RawEdge& e : raw) {
269 ++offsets_[e.from + 1];
270 }
271 for (Index i = 0; i < n; ++i) {
272 offsets_[i + 1] += offsets_[i];
273 }
274 edges_.resize(raw.size());
275 std::vector<Index> fill(offsets_.begin(), offsets_.end() - 1);
276 for (const RawEdge& e : raw) {
277 edges_[fill[e.from]++] = e.edge;
278 }
279 escape_rates_.assign(n, 0.0);
280 for (Index i = 0; i < n; ++i) {
281 for (const Edge& e : OutEdges(i)) {
282 escape_rates_[i] += e.rate;
283 }
284 }
285}
286
288 Index n = NumberOfSites();
289 // incoming edges with positive rate
290 std::vector<std::vector<Index>> incoming(n);
291 for (Index i = 0; i < n; ++i) {
292 for (const Edge& e : OutEdges(i)) {
293 if (e.rate > 0.0) {
294 incoming[e.to].push_back(i);
295 }
296 }
297 }
298 std::queue<Index> queue;
299 for (Index s : sinks_) {
300 sites_[s].reaches_sink = true;
301 queue.push(s);
302 }
303 while (!queue.empty()) {
304 Index j = queue.front();
305 queue.pop();
306 for (Index i : incoming[j]) {
307 if (!sites_[i].reaches_sink) {
308 sites_[i].reaches_sink = true;
309 queue.push(i);
310 }
311 }
312 }
314 double mean_x = 0.0;
315 for (Index i = 0; i < n; ++i) {
316 if (!sites_[i].reaches_sink) {
318 }
319 if (sites_[i].role == Role::Source) {
320 if (sites_[i].reaches_sink) {
321 sources_.push_back(i);
322 mean_x += sites_[i].x;
323 } else {
324 disconnected_sources_.push_back(i);
325 }
326 }
327 }
328 if (sources_.empty()) {
329 throw std::runtime_error(
330 "ToFGraph: no source site from which a sink can be reached (" +
331 std::to_string(disconnected_sources_.size()) +
332 " source sites without connection to a sink)");
333 }
334 mean_x /= double(sources_.size());
335 double min_sink_x = std::accumulate(
336 sinks_.begin(), sinks_.end(), std::numeric_limits<double>::max(),
337 [this](double m, Index s) { return std::min(m, sites_[s].x); });
338 length_ = min_sink_x - mean_x;
339 if (length_ <= 0.0) {
340 throw std::runtime_error(
341 "ToFGraph: the sink sites are not further along the transport axis "
342 "than the source sites");
343 }
344}
345
347 for (Index i = 0; i < NumberOfSites(); ++i) {
348 if (sites_[i].role == Role::Source) {
349 sources_.push_back(i);
350 }
351 }
352 if (sources_.empty()) {
353 throw std::runtime_error(
354 "ToFGraph: no injection site matches the pattern '" +
355 opt_.source_pattern + "'");
356 }
358}
359
360std::string ToFGraph::Summary() const {
361 std::stringstream s;
362 double nm = tools::conv::bohr2nm;
363 const char* axes[] = {"a", "b", "c"};
364 if (opt_.bulk) {
365 s << "Bulk hopping graph (periodic in a, b and c, no sinks) for "
366 << carrier_.ToLongString() << "s\n";
367 s << " sites: " << NumberOfSites() << " (" << ignored_sites_
368 << " of ignored types), hops: " << NumberOfEdges() << "\n";
369 s << " injection sites: " << sources_.size() << "\n";
370 s << " detailed balance: " << (reversible_ ? "yes" : "no")
371 << " (max deviation of ln(w_ij/w_ji) " << db_deviation_ << ")";
372 return s.str();
373 }
374 s << "ToF graph for " << carrier_.ToLongString() << "s\n";
375 s << " periodicity cut across box vector " << axes[opt_.axis] << ", normal ("
376 << normal_.x() << " " << normal_.y() << " " << normal_.z()
377 << "), thickness " << thickness_ * nm << " nm\n";
378 s << " sites: " << NumberOfSites() << " (" << ignored_sites_
379 << " of ignored types), hops: " << NumberOfEdges()
380 << ", pairs cut at the periodic boundary: " << cut_pairs_ << "\n";
381 s << " sources: " << sources_.size() << " connected, "
382 << disconnected_sources_.size() << " without path to a sink (excluded)\n";
383 s << " sinks: " << sinks_.size()
384 << ", sites without path to a sink: " << sites_without_sink_ << "\n";
385 s << " transport length (mean source to first sink): " << length_ * nm
386 << " nm\n";
387 s << " detailed balance: " << (reversible_ ? "yes" : "no")
388 << " (max deviation of ln(w_ij/w_ji) " << db_deviation_ << ")";
389 return s.str();
390}
391
392} // namespace xtp
393} // namespace votca
Index size() const
Definition pairlist.h:53
class to manage program options with xml serialization functionality
Definition property.h:55
Property & get(const std::string &key)
get existing property
Definition property.cc:79
T as() const
return value as type
Definition property.h:283
T ifExistsReturnElseReturnDefault(const std::string &key, T defaultvalue) const
Definition property.h:332
const std::string & getType() const
const Eigen::Vector3d & getPos() const
PairRates Rate(const QMPair &pair, QMStateType carriertype) const
double getSiteEnergy(QMStateType state) const
Definition segment.h:79
void SetupSites(const Topology &top)
Definition tofgraph.cc:134
Eigen::Vector3d normal_
Definition tofgraph.h:218
QMStateType carrier_
Definition tofgraph.h:204
Eigen::Vector3d field_
Definition tofgraph.h:206
std::vector< double > escape_rates_
Definition tofgraph.h:211
std::vector< Index > disconnected_sources_
Definition tofgraph.h:213
std::vector< Site > sites_
Definition tofgraph.h:208
Index NumberOfSites() const
Definition tofgraph.h:149
std::vector< Index > offsets_
Definition tofgraph.h:210
std::vector< Index > sources_
Definition tofgraph.h:212
std::vector< Edge > edges_
Definition tofgraph.h:209
ToFGraph(const Topology &top, const Options &opt, QMStateType carrier, double temperature, const Eigen::Vector3d &field)
temperature as kT in Hartree, field in Hartree/bohr (as Rate_Engine)
Definition tofgraph.cc:96
static Options BulkOptions(const std::string &injection_pattern, const std::string &ignore_types)
Definition tofgraph.cc:61
Index NumberOfEdges() const
Definition tofgraph.h:157
EdgeRange OutEdges(Index i) const
Definition tofgraph.h:153
std::vector< Index > sinks_
Definition tofgraph.h:214
static Options OptionsFromProperty(const tools::Property &tof)
Options from the "tof" subpackage (subpackages/tof.xml), lengths in nm.
Definition tofgraph.cc:73
void SetupEdges(const Topology &top)
Definition tofgraph.cc:216
std::string Summary() const
human readable summary
Definition tofgraph.cc:360
Container for segments and box and atoms.
Definition topology.h:41
std::vector< Segment > & Segments()
Definition topology.h:58
const Eigen::Matrix3d & getBox() const
Definition topology.h:64
QMNBList & NBList()
Definition topology.h:70
const double bohr2nm
Definition constants.h:46
const double nm2bohr
Definition constants.h:47
int wildcmp(const char *wild, const char *string)
Definition tokenizer.cc:28
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
std::vector< std::string > ignore_types
segment type wildcards of segments without edges
Definition tofgraph.h:94
Index axis
box vector across which the periodicity is cut (0: a, 1: b, 2: c)
Definition tofgraph.h:81
double potential
energy + field term, U = E - q F.r: hops release U_i - U_j
Definition tofgraph.h:120
double energy
site energy of the carrier
Definition tofgraph.h:118
bool ignored
segment type in Options::ignore_types, no edges
Definition tofgraph.h:122
double x
coordinate along the transport normal, in [0, thickness)
Definition tofgraph.h:113
Eigen::Vector3d pos
position with the fractional coordinates wrapped into [0,1)
Definition tofgraph.h:111