votca 2026-dev
Loading...
Searching...
No Matches
stochasticnetwork.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 <iomanip>
24#include <limits>
25#include <sstream>
26#include <stdexcept>
27
28// Third party includes
29#include <boost/math/special_functions/erf.hpp>
30#include <boost/math/special_functions/gamma.hpp>
31
32// VOTCA includes
35
36// Local VOTCA includes
37#include "votca/xtp/citations.h"
39
40namespace votca {
41namespace xtp {
42
43// ---------------------------------------------------------------- random
44
45ModelRandom::ModelRandom(std::uint64_t seed, std::uint64_t stream) {
46 // std::seed_seq and mt19937_64 are fully specified by the standard
47 std::seed_seq seq{
48 std::uint32_t(seed & 0xffffffffu), std::uint32_t(seed >> 32),
49 std::uint32_t(stream & 0xffffffffu), std::uint32_t(stream >> 32)};
50 engine_.seed(seq);
51}
52
54 if (has_spare_) {
55 has_spare_ = false;
56 return spare_;
57 }
58 double r = std::sqrt(-2.0 * std::log(Uniform()));
59 double phi = 2.0 * tools::conv::Pi * Uniform();
60 spare_ = r * std::sin(phi);
61 has_spare_ = true;
62 return r * std::cos(phi);
63}
64
66 if (!(mean >= 0.0) || !std::isfinite(mean)) {
67 throw std::runtime_error("ModelRandom::Poisson: mean must be >= 0");
68 }
69 // sum of Poisson variables with means <= 500 (Knuth's product of uniforms)
70 Index n = 0;
71 while (mean > 0.0) {
72 double m = std::min(mean, 500.0);
73 mean -= m;
74 const double limit = std::exp(-m);
75 double p = Uniform();
76 while (p > limit) {
77 ++n;
78 p *= Uniform();
79 }
80 }
81 return n;
82}
83
85 if (n <= 0) {
86 throw std::runtime_error("ModelRandom::Integer: n must be positive");
87 }
88 const std::uint64_t un = std::uint64_t(n);
89 const std::uint64_t limit = std::numeric_limits<std::uint64_t>::max() -
90 std::numeric_limits<std::uint64_t>::max() % un;
91 std::uint64_t x;
92 do {
93 x = engine_();
94 } while (x >= limit);
95 return Index(x % un);
96}
97
98// ---------------------------------------------------------------- cells
99
100PeriodicCells::PeriodicCells(const Eigen::Vector3d& box, double radius,
101 const std::vector<Eigen::Vector3d>& points)
102 : box_(box), points_(points) {
103 if (!(box.minCoeff() > 0.0)) {
104 throw std::runtime_error("PeriodicCells: box lengths must be positive");
105 }
106 if (!(radius > 0.0) || !(2.0 * radius < box.minCoeff())) {
107 throw std::runtime_error(
108 "PeriodicCells: the search radius must be positive and smaller than "
109 "half the box");
110 }
111 for (Index d = 0; d < 3; ++d) {
112 n_[d] = std::max(Index(1), Index(std::floor(box[d] / radius)));
113 }
114 // not many more cells than points (larger cells are fine for Within)
115 while (double(n_[0]) * double(n_[1]) * double(n_[2]) >
116 2.0 * double(points.size()) + 27.0) {
117 Index d = Index(std::max_element(n_.begin(), n_.end()) - n_.begin());
118 n_[d] = std::max(Index(1), n_[d] / 2);
119 }
120 for (Index d = 0; d < 3; ++d) {
121 cell_[d] = box[d] / double(n_[d]);
122 }
123 Index ncells = n_[0] * n_[1] * n_[2];
124 std::vector<Index> cell_of(points.size());
125 start_.assign(ncells + 1, 0);
126 std::array<Index, 3> c;
127 for (Index i = 0; i < Index(points.size()); ++i) {
128 cell_of[i] = CellIndex(points[i], c);
129 ++start_[cell_of[i] + 1];
130 }
131 for (Index k = 0; k < ncells; ++k) {
132 start_[k + 1] += start_[k];
133 }
134 members_.resize(points.size());
135 std::vector<Index> fill(start_.begin(), start_.end() - 1);
136 for (Index i = 0; i < Index(points.size()); ++i) {
137 members_[fill[cell_of[i]]++] = i;
138 }
139}
140
141Index PeriodicCells::CellIndex(const Eigen::Vector3d& p,
142 std::array<Index, 3>& c) const {
143 for (Index d = 0; d < 3; ++d) {
144 double x = p[d] - box_[d] * std::floor(p[d] / box_[d]);
145 Index k = Index(std::floor(x / cell_[d]));
146 c[d] = std::min(std::max(k, Index(0)), n_[d] - 1);
147 }
148 return (c[0] * n_[1] + c[1]) * n_[2] + c[2];
149}
150
151template <class F>
152void PeriodicCells::Shell(const std::array<Index, 3>& c, Index s, F&& f) const {
153 // offsets -lo..hi cover every cell exactly once also for few cells
154 std::array<Index, 3> lo, hi;
155 for (Index d = 0; d < 3; ++d) {
156 lo[d] = std::min(s, (n_[d] - 1) / 2);
157 hi[d] = std::min(s, n_[d] - 1 - (n_[d] - 1) / 2);
158 }
159 for (Index o0 = -lo[0]; o0 <= hi[0]; ++o0) {
160 for (Index o1 = -lo[1]; o1 <= hi[1]; ++o1) {
161 for (Index o2 = -lo[2]; o2 <= hi[2]; ++o2) {
162 if (std::max({std::abs(o0), std::abs(o1), std::abs(o2)}) != s) {
163 continue;
164 }
165 Index k0 = (c[0] + o0 + n_[0]) % n_[0];
166 Index k1 = (c[1] + o1 + n_[1]) % n_[1];
167 Index k2 = (c[2] + o2 + n_[2]) % n_[2];
168 f((k0 * n_[1] + k1) * n_[2] + k2);
169 }
170 }
171 }
172}
173
174Eigen::Vector3d PeriodicCells::Connection(const Eigen::Vector3d& r1,
175 const Eigen::Vector3d& r2) const {
176 Eigen::Vector3d d = r2 - r1;
177 for (Index k = 0; k < 3; ++k) {
178 // points in the box: one image shift at most
179 if (d[k] > 0.5 * box_[k]) {
180 d[k] -= box_[k];
181 } else if (d[k] < -0.5 * box_[k]) {
182 d[k] += box_[k];
183 }
184 if (std::abs(d[k]) > 0.5 * box_[k]) {
185 d[k] -= box_[k] * std::round(d[k] / box_[k]);
186 }
187 }
188 return d;
189}
190
192 const Eigen::Vector3d& p, double radius,
193 const std::function<void(Index, const Eigen::Vector3d&, double)>& f) const {
194 if (radius > cell_.minCoeff() * (1.0 + 1e-12)) {
195 throw std::runtime_error(
196 "PeriodicCells::Within: radius larger than the cell list radius");
197 }
198 std::array<Index, 3> c;
199 CellIndex(p, c);
200 for (Index s = 0; s <= 1; ++s) {
201 Shell(c, s, [&](Index cell) {
202 for (Index k = start_[cell]; k < start_[cell + 1]; ++k) {
203 Index j = members_[k];
204 Eigen::Vector3d conn = Connection(p, points_[j]);
205 double dist = conn.norm();
206 if (dist < radius) {
207 f(j, conn, dist);
208 }
209 }
210 });
211 }
212}
213
214std::vector<Index> PeriodicCells::Nearest(Index i, Index l) const {
215 return NearestTo(points_[i], l);
216}
217
218std::vector<Index> PeriodicCells::NearestTo(const Eigen::Vector3d& p,
219 Index l) const {
220 const Index n = Index(points_.size());
221 if (l > n) {
222 throw std::runtime_error("PeriodicCells::Nearest: more neighbours (" +
223 std::to_string(l) + ") than points (" +
224 std::to_string(n) + ")");
225 }
226 if (l <= 0) {
227 return {};
228 }
229 std::array<Index, 3> c;
230 CellIndex(p, c);
231 std::vector<std::pair<double, Index>> cand;
232 const double inf = std::numeric_limits<double>::infinity();
233 for (Index s = 0;; ++s) {
234 Shell(c, s, [&](Index cell) {
235 for (Index k = start_[cell]; k < start_[cell + 1]; ++k) {
236 Index j = members_[k];
237 cand.emplace_back(Connection(p, points_[j]).norm(), j);
238 }
239 });
240 // every point outside the shells 0..s is farther than `guaranteed`
241 double guaranteed = inf;
242 bool all = true;
243 for (Index d = 0; d < 3; ++d) {
244 Index full = std::max((n_[d] - 1) / 2, n_[d] - 1 - (n_[d] - 1) / 2);
245 if (s < full) {
246 all = false;
247 guaranteed = std::min(guaranteed, double(s) * cell_[d]);
248 }
249 }
250 if (all) {
251 break;
252 }
253 if (Index(cand.size()) >= l &&
254 std::count_if(cand.begin(), cand.end(),
255 [guaranteed](const std::pair<double, Index>& x) {
256 return x.first <= guaranteed;
257 }) >= l) {
258 break;
259 }
260 }
261 std::partial_sort(cand.begin(), cand.begin() + l, cand.end());
262 std::vector<Index> result(l);
263 for (Index k = 0; k < l; ++k) {
264 result[k] = cand[k].second;
265 }
266 return result;
267}
268
269// ------------------------------------------------------------ the model
270
272 Check();
273 citations::Cite("Baumeier:2012");
274 citations::Cite("Kordt:2014");
275 if (!opt_.traps.empty() && opt_.trap_factor > 0.0) {
276 citations::Cite("Chen:2025b");
277 }
278 ModelRandom points(opt_.seed, 0);
279 iterations_ = SamplePoints(opt_.points, opt_.box, opt_.sites, points,
281 ModelRandom energies(opt_.seed, 1);
282 GenerateEnergies(energies);
283 ModelRandom traps(opt_.seed, 3);
284 InsertTraps(traps);
285 ModelRandom pairs(opt_.seed, 2);
286 GeneratePairs(pairs);
287}
288
290 auto fail = [](const std::string& m) {
291 throw std::runtime_error("StochasticNetwork: " + m);
292 };
293 if (!(opt_.box.minCoeff() > 0.0)) {
294 fail("box lengths must be positive");
295 }
296 if (opt_.sites <= 0) {
297 fail("need at least one site");
298 }
299 const PointProcess& pp = opt_.points;
300 if (!(pp.hard_core >= 0.0) || !(pp.radius_mean >= 0.0) ||
301 !(pp.radius_variance >= 0.0) || !(pp.candidates > 0.0) ||
302 pp.max_iterations <= 0) {
303 fail(
304 "point process: hard core, radius mean and variance must be >= 0, "
305 "candidates and iterations > 0");
306 }
307 if (pp.radius_variance > 0.0 && pp.radius_mean == 0.0) {
308 fail("point process: a radius variance needs a positive radius mean");
309 }
310 if (opt_.connection_r.empty() ||
311 opt_.connection_r.size() != opt_.connection_p.size()) {
312 fail("connection table: r and p need the same, nonzero length");
313 }
314 for (std::size_t k = 0; k < opt_.connection_r.size(); ++k) {
315 if (!(opt_.connection_p[k] >= 0.0 && opt_.connection_p[k] <= 1.0)) {
316 fail("connection probabilities must be in [0, 1]");
317 }
318 if (!(opt_.connection_r[k] >= 0.0) ||
319 (k > 0 && !(opt_.connection_r[k] > opt_.connection_r[k - 1]))) {
320 fail("connection distances must be >= 0 and ascending");
321 }
322 }
323 if (!(2.0 * opt_.connection_r.back() < opt_.box.minCoeff())) {
324 fail("the largest connection distance must be smaller than half the box");
325 }
326 if (opt_.carriers.empty()) {
327 fail("need at least one carrier");
328 }
329 for (std::size_t c = 0; c < opt_.carriers.size(); ++c) {
330 const Carrier& car = opt_.carriers[c];
331 if (!car.state.isKMCState()) {
332 fail(car.state.ToString() + " is not a hopping state (e, h, s, t)");
333 }
334 for (std::size_t d = 0; d < c; ++d) {
335 if (opt_.carriers[d].state == car.state) {
336 fail("carrier " + car.state.ToString() + " given twice");
337 }
338 }
339 const EnergyModel& e = car.energy;
340 double w_c = 1.0 - e.w_a - e.w_b;
341 if (!(e.sigma >= 0.0) || !(e.w_a >= 0.0) || !(e.w_b >= 0.0) ||
342 w_c < -1e-12) {
343 fail("energy model: sigma, w_a, w_b >= 0 and w_a + w_b <= 1");
344 }
345 if (e.l_b <= 0 || e.l_c <= 0 || (e.w_b > 0.0 && e.l_b > opt_.sites) ||
346 (w_c > 1e-12 && e.l_c > opt_.sites)) {
347 fail("energy model: l_b and l_c must be in [1, number of sites]");
348 }
349 const CouplingModel& m = car.coupling;
350 for (std::size_t k = 1; k < m.residual_quantiles.size(); ++k) {
351 if (m.residual_quantiles[k] < m.residual_quantiles[k - 1]) {
352 fail("coupling model: residual quantiles must be ascending");
353 }
354 }
355 }
356 if (!(opt_.trap_factor >= 0.0)) {
357 fail("the trap factor must be >= 0");
358 }
359 for (const Trap& t : opt_.traps) {
360 if (t.carrier < 0 || t.carrier >= Index(opt_.carriers.size()) ||
361 !(t.density >= 0.0) || t.offsets.empty()) {
362 fail("traps need a carrier, a density >= 0 and at least one site");
363 }
364 for (const auto& o : t.offsets) {
365 if (o.size() != opt_.carriers.size()) {
366 fail("trap offsets need one value per carrier");
367 }
368 }
369 }
370 if (!(std::abs(opt_.energy_correlation) <= 1.0) ||
371 !(std::abs(opt_.coupling_correlation) <= 1.0)) {
372 fail("correlations must be in [-1, 1]");
373 }
374}
375
377 const Eigen::Vector3d& box, Index n,
378 ModelRandom& random,
379 std::vector<Eigen::Vector3d>& positions,
380 std::vector<double>& radii) {
381 // radii by inversion of the Gamma distribution function: continuous in the
382 // parameters for the same random numbers (common random numbers in fits);
383 // tabulated quantiles, exact in the tails
384 const bool gamma = pp.radius_variance > 0.0;
385 const double shape =
386 gamma ? pp.radius_mean * pp.radius_mean / pp.radius_variance : 0.0;
387 const double scale = gamma ? pp.radius_variance / pp.radius_mean : 0.0;
388 constexpr Index M = 512;
389 std::vector<double> table;
390 if (gamma) {
391 table.resize(M + 1);
392 for (Index k = 1; k < M; ++k) {
393 table[k] = boost::math::gamma_p_inv(shape, double(k) / double(M));
394 }
395 }
396 auto radius = [&]() {
397 if (!gamma) {
398 return pp.hard_core + pp.radius_mean;
399 }
400 double u = random.Uniform();
401 double t = u * double(M);
402 Index k = Index(t);
403 double x;
404 if (k < 1 || k >= M - 1) {
405 x = boost::math::gamma_p_inv(shape, std::min(u, 1.0 - 1e-16));
406 } else {
407 x = table[k] + (t - double(k)) * (table[k + 1] - table[k]);
408 }
409 return pp.hard_core + x * scale;
410 };
411 const double half_box = 0.5 * box.minCoeff();
412 positions.clear();
413 radii.clear();
414 positions.reserve(n);
415 radii.reserve(n);
416 double max_accepted = 0.0;
417 std::vector<Eigen::Vector3d> cand;
418 std::vector<double> cand_r;
419 std::vector<char> keep;
420 std::vector<Index> survivors;
421 for (Index it = 1; it <= pp.max_iterations; ++it) {
422 Index m = random.Poisson(pp.candidates * double(n));
423 cand.resize(m);
424 cand_r.resize(m);
425 double max_cand = 0.0;
426 for (Index k = 0; k < m; ++k) {
427 for (Index d = 0; d < 3; ++d) {
428 cand[k][d] = (1.0 - random.Uniform()) * box[d];
429 }
430 cand_r[k] = radius();
431 max_cand = std::max(max_cand, cand_r[k]);
432 }
433 double search = std::max(max_cand, max_accepted);
434 if (!(search < half_box)) {
435 throw std::runtime_error("StochasticNetwork: point radii (" +
436 std::to_string(search * tools::conv::bohr2nm) +
437 " nm) must be smaller than half the box");
438 }
439 // a zero search radius means no exclusion
440 search = std::max(search, 1e-9 * half_box);
441 keep.assign(m, 1);
442 // against accepted points: distance >= max(R_candidate, R_accepted)
443 if (!positions.empty()) {
444 PeriodicCells acc(box, search, positions);
445 for (Index k = 0; k < m; ++k) {
446 acc.Within(cand[k], std::max(cand_r[k], max_accepted),
447 [&](Index j, const Eigen::Vector3d&, double dist) {
448 if (dist < std::max(cand_r[k], radii[j])) {
449 keep[k] = 0;
450 }
451 });
452 }
453 }
454 // dominance among all candidates of the iteration: inside the sphere of
455 // a larger point (equal radii: the earlier point wins)
456 {
457 PeriodicCells cells(box, std::max(max_cand, 1e-9 * half_box), cand);
458 for (Index k = 0; k < m; ++k) {
459 if (!keep[k]) {
460 continue; // removed already; it still dominates others
461 }
462 cells.Within(cand[k], std::max(max_cand, 1e-9 * half_box),
463 [&](Index j, const Eigen::Vector3d&, double dist) {
464 if (j == k) {
465 return;
466 }
467 bool larger = cand_r[j] > cand_r[k] ||
468 (j < k && cand_r[j] == cand_r[k]);
469 if (larger && dist < cand_r[j]) {
470 keep[k] = 0;
471 }
472 });
473 }
474 }
475 survivors.clear();
476 for (Index k = 0; k < m; ++k) {
477 if (keep[k]) {
478 survivors.push_back(k);
479 }
480 }
481 Index need = n - Index(positions.size());
482 if (Index(survivors.size()) > need) {
483 // random subset (partial Fisher-Yates)
484 for (Index k = 0; k < need; ++k) {
485 Index r = k + random.Integer(Index(survivors.size()) - k);
486 std::swap(survivors[k], survivors[r]);
487 }
488 survivors.resize(need);
489 }
490 for (Index k : survivors) {
491 positions.push_back(cand[k]);
492 radii.push_back(cand_r[k]);
493 max_accepted = std::max(max_accepted, cand_r[k]);
494 }
495 if (Index(positions.size()) == n) {
496 return it;
497 }
498 }
499 throw std::runtime_error(
500 "StochasticNetwork: the point process placed only " +
501 std::to_string(positions.size()) + " of " + std::to_string(n) +
502 " sites in " + std::to_string(pp.max_iterations) +
503 " iterations; the box is too small for the radii (increase the box or "
504 "max_iterations)");
505}
506
508 const PeriodicCells& cells, const EnergyModel& m, Index sites,
509 const std::vector<double>& A, const std::vector<double>& Bf,
510 const std::vector<double>& C) {
511 if (Index(A.size()) != sites || Index(Bf.size()) != sites ||
512 Index(C.size()) != sites) {
513 throw std::runtime_error(
514 "StochasticNetwork::MovingAverage: need one field value per site");
515 }
516 const double w_c = std::max(0.0, 1.0 - m.w_a - m.w_b);
517 const double fa = std::sqrt(m.w_a);
518 const double fb = std::sqrt(m.w_b / double(m.l_b));
519 const double fc = std::sqrt(w_c / double(m.l_c));
520 const Index lb = m.w_b > 0.0 ? m.l_b : 0;
521 const Index lc = w_c > 0.0 ? m.l_c : 0;
522 const Index lmax = std::max(lb, lc);
523 std::vector<double> z(sites);
524#pragma omp parallel for schedule(dynamic, 256)
525 for (Index i = 0; i < sites; ++i) {
526 std::vector<Index> nb = cells.Nearest(i, lmax);
527 double b = 0.0;
528 for (Index k = 0; k < lb; ++k) {
529 b += Bf[nb[k]];
530 }
531 double c = 0.0;
532 for (Index k = 0; k < lc; ++k) {
533 c += C[nb[k]];
534 }
535 z[i] = fa * A[i] + fb * b + fc * c;
536 }
537 return z;
538}
539
541 const Index n = NumberOfSites();
542 // cells of about eight points for the nearest-neighbour searches
543 double volume = opt_.box.prod();
544 double radius =
545 std::min(0.49 * opt_.box.minCoeff(), 2.0 * std::cbrt(volume / double(n)));
546 PeriodicCells cells(opt_.box, radius, positions_);
547 energies_.clear();
548 std::vector<double> first;
549 std::vector<double> A(n), B(n), C(n);
550 for (std::size_t c = 0; c < opt_.carriers.size(); ++c) {
551 const EnergyModel& m = opt_.carriers[c].energy;
552 // the fields are always drawn, so that the energies of one carrier do
553 // not depend on the model of another
554 for (std::vector<double>* f : {&A, &B, &C}) {
555 for (double& x : *f) {
556 x = random.Normal();
557 }
558 }
559 std::vector<double> z = MovingAverage(cells, m, n, A, B, C);
560 if (c == 0) {
561 first = z;
562 } else if (c == 1 && opt_.energy_correlation != 0.0) {
563 double rho = opt_.energy_correlation;
564 double s = std::sqrt(std::max(0.0, 1.0 - rho * rho));
565 for (Index i = 0; i < n; ++i) {
566 z[i] = rho * first[i] + s * z[i];
567 }
568 }
569 for (double& x : z) {
570 x = m.mean + m.sigma * x;
571 }
572 energies_.push_back(std::move(z));
573 }
574}
575
576double StochasticNetwork::Connection(double r) const {
577 const std::vector<double>& rs = opt_.connection_r;
578 const std::vector<double>& ps = opt_.connection_p;
579 if (r <= rs.front()) {
580 return ps.front();
581 }
582 if (r >= rs.back()) {
583 return 0.0;
584 }
585 std::size_t k = std::size_t(std::upper_bound(rs.begin(), rs.end(), r) -
586 rs.begin()); // rs[k-1] < r < rs[k]
587 double t = (r - rs[k - 1]) / (rs[k] - rs[k - 1]);
588 return ps[k - 1] + t * (ps[k] - ps[k - 1]);
589}
590
591namespace {
592double NormalCdf(double g) { return 0.5 * std::erfc(-g / std::sqrt(2.0)); }
593double NormalQuantile(double u) {
594 return -std::sqrt(2.0) * boost::math::erfc_inv(2.0 * u);
595}
596} // namespace
597
598double StochasticNetwork::Residual(const CouplingModel& m, double g) {
599 const std::vector<double>& q = m.residual_quantiles;
600 const Index K = Index(q.size());
601 if (K == 0) {
602 return g;
603 }
604 if (K == 1) {
605 return q[0] + g;
606 }
607 // q[k] belongs to the normal quantile g_k = Phi^-1((k + 1/2) / K);
608 // linear interpolation in g, beyond the table like a normal distribution
609 auto node = [K](Index k) {
610 return NormalQuantile((double(k) + 0.5) / double(K));
611 };
612 double t = NormalCdf(g) * double(K) - 0.5;
613 if (t <= 0.0) {
614 return q[0] + (g - node(0));
615 }
616 if (t >= double(K - 1)) {
617 return q[K - 1] + (g - node(K - 1));
618 }
619 Index k = std::min(Index(std::floor(t)), K - 2);
620 double g0 = node(k);
621 double g1 = node(k + 1);
622 double f = std::min(1.0, std::max(0.0, (g - g0) / (g1 - g0)));
623 return q[k] + f * (q[k + 1] - q[k]);
624}
625
626double StochasticNetwork::Log10J2(const CouplingModel& m, double r, double g) {
627 double rnm = r * tools::conv::bohr2nm;
628 double mean = m.mean_offset + m.mean_slope * rnm;
629 double var = std::max(0.0, m.var_offset + m.var_slope * rnm);
630 return mean + std::sqrt(var) * Residual(m, g);
631}
632
634 inserted_.clear();
635 if (opt_.traps.empty() || opt_.trap_factor == 0.0) {
636 return;
637 }
638 const Index n = NumberOfSites();
639 double volume = opt_.box.prod();
640 double radius =
641 std::min(0.49 * opt_.box.minCoeff(), 2.0 * std::cbrt(volume / double(n)));
642 PeriodicCells cells(opt_.box, radius, positions_);
643 std::vector<char> used(n, 0);
644 for (std::size_t t = 0; t < opt_.traps.size(); ++t) {
645 const Trap& trap = opt_.traps[t];
646 double expected = trap.density * opt_.trap_factor * double(n);
647 Index count = Index(std::floor(expected));
648 if (random.Uniform() <= expected - double(count)) {
649 ++count;
650 }
651 const Index size = Index(trap.offsets.size());
652 for (Index k = 0; k < count; ++k) {
653 // a random free site whose nearest sites are free as well
654 std::vector<Index> sites;
655 for (Index attempt = 0; attempt < 1000 && sites.empty(); ++attempt) {
656 Index centre = random.Integer(n);
657 std::vector<Index> nb = cells.Nearest(centre, size);
658 if (std::none_of(nb.begin(), nb.end(),
659 [&used](Index i) { return used[i] != 0; })) {
660 sites = nb;
661 }
662 }
663 if (sites.empty()) {
664 throw std::runtime_error(
665 "StochasticNetwork: no free place for a trap; the trap density "
666 "is too high");
667 }
668 for (Index q = 0; q < size; ++q) {
669 used[sites[q]] = 1;
670 for (std::size_t c = 0; c < opt_.carriers.size(); ++c) {
671 energies_[c][sites[q]] =
672 opt_.carriers[c].energy.mean + trap.offsets[q][c];
673 }
674 }
675 inserted_.push_back({Index(t), sites});
676 }
677 }
678}
679
681 const Index n = NumberOfSites();
682 const double rmax = opt_.connection_r.back();
683 pairs_.clear();
684 if (!(rmax > 0.0)) {
685 return;
686 }
687 PeriodicCells cells(opt_.box, rmax, positions_);
688 const double ev2 = tools::conv::ev2hrt * tools::conv::ev2hrt;
689 const std::size_t ncar = opt_.carriers.size();
690 const double rho = opt_.coupling_correlation;
691 const double s = std::sqrt(std::max(0.0, 1.0 - rho * rho));
692 struct Partner {
693 Index j;
694 Eigen::Vector3d R;
695 double dist;
696 };
697 std::vector<Partner> partners;
698 std::vector<double> g(ncar);
699 for (Index i = 0; i < n; ++i) {
700 partners.clear();
701 cells.Within(positions_[i], rmax,
702 [&](Index j, const Eigen::Vector3d& conn, double dist) {
703 if (j > i) {
704 partners.push_back({j, conn, dist});
705 }
706 });
707 std::sort(partners.begin(), partners.end(),
708 [](const Partner& a, const Partner& b) { return a.j < b.j; });
709 for (const Partner& p : partners) {
710 double f = Connection(p.dist);
711 if (f <= 0.0 || (f < 1.0 && random.Uniform() > f)) {
712 continue;
713 }
714 for (std::size_t c = 0; c < ncar; ++c) {
715 g[c] = random.Normal();
716 }
717 if (ncar > 1) {
718 g[1] = rho * g[0] + s * g[1];
719 }
720 Pair pair{i, p.j, p.R, std::vector<double>(ncar)};
721 for (std::size_t c = 0; c < ncar; ++c) {
722 pair.J2[c] =
723 std::pow(10.0, Log10J2(opt_.carriers[c].coupling, p.dist, g[c])) *
724 ev2;
725 }
726 pairs_.push_back(std::move(pair));
727 }
728 }
729}
730
732 if (!top.Segments().empty() || top.NBList().size() > 0) {
733 throw std::runtime_error("StochasticNetwork: topology must be empty");
734 }
735 Eigen::Matrix3d box = Eigen::Matrix3d::Zero();
736 box.diagonal() = opt_.box;
737 top.setBox(box);
738 top.setStep(0);
739 top.setTime(0.0);
740 for (Index i = 0; i < NumberOfSites(); ++i) {
741 Segment& seg = top.AddSegment(opt_.segment_name);
742 seg.push_back(Atom(0, "C", positions_[i]));
743 for (std::size_t c = 0; c < opt_.carriers.size(); ++c) {
744 const Carrier& car = opt_.carriers[c];
745 seg.setEMpoles(car.state, energies_[c][i]);
746 seg.setU_xX_nN(car.U_xX_nN, car.state);
747 seg.setU_nX_nN(car.U_nX_nN, car.state);
748 seg.setU_xN_xX(car.U_xN_xX, car.state);
749 }
750 }
751 const std::vector<Segment>& segs = top.Segments();
752 QMNBList& nblist = top.NBList();
753 for (const Pair& p : pairs_) {
754 QMPair& pair = nblist.Add(segs[p.i], segs[p.j], p.R);
755 for (std::size_t c = 0; c < opt_.carriers.size(); ++c) {
756 pair.setJeff2(p.J2[c], opt_.carriers[c].state);
757 }
758 }
759}
760
761// ------------------------------------------------------- parameter file
762
764 const tools::Property& p) {
765 const double nm = tools::conv::nm2bohr;
766 const double ev = tools::conv::ev2hrt;
767 Options opt;
768 opt.sites = p.ifExistsReturnElseReturnDefault<Index>("sites", 0);
769 if (p.exists("box")) {
770 std::vector<double> box = p.get("box").as<std::vector<double>>();
771 if (box.size() != 3) {
772 throw std::runtime_error("stochasticnetwork: box needs three lengths");
773 }
774 opt.box = Eigen::Vector3d(box[0], box[1], box[2]) * nm;
775 }
776 opt.seed = p.ifExistsReturnElseReturnDefault<std::uint64_t>("seed", 1);
777 opt.segment_name =
778 p.ifExistsReturnElseReturnDefault<std::string>("segmentname", "site");
779 const tools::Property& pp = p.get("points");
780 opt.points.hard_core = pp.get("hardcore").as<double>() * nm;
781 opt.points.radius_mean = pp.get("radiusmean").as<double>() * nm;
782 opt.points.radius_variance = pp.get("radiusvariance").as<double>() * nm * nm;
783 opt.points.candidates =
784 pp.ifExistsReturnElseReturnDefault<double>("candidates", 3.0);
786 pp.ifExistsReturnElseReturnDefault<Index>("maxiterations", 200);
787 opt.connection_r = p.get("connection.r").as<std::vector<double>>();
788 for (double& r : opt.connection_r) {
789 r *= nm;
790 }
791 opt.connection_p = p.get("connection.p").as<std::vector<double>>();
793 p.ifExistsReturnElseReturnDefault<double>("energycorrelation", 0.0);
795 p.ifExistsReturnElseReturnDefault<double>("couplingcorrelation", 0.0);
796 for (const tools::Property* c : p.Select("carrier")) {
797 Carrier car;
798 car.state = QMStateType(c->get("state").as<std::string>());
799 const tools::Property& e = c->get("energy");
800 car.energy.mean = e.get("mean").as<double>() * ev;
801 car.energy.sigma = e.get("sigma").as<double>() * ev;
802 car.energy.w_a = e.get("w_a").as<double>();
803 car.energy.w_b = e.get("w_b").as<double>();
804 car.energy.l_b = e.get("l_b").as<Index>();
805 car.energy.l_c = e.get("l_c").as<Index>();
806 const tools::Property& j = c->get("coupling");
807 car.coupling.mean_offset = j.get("meanoffset").as<double>();
808 car.coupling.mean_slope = j.get("meanslope").as<double>();
809 car.coupling.var_offset = j.get("varoffset").as<double>();
810 car.coupling.var_slope = j.get("varslope").as<double>();
811 if (j.exists("quantiles")) {
813 j.get("quantiles").as<std::vector<double>>();
814 }
815 car.U_xX_nN =
816 c->ifExistsReturnElseReturnDefault<double>("U_xX_nN", 0.0) * ev;
817 car.U_nX_nN =
818 c->ifExistsReturnElseReturnDefault<double>("U_nX_nN", 0.0) * ev;
819 car.U_xN_xX =
820 c->ifExistsReturnElseReturnDefault<double>("U_xN_xX", 0.0) * ev;
821 opt.carriers.push_back(car);
822 }
823 for (const tools::Property* t : p.Select("trap")) {
824 Trap trap;
825 QMStateType state(t->get("carrier").as<std::string>());
826 trap.carrier = -1;
827 for (std::size_t c = 0; c < opt.carriers.size(); ++c) {
828 if (opt.carriers[c].state == state) {
829 trap.carrier = Index(c);
830 }
831 }
832 if (trap.carrier < 0) {
833 throw std::runtime_error("stochasticnetwork: trap for carrier " +
834 state.ToString() + " without a carrier block");
835 }
836 trap.density = t->get("density").as<double>();
837 trap.release_rate =
838 t->ifExistsReturnElseReturnDefault<double>("releaserate", 0.0);
839 for (const tools::Property* site : t->Select("site")) {
840 std::vector<double> o = site->as<std::vector<double>>();
841 for (double& x : o) {
842 x *= ev;
843 }
844 trap.offsets.push_back(o);
845 }
846 opt.traps.push_back(trap);
847 }
848 return opt;
849}
850
851std::string StochasticNetwork::ToXML(const Options& opt) {
852 const double nm = tools::conv::bohr2nm;
853 const double ev = tools::conv::hrt2ev;
854 std::ostringstream out;
855 out << std::setprecision(10);
856 auto list = [&out](const std::vector<double>& v, double f) {
857 for (std::size_t k = 0; k < v.size(); ++k) {
858 out << (k ? " " : "") << v[k] * f;
859 }
860 };
861 out << "<?xml version=\"1.0\"?>\n";
862 out << "<!-- stochastic network model; lengths nm, energies eV, couplings "
863 "log10(J^2/eV^2) with r in nm -->\n";
864 out << "<stochasticnetwork>\n";
865 out << " <sites>" << opt.sites << "</sites>\n";
866 out << " <box>" << opt.box[0] * nm << " " << opt.box[1] * nm << " "
867 << opt.box[2] * nm << "</box>\n";
868 out << " <seed>" << opt.seed << "</seed>\n";
869 out << " <segmentname>" << opt.segment_name << "</segmentname>\n";
870 out << " <points>\n";
871 out << " <hardcore>" << opt.points.hard_core * nm << "</hardcore>\n";
872 out << " <radiusmean>" << opt.points.radius_mean * nm << "</radiusmean>\n";
873 out << " <radiusvariance>" << opt.points.radius_variance * nm * nm
874 << "</radiusvariance>\n";
875 out << " <candidates>" << opt.points.candidates << "</candidates>\n";
876 out << " <maxiterations>" << opt.points.max_iterations
877 << "</maxiterations>\n";
878 out << " </points>\n";
879 out << " <connection>\n <r>";
880 list(opt.connection_r, nm);
881 out << "</r>\n <p>";
882 list(opt.connection_p, 1.0);
883 out << "</p>\n </connection>\n";
884 out << " <energycorrelation>" << opt.energy_correlation
885 << "</energycorrelation>\n";
886 out << " <couplingcorrelation>" << opt.coupling_correlation
887 << "</couplingcorrelation>\n";
888 for (const Carrier& c : opt.carriers) {
889 out << " <carrier>\n";
890 out << " <state>" << c.state.ToString() << "</state>\n";
891 out << " <energy>\n";
892 out << " <mean>" << c.energy.mean * ev << "</mean>\n";
893 out << " <sigma>" << c.energy.sigma * ev << "</sigma>\n";
894 out << " <w_a>" << c.energy.w_a << "</w_a>\n";
895 out << " <w_b>" << c.energy.w_b << "</w_b>\n";
896 out << " <l_b>" << c.energy.l_b << "</l_b>\n";
897 out << " <l_c>" << c.energy.l_c << "</l_c>\n";
898 out << " </energy>\n";
899 out << " <coupling>\n";
900 out << " <meanoffset>" << c.coupling.mean_offset << "</meanoffset>\n";
901 out << " <meanslope>" << c.coupling.mean_slope << "</meanslope>\n";
902 out << " <varoffset>" << c.coupling.var_offset << "</varoffset>\n";
903 out << " <varslope>" << c.coupling.var_slope << "</varslope>\n";
904 if (!c.coupling.residual_quantiles.empty()) {
905 out << " <quantiles>";
906 list(c.coupling.residual_quantiles, 1.0);
907 out << "</quantiles>\n";
908 }
909 out << " </coupling>\n";
910 out << " <U_xX_nN>" << c.U_xX_nN * ev << "</U_xX_nN>\n";
911 out << " <U_nX_nN>" << c.U_nX_nN * ev << "</U_nX_nN>\n";
912 out << " <U_xN_xX>" << c.U_xN_xX * ev << "</U_xN_xX>\n";
913 out << " </carrier>\n";
914 }
915 for (const Trap& t : opt.traps) {
916 out << " <!-- trap: site energies relative to the bulk mean (eV) for the "
917 "carriers in the order of the carrier blocks, deepest site first "
918 "-->\n";
919 out << " <trap>\n";
920 out << " <carrier>" << opt.carriers[t.carrier].state.ToString()
921 << "</carrier>\n";
922 out << " <density>" << t.density << "</density>\n";
923 out << " <releaserate>" << t.release_rate << "</releaserate>\n";
924 for (const auto& o : t.offsets) {
925 out << " <site>";
926 list(o, ev);
927 out << "</site>\n";
928 }
929 out << " </trap>\n";
930 }
931 out << "</stochasticnetwork>\n";
932 return out.str();
933}
934
935} // namespace xtp
936} // 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
bool exists(const std::string &key) const
check whether property exists
Definition property.cc:122
T as() const
return value as type
Definition property.h:283
T ifExistsReturnElseReturnDefault(const std::string &key, T defaultvalue) const
Definition property.h:332
std::vector< Property * > Select(const std::string &filter)
select property based on a filter
Definition property.cc:185
void push_back(const T &atom)
Platform independent random numbers for the stochastic models: mt19937_64 with self-written transform...
Index Poisson(double mean)
Poisson distributed count.
Index Integer(Index n)
integer in [0, n)
ModelRandom(std::uint64_t seed, std::uint64_t stream)
independent streams for the same seed (points, energies, pairs)
Periodic orthorhombic cell list for neighbour searches.
std::array< Index, 3 > n_
PeriodicCells(const Eigen::Vector3d &box, double radius, const std::vector< Eigen::Vector3d > &points)
box lengths and the largest search radius used with Within()
std::vector< Index > NearestTo(const Eigen::Vector3d &p, Index l) const
indices of the l points nearest to any position p
Index CellIndex(const Eigen::Vector3d &p, std::array< Index, 3 > &c) const
std::vector< Index > start_
std::vector< Index > members_
void Shell(const std::array< Index, 3 > &c, Index s, F &&f) const
Eigen::Vector3d Connection(const Eigen::Vector3d &r1, const Eigen::Vector3d &r2) const
shortest periodic connection r2 - r1
void Within(const Eigen::Vector3d &p, double radius, const std::function< void(Index, const Eigen::Vector3d &, double)> &f) const
std::vector< Index > Nearest(Index i, Index l) const
const std::vector< Eigen::Vector3d > & points_
QMPair & Add(const Segment &seg1, const Segment &seg2, const Eigen::Vector3d &r)
Definition qmnblist.cc:27
bool isKMCState() const
Definition qmstate.h:78
std::string ToString() const
Definition qmstate.cc:35
void setU_xX_nN(double dU, QMStateType state)
UxX - UnN.
Definition segment.h:53
void setU_nX_nN(double dU, QMStateType state)
UnX - UnN.
Definition segment.h:57
void setEMpoles(QMStateType state, double energy)
Definition segment.h:87
void setU_xN_xX(double dU, QMStateType state)
UxN - UxX.
Definition segment.h:61
StochasticNetwork(const Options &opt)
static Index SamplePoints(const PointProcess &pp, const Eigen::Vector3d &box, Index sites, ModelRandom &random, std::vector< Eigen::Vector3d > &positions, std::vector< double > &radii)
void BuildTopology(Topology &top) const
fill an empty topology (segments with one pseudo atom, box, pairs)
std::vector< InsertedTrap > inserted_
std::vector< std::vector< double > > energies_
std::vector< Eigen::Vector3d > positions_
void GenerateEnergies(ModelRandom &random)
static std::vector< double > MovingAverage(const PeriodicCells &cells, const EnergyModel &m, Index sites, const std::vector< double > &A, const std::vector< double > &Bf, const std::vector< double > &C)
double Connection(double r) const
connection probability at distance r (bohr)
static double Log10J2(const CouplingModel &m, double r, double g)
log10(J^2/eV^2) for distance r (bohr) and standard normal g
static std::string ToXML(const Options &opt)
the parameter file (root element "stochasticnetwork")
void InsertTraps(ModelRandom &random)
static double Residual(const CouplingModel &m, double g)
standardized residual for a standard normal g
void GeneratePairs(ModelRandom &random)
static Options FromProperty(const tools::Property &p)
Container for segments and box and atoms.
Definition topology.h:41
Segment & AddSegment(std::string segment_name)
Definition topology.cc:76
void setTime(double time)
Definition topology.h:78
void setBox(const Eigen::Matrix3d &box, csg::BoundaryCondition::eBoxtype boxtype=csg::BoundaryCondition::typeAuto)
Definition topology.cc:85
std::vector< Segment > & Segments()
Definition topology.h:58
QMNBList & NBList()
Definition topology.h:70
void setStep(Index step)
Definition topology.h:76
const double ev2hrt
Definition constants.h:54
const double bohr2nm
Definition constants.h:46
const double hrt2ev
Definition constants.h:53
const double Pi
Definition constants.h:36
const double nm2bohr
Definition constants.h:47
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
std::vector< Trap > traps
trap templates, inserted after the Gaussian energies
std::vector< double > connection_r
connection probability table: f at distances r (ascending, bohr)
double candidates
candidates per iteration in units of the number of sites
std::vector< std::vector< double > > offsets
offsets[k][c]: site k (deepest first), carrier c, Hartree